Skip to content
10 changes: 7 additions & 3 deletions app/controllers/concerns/importable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ def import_csv
data = File.read(params[:file].path, encoding: "BOM|UTF-8")
csv = CSV.parse(data, headers: true)
if csv.count.positive? && csv.first.headers.all? { |header| !header.nil? }
errors = resource_model.import_csv(csv, current_organization.id)
if errors.empty?
results = resource_model.import_csv(csv, current_organization.id)
if results[:errors].empty?
flash[:notice] = "#{resource_model_humanized} were imported successfully!"
else
flash[:error] = "The following #{resource_model_humanized} did not import successfully:\n#{errors.join("\n")}"
flash[:error] = "The following #{resource_model_humanized} did not import successfully:\n#{results[:errors].join("\n")}"
end

if results[:warnings].present?
flash[:alert] = "The following #{resource_model_humanized} imported with warnings:\n#{results[:warnings].join("\n")}"
end
else
flash[:error] = "Check headers in file!"
Expand Down
3 changes: 2 additions & 1 deletion app/models/concerns/provideable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def self.import_csv(csv, organization)

loc.save!
end
[]

{errors: [], warnings: []}
end

def self.csv_export_headers
Expand Down
4 changes: 3 additions & 1 deletion app/models/donation_site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class DonationSite < ApplicationRecord

def self.import_csv(csv, organization)
errors = []

csv.each_with_index do |row, index|
loc = DonationSite.new(row.to_hash)
loc.organization_id = organization
Expand All @@ -47,7 +48,8 @@ def self.import_csv(csv, organization)
errors << "Row #{index + 2}, #{row.to_hash["name"]} - #{loc.errors.full_messages.join(", ")}"
end
end
errors

{errors: errors, warnings: []}
end

def self.csv_export_headers
Expand Down
31 changes: 26 additions & 5 deletions app/models/partner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Partner < ApplicationRecord
validate :correct_document_mime_type

before_save { email&.downcase! }
before_create :default_send_reminders_to_false, if: :send_reminders_nil?
before_update :invite_new_partner, if: :should_invite_because_email_changed?

scope :alphabetized, -> { order(:name) }
Expand Down Expand Up @@ -103,18 +104,30 @@ def approvable?
# better to extract this outside of the model
def self.import_csv(csv, organization_id)
errors = []
warnings = []
organization = Organization.find(organization_id)

csv.each do |row|
hash_rows = Hash[row.to_hash.map { |k, v| [k.downcase, v] }]

svc = PartnerCreateService.new(organization: organization, partner_attrs: hash_rows)
svc.call
if svc.errors.present?
errors << "#{svc.partner.name}: #{svc.partner.errors.full_messages.to_sentence}"
partner_create_service = PartnerCreateService.new(organization: organization, partner_attrs: hash_rows)
partner_create_service.call

if partner_create_service.errors.present?
formatted_errors = partner_create_service.errors.map do |error|
"#{error.attribute.to_s.humanize} #{error.message.downcase}"
end
errors << "#{partner_create_service.partner.name}: #{formatted_errors.to_sentence}"

elsif partner_create_service.warnings.present?
formatted_warnings = partner_create_service.warnings.map do |warning|
"#{warning.attribute.to_s.humanize} #{warning.message.downcase}"
end
warnings << "#{partner_create_service.partner.name}: #{formatted_warnings.to_sentence}"
end
end
errors

{errors: errors, warnings: warnings}
end

def partials_to_show
Expand Down Expand Up @@ -165,6 +178,14 @@ def correct_document_mime_type
end
end

def default_send_reminders_to_false
self.send_reminders = false
end

def send_reminders_nil?
send_reminders.nil?
end

def invite_new_partner
UserInviteService.invite(email: email, roles: [Role::PARTNER], resource: self)
end
Expand Down
3 changes: 2 additions & 1 deletion app/models/storage_location.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ def self.import_csv(csv, organization)
loc.organization_id = organization
loc.save!
end
[]

{errors: [], warnings: []}
end

# NOTE: We should generalize this elsewhere -- Importable concern?
Expand Down
24 changes: 24 additions & 0 deletions app/services/partner_create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ def initialize(organization:, partner_attrs:)
end

def call
process_default_storage_location

@partner = organization.partners.build(partner_attrs)

if @partner.valid?
Expand Down Expand Up @@ -37,5 +39,27 @@ def call

private

def process_default_storage_location
return unless partner_attrs.has_key?("default_storage_location")

if partner_attrs["default_storage_location"].blank?
partner_attrs.delete("default_storage_location")
else
default_storage_location_name = partner_attrs["default_storage_location"]&.titlecase
default_storage_location_id = StorageLocation.find_by(
name: default_storage_location_name,
organization: organization.id
)&.id

if default_storage_location_id.nil?
add_warning(:default_storage_location,
"is not a storage location for this partner's organization")
end

partner_attrs.delete("default_storage_location")
partner_attrs["default_storage_location_id"] = default_storage_location_id
end
end

attr_reader :organization, :partner_attrs
end
9 changes: 8 additions & 1 deletion app/services/service_object_errors_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ def errors
@errors ||= ActiveModel::Errors.new(self)
end

def warnings
@warnings ||= ActiveModel::Errors.new(self)
end

def add_warning(attribute, message)
warnings.add(attribute, message)
end

def read_attribute_for_validation(attr)
send(attr)
end
Expand All @@ -45,4 +53,3 @@ def lookup_ancestors
end
end
end

12 changes: 12 additions & 0 deletions app/views/dashboard/_getting_started_prompt.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,17 @@
<%= button_to "Dismiss", "/manage", method: :patch, params: {organization: { bank_is_set_up: true }}, class: "btn btn-primary" %>
</div>

<%= render(
layout: "shared/csv_import_modal",
locals: {
import_type: "Partners",
csv_template_url: "/partners_template_updated.csv",
csv_import_url: import_csv_partners_path,
},
) do %>
<li>Open the CSV file with Excel or your favourite spreadsheet program.</li>
<li>Delete the sample data and enter your partner agency names and addresses in the appropriate columns.</li>
<li>Save the file as a CSV file.</li>
<% end %>
<% end %>
<% end %>
2 changes: 1 addition & 1 deletion app/views/partners/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
layout: "shared/csv_import_modal",
locals: {
import_type: "Partners",
csv_template_url: "/partners_template.csv",
csv_template_url: "/partners_template_updated.csv",
csv_import_url: import_csv_partners_path
}
) do %>
Expand Down
4 changes: 0 additions & 4 deletions public/partners_template.csv

This file was deleted.

4 changes: 4 additions & 0 deletions public/partners_template_updated.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 1,partner1@example.com,"Smithsonian Conservation Center",false,20,"test note 1"
Partner 2,partner2@example.com,"Smithsonian Conservation Center",false,30,"test note 2"
Partner 3,partner3@example.com,"Smithsonian Conservation Center",true,10,"test note 3"
4 changes: 0 additions & 4 deletions spec/fixtures/files/partners.csv

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 51,partner51@example.com,,false,50,"great partner"

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name,email,send_reminders,quota,notes
Partner 71,partner71@example.com,false,50,"great partner"
3 changes: 3 additions & 0 deletions spec/fixtures/files/partners_missing_send_reminders_field.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 51,partner51@example.com,"Smithsonian Conservation Center",,50,"great partner"

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,email,default_storage_location,quota,notes
Partner 51,partner51@example.com,"Smithsonian Conservation Center",50,"great partner"

42 changes: 21 additions & 21 deletions spec/fixtures/files/partners_with_bom_encoding.csv
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
name,email
Beaverton Police Department,krodriguez@beavertonoregon.gov
Catholic Charities,lcrombie@catholiccharitiesoregon.org
Clackamas Service Center,debramason@cscoregon.org
Healthy Familes of Clackamas County,bkersens@healthyfamiliescc.org
Dollar for Portland,jared@dollorfor.org
Emmanuel Housing Center,shauntemeyers@emmanuelpdx.org
Helensview School,hgandee@mesd.k12.or.us
JOIN,ccarroll@joinpdx.org
Job Corps (PIVOT),zutz.kayla@jobcorps.org
NARA,christman@naranw.org
NW Housing Alternatives,doty@nwhousing.org
Pregnancy Resource Center,debbie@portlandprc.org
Portland Homeless Family Solutions,emma@pdxhfs.org
Raphael House,lvold@raphaelhouse.com
The Rebecca Foundation's Cloth Diaper Closet,portland@clothforall.org
Rose Haven,adeol@rosehaven.org
Self Enhancement Inc.,stephaniep@selfenhancement.org
Teen Parent Services (PPS),lgovan@pps.net
Volunteers of America,cross@voaor.org
Central City Concern,lindsey.ramsey@ccconcern.org
name,email,default_storage_location,send_reminders,quota,notes
Beaverton Police Department,krodriguez@beavertonoregon.gov,"Smithsonian Conservation Center",true,50,"great partner"
Catholic Charities,lcrombie@catholiccharitiesoregon.org,"Smithsonian Conservation Center",true,50,"great partner"
Clackamas Service Center,debramason@cscoregon.org,"Smithsonian Conservation Center",true,50,"great partner"
Healthy Familes of Clackamas County,bkersens@healthyfamiliescc.org,"Smithsonian Conservation Center",true,50,"great partner"
Dollar for Portland,jared@dollorfor.org,"Smithsonian Conservation Center",true,50,"great partner"
Emmanuel Housing Center,shauntemeyers@emmanuelpdx.org,"Smithsonian Conservation Center",true,50,"great partner"
Helensview School,hgandee@mesd.k12.or.us,"Smithsonian Conservation Center",true,50,"great partner"
JOIN,ccarroll@joinpdx.org,"Smithsonian Conservation Center",true,50,"great partner"
Job Corps (PIVOT),zutz.kayla@jobcorps.org,"Smithsonian Conservation Center",true,50,"great partner"
NARA,christman@naranw.org,"Smithsonian Conservation Center",true,50,"great partner"
NW Housing Alternatives,doty@nwhousing.org,"Smithsonian Conservation Center",true,50,"great partner"
Pregnancy Resource Center,debbie@portlandprc.org,"Smithsonian Conservation Center",true,50,"great partner"
Portland Homeless Family Solutions,emma@pdxhfs.org,"Smithsonian Conservation Center",true,50,"great partner"
Raphael House,lvold@raphaelhouse.com,"Smithsonian Conservation Center",true,50,"great partner"
The Rebecca Foundation's Cloth Diaper Closet,portland@clothforall.org,"Smithsonian Conservation Center",true,50,"great partner"
Rose Haven,adeol@rosehaven.org,"Smithsonian Conservation Center",true,50,"great partner"
Self Enhancement Inc.,stephaniep@selfenhancement.org,"Smithsonian Conservation Center",true,50,"great partner"
Teen Parent Services (PPS),lgovan@pps.net,"Smithsonian Conservation Center",true,50,"great partner"
Volunteers of America,cross@voaor.org,"Smithsonian Conservation Center",true,50,"great partner"
Central City Concern,lindsey.ramsey@ccconcern.org,"Smithsonian Conservation Center",true,50,"great partner"
4 changes: 0 additions & 4 deletions spec/fixtures/files/partners_with_duplicates.csv

This file was deleted.

3 changes: 3 additions & 0 deletions spec/fixtures/files/partners_with_final_line_blank.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 31,partner31@example.com,"Smithsonian Conservation Center",true,50,"great partner"

4 changes: 4 additions & 0 deletions spec/fixtures/files/partners_with_six_fields.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 1,partner1@example.com,"Smithsonian Conservation Center",true,50,"great partner"
Partner 2,partner2@example.com,"Smithsonian Conservation Center",true,75,"such a great partner"
Partner 4,partner4@example.com,"Smithsonian Conservation Center",false,80,"really ten out of ten"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 1,partner1@example.com,"Smithsonian Conservation Center",true,50,"great partner"
Partner 2,partner2@example.com,"Smithsonian Conservation Center",true,75,"such a great partner"
Partner 3,partner3@example.com,"Smithsonian Conservation Center",true,80,"yay, a partner"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 1,partner1@example.com,"SMITHSONIAN CONSERVATION CENTER",true,50,"great partner"
Partner 2,partner2@example.com,"Smithsonian Conservation Center",true,75,"such a great partner"
Partner 4,partner4@example.com,"Smithsonian Conservation Center",false,80,"really ten out of ten"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name,email,default_storage_location,send_reminders,quota,notes
Partner 4,partner4@example.com,"Invalid",false,80,"really ten out of ten"
13 changes: 9 additions & 4 deletions spec/models/donation_site_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
data = File.read(duplicated_name_csv_path, encoding: "BOM|UTF-8")
csv = CSV.parse(data, headers: true)

errors = DonationSite.import_csv(csv, organization.id)
response = DonationSite.import_csv(csv, organization.id)
errors = response[:errors]
expect(errors).not_to be_empty
expect(errors.first).to match(/Row/)
expect(errors.first).to include("Name must be unique within the organization")
Expand All @@ -55,7 +56,8 @@
data = File.read(valid_csv_path, encoding: "BOM|UTF-8")
csv = CSV.parse(data, headers: true)

errors = DonationSite.import_csv(csv, organization.id)
response = DonationSite.import_csv(csv, organization.id)
errors = response[:errors]
expect(errors).to be_empty
expect(DonationSite.count).to eq 1

Expand All @@ -67,7 +69,8 @@
data = File.read(invalid_csv_path, encoding: "BOM|UTF-8")
csv = CSV.parse(data, headers: true)

errors = DonationSite.import_csv(csv, organization.id)
response = DonationSite.import_csv(csv, organization.id)
errors = response[:errors]
expect(errors).not_to be_empty
expect(errors.first).to match(/Row/)
expect(errors.first).to include("can't be blank")
Expand All @@ -79,7 +82,9 @@
import_file_path = Rails.root.join("spec", "fixtures", "files", "donation_sites.csv")
data = File.read(import_file_path, encoding: "BOM|UTF-8")
csv = CSV.parse(data, headers: true)
DonationSite.import_csv(csv, organization.id)
response = DonationSite.import_csv(csv, organization.id)
errors = response[:errors]
expect(errors).to be_empty
expect(DonationSite.count).to eq 1
end
end
Expand Down
Loading
Loading