From a15b1dedc145b6722f71f234d183365416dd547c Mon Sep 17 00:00:00 2001 From: Nozomi R Date: Mon, 30 Dec 2024 13:18:05 +0800 Subject: [PATCH 1/2] Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions --- app/controllers/donations_controller.rb | 2 +- app/controllers/organizations_controller.rb | 2 +- app/models/organization.rb | 63 ++++++------ .../export_distributions_csv_service.rb | 23 ++++- .../exports/export_donations_csv_service.rb | 25 ++++- app/views/organizations/_details.html.erb | 6 ++ app/views/organizations/edit.html.erb | 1 + ...lues_in_exported_files_to_organizations.rb | 6 ++ ...lues_in_exported_files_to_organizations.rb | 9 ++ db/schema.rb | 3 +- docs/user_guide/bank/exports.md | 12 +++ .../bank/getting_started_customization.md | 10 ++ spec/factories/items.rb | 1 + spec/factories/organizations.rb | 63 ++++++------ spec/models/organization_spec.rb | 63 ++++++------ .../export_distributions_csv_service_spec.rb | 94 +++++++++++++----- .../export_donations_csv_service_spec.rb | 97 ++++++++++++++----- 17 files changed, 330 insertions(+), 150 deletions(-) create mode 100644 db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb create mode 100644 db/migrate/20241230044251_backfill_add_include_in_kind_values_in_exported_files_to_organizations.rb diff --git a/app/controllers/donations_controller.rb b/app/controllers/donations_controller.rb index f858a34b38..3a2c2092e8 100644 --- a/app/controllers/donations_controller.rb +++ b/app/controllers/donations_controller.rb @@ -48,7 +48,7 @@ def index respond_to do |format| format.html format.csv do - send_data Exports::ExportDonationsCSVService.new(donation_ids: @donations.map(&:id)).generate_csv, filename: "Donations-#{Time.zone.today}.csv" + send_data Exports::ExportDonationsCSVService.new(donation_ids: @donations.map(&:id), organization: current_organization).generate_csv, filename: "Donations-#{Time.zone.today}.csv" end end end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index b10cbce8e1..d3a672617a 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -98,7 +98,7 @@ def organization_params :enable_individual_requests, :enable_quantity_based_requests, :ytd_on_distribution_printout, :one_step_partner_invite, :hide_value_columns_on_receipt, :hide_package_column_on_receipt, - :signature_for_distribution_pdf, + :signature_for_distribution_pdf, :include_in_kind_values_in_exported_files, partner_form_fields: [], request_unit_names: [] ) diff --git a/app/models/organization.rb b/app/models/organization.rb index b59a2a420d..bc3bb54d67 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -2,37 +2,38 @@ # # Table name: organizations # -# id :integer not null, primary key -# city :string -# deadline_day :integer -# default_storage_location :integer -# distribute_monthly :boolean default(FALSE), not null -# email :string -# enable_child_based_requests :boolean default(TRUE), not null -# enable_individual_requests :boolean default(TRUE), not null -# enable_quantity_based_requests :boolean default(TRUE), not null -# hide_package_column_on_receipt :boolean default(FALSE) -# hide_value_columns_on_receipt :boolean default(FALSE) -# intake_location :integer -# invitation_text :text -# latitude :float -# longitude :float -# name :string -# one_step_partner_invite :boolean default(FALSE), not null -# partner_form_fields :text default([]), is an Array -# reminder_day :integer -# repackage_essentials :boolean default(FALSE), not null -# short_name :string -# signature_for_distribution_pdf :boolean default(FALSE) -# state :string -# street :string -# url :string -# ytd_on_distribution_printout :boolean default(TRUE), not null -# zipcode :string -# created_at :datetime not null -# updated_at :datetime not null -# account_request_id :integer -# ndbn_member_id :bigint +# id :integer not null, primary key +# city :string +# deadline_day :integer +# default_storage_location :integer +# distribute_monthly :boolean default(FALSE), not null +# email :string +# enable_child_based_requests :boolean default(TRUE), not null +# enable_individual_requests :boolean default(TRUE), not null +# enable_quantity_based_requests :boolean default(TRUE), not null +# hide_package_column_on_receipt :boolean default(FALSE) +# hide_value_columns_on_receipt :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE) +# intake_location :integer +# invitation_text :text +# latitude :float +# longitude :float +# name :string +# one_step_partner_invite :boolean default(FALSE), not null +# partner_form_fields :text default([]), is an Array +# reminder_day :integer +# repackage_essentials :boolean default(FALSE), not null +# short_name :string +# signature_for_distribution_pdf :boolean default(FALSE) +# state :string +# street :string +# url :string +# ytd_on_distribution_printout :boolean default(TRUE), not null +# zipcode :string +# created_at :datetime not null +# updated_at :datetime not null +# account_request_id :integer +# ndbn_member_id :bigint # class Organization < ApplicationRecord diff --git a/app/services/exports/export_distributions_csv_service.rb b/app/services/exports/export_distributions_csv_service.rb index 70099d6c37..7638e40293 100644 --- a/app/services/exports/export_distributions_csv_service.rb +++ b/app/services/exports/export_distributions_csv_service.rb @@ -82,7 +82,7 @@ def base_table end }, "Total Value" => ->(distribution) { - distribution.cents_to_dollar(distribution.line_items.total_value) + Money.from_cents(distribution.line_items.total_value) }, "Delivery Method" => ->(distribution) { distribution.delivery_method @@ -119,22 +119,37 @@ def item_headers return @item_headers if @item_headers @item_headers = @organization.items.select("DISTINCT ON (LOWER(name)) items.name").order("LOWER(name) ASC").map(&:name) + @item_headers = @item_headers.flat_map { |h| [h, "#{h} In-Kind Value"] } if @organization.include_in_kind_values_in_exported_files + + @item_headers end def build_row_data(distribution) row = base_table.values.map { |closure| closure.call(distribution) } - row += Array.new(item_headers.size, 0) - + row += make_item_quantity_and_value_slots distribution.line_items.each do |line_item| item_name = line_item.item.name item_column_idx = headers_with_indexes[item_name] next unless item_column_idx row[item_column_idx] += line_item.quantity + row[item_column_idx + 1] += Money.new(line_item.value_per_line_item) if @organization.include_in_kind_values_in_exported_files end - row + convert_to_dollar(row) + end + + def make_item_quantity_and_value_slots + slots = Array.new(item_headers.size, 0) + slots = slots.map.with_index { |value, index| index.odd? ? Money.new(0) : value } if @organization.include_in_kind_values_in_exported_files + slots + end + + def convert_to_dollar(row) + row.map do |column| + column.is_a?(Money) ? column.to_f : column + end end end end diff --git a/app/services/exports/export_donations_csv_service.rb b/app/services/exports/export_donations_csv_service.rb index 6e600ea09b..4e66cc1149 100644 --- a/app/services/exports/export_donations_csv_service.rb +++ b/app/services/exports/export_donations_csv_service.rb @@ -1,6 +1,6 @@ module Exports class ExportDonationsCSVService - def initialize(donation_ids:) + def initialize(donation_ids:, organization:) # Use a where lookup so that I can eager load all the resources # needed rather than depending on external code to do it for me. # This makes this code more self contained and efficient! @@ -13,6 +13,7 @@ def initialize(donation_ids:) ).where( id: donation_ids, ).order(created_at: :asc) + @organization = organization end def generate_csv @@ -80,7 +81,7 @@ def base_table "Variety of Items" => ->(donation) { donation.line_items.map(&:name).uniq.size }, - "In-Kind Value" => ->(donation) { + "In-Kind Total" => ->(donation) { donation.in_kind_value_money }, "Comments" => ->(donation) { @@ -105,20 +106,36 @@ def item_headers end @item_headers = item_names.sort + @item_headers = @item_headers.flat_map { |h| [h, "#{h} In-Kind Value"] } if @organization.include_in_kind_values_in_exported_files + + @item_headers end def build_row_data(donation) row = base_table.values.map { |closure| closure.call(donation) } - row += Array.new(item_headers.size, 0) + row += make_item_quantity_and_value_slots donation.line_items.each do |line_item| item_name = line_item.item.name item_column_idx = headers_with_indexes[item_name] row[item_column_idx] += line_item.quantity + row[item_column_idx + 1] += Money.new(line_item.value_per_line_item) if @organization.include_in_kind_values_in_exported_files end - row + convert_to_dollar(row) + end + + def make_item_quantity_and_value_slots + slots = Array.new(item_headers.size, 0) + slots = slots.map.with_index { |value, index| index.odd? ? Money.new(0) : value } if @organization.include_in_kind_values_in_exported_files + slots + end + + def convert_to_dollar(row) + row.map do |column| + column.is_a?(Money) ? column.to_f : column + end end end end diff --git a/app/views/organizations/_details.html.erb b/app/views/organizations/_details.html.erb index 90dc479f6f..80a2989d5a 100644 --- a/app/views/organizations/_details.html.erb +++ b/app/views/organizations/_details.html.erb @@ -194,6 +194,12 @@ <%= humanize_boolean(@organization.hide_package_column_on_receipt) %>

+
+

Include in-kind value in donation and distribution exports:

+

+ <%= humanize_boolean(@organization.include_in_kind_values_in_exported_files) %> +

+
<% if @organization.logo.attached? %>

Logo

diff --git a/app/views/organizations/edit.html.erb b/app/views/organizations/edit.html.erb index b67acaec03..609aeaacc9 100644 --- a/app/views/organizations/edit.html.erb +++ b/app/views/organizations/edit.html.erb @@ -131,6 +131,7 @@ <%= f.input :one_step_partner_invite, label: 'Use One Step Invite and Approve partner process?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <%= f.input :hide_value_columns_on_receipt, label: 'Hide both value columns on distribution receipts?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <%= f.input :hide_package_column_on_receipt, label: 'Hide the package column on distribution receipts?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> + <%= f.input :include_in_kind_values_in_exported_files, label: 'Include in-kind value in donation and distribution exports?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %> <% default_email_text_hint = "You can use the variables %{partner_name}, %{delivery_method}, %{distribution_date}, and %{comment} to include the partner's name, delivery method, distribution date, and comments sent in the request." %> <%= f.input :default_email_text, label: "Distribution Email Content", hint: default_email_text_hint.html_safe do %> diff --git a/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb b/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb new file mode 100644 index 0000000000..4c4184afc3 --- /dev/null +++ b/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb @@ -0,0 +1,6 @@ +class AddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[7.2] + def change + add_column :organizations, :include_in_kind_values_in_exported_files, :boolean + change_column_default :organizations, :include_in_kind_values_in_exported_files, false + end +end diff --git a/db/migrate/20241230044251_backfill_add_include_in_kind_values_in_exported_files_to_organizations.rb b/db/migrate/20241230044251_backfill_add_include_in_kind_values_in_exported_files_to_organizations.rb new file mode 100644 index 0000000000..b39122d39a --- /dev/null +++ b/db/migrate/20241230044251_backfill_add_include_in_kind_values_in_exported_files_to_organizations.rb @@ -0,0 +1,9 @@ +class BackfillAddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[7.2] + disable_ddl_transaction! + def change + Organization.unscoped.in_batches do |relation| + relation.update_all include_in_kind_values_in_exported_files: false + sleep(0.01) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index afc02b83d7..7fe9578f35 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_20_020009) do +ActiveRecord::Schema[7.2].define(version: 2024_12_30_044251) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -491,6 +491,7 @@ t.boolean "hide_value_columns_on_receipt", default: false t.boolean "hide_package_column_on_receipt", default: false t.boolean "signature_for_distribution_pdf", default: false + t.boolean "include_in_kind_values_in_exported_files", default: false t.index ["latitude", "longitude"], name: "index_organizations_on_latitude_and_longitude" t.index ["short_name"], name: "index_organizations_on_short_name" end diff --git a/docs/user_guide/bank/exports.md b/docs/user_guide/bank/exports.md index 0b6c529d63..164cbbed8f 100644 --- a/docs/user_guide/bank/exports.md +++ b/docs/user_guide/bank/exports.md @@ -129,6 +129,12 @@ For each of the distributions in the filtered list: [!NOTE] This includes inactive Items as well as active ones. +### Add In-Kind Value for each item +The default export includes the number of items exported, but not the values. If you want to also have the export include the in-kind value for each item in the distributions, you can set that option. +Click "My Organization" in the left hand menu. Click "Edit" button. Set the "Include in-kind value in donation and distribution exports?" to "yes", then click "Save". + +[!NOTE] Setting this affects both the donation and distribution exports. + ## Donations ### Navigating to export Donations @@ -157,6 +163,12 @@ For each of the Donations in the filtered list: - Comments, - and the quantity of each of your organization's Items in the Donations. +### Add In-Kind Value for each item +The default export includes the number of items exported, but not the values. If you want to also have the export include the in-kind value for each item in the distributions, you can set that option. +Click "My Organization" in the left hand menu. Click "Edit" button. Set the "Include in-kind value in donation and distribution exports?" to "yes", then click "Save". + +[!NOTE] Setting this affects both the donation and distribution exports. + ## Donation Sites ### Navigating to export Donation Sites Click "Community", then "Donation Sites" in the left hand menu. Then click "Export Donation Sites" diff --git a/docs/user_guide/bank/getting_started_customization.md b/docs/user_guide/bank/getting_started_customization.md index 28fceedd3f..216dd25b30 100644 --- a/docs/user_guide/bank/getting_started_customization.md +++ b/docs/user_guide/bank/getting_started_customization.md @@ -182,6 +182,16 @@ Partners can't submit Requests until they are approved by the bank. The full partner approval process requires the partner to fill in their profile and submit it for approval. Some banks handle that for their partners, gather the information through other means (such as a phone conversation). Checking this will change the process so that the partners are automatically approved when they are invited. Note that any invited partners that are not yet approved will still need to be approved by the bank. +## Configuring exports + +### Include in-kind value in donation and distribution exports? + +You can configure whether the exports for donations and distributions include the "In-Kind Value" column for each item. By default, these values are excluded. To include them, set the "Include in-kind value in donation and distribution exports?" option to "Yes". + +Click "My Organization" in the left hand menu. Click "Edit" button. Set the "Include in-kind value in donation and distribution exports?" to "yes", then click "Save". + +[!NOTE] Setting this affects both the donation and distribution exports. + #### Distribution Email Content Note that there is a checkbox on the partner for them to receive Distribution emails. We recommend you do customize this content, as the default text is abrupt. You can customize this quite a bit! diff --git a/spec/factories/items.rb b/spec/factories/items.rb index 919c5e2a27..1be72de4ad 100644 --- a/spec/factories/items.rb +++ b/spec/factories/items.rb @@ -26,6 +26,7 @@ sequence(:name) { |n| "#{n}T Diapers" } organization { Organization.try(:first) || create(:organization) } partner_key { BaseItem.first&.partner_key || create(:base_item).partner_key } + value_in_cents { 0 } kit { nil } trait :active do diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb index f5fa0aad71..afdbeb2dd7 100644 --- a/spec/factories/organizations.rb +++ b/spec/factories/organizations.rb @@ -2,37 +2,38 @@ # # Table name: organizations # -# id :integer not null, primary key -# city :string -# deadline_day :integer -# default_storage_location :integer -# distribute_monthly :boolean default(FALSE), not null -# email :string -# enable_child_based_requests :boolean default(TRUE), not null -# enable_individual_requests :boolean default(TRUE), not null -# enable_quantity_based_requests :boolean default(TRUE), not null -# hide_package_column_on_receipt :boolean default(FALSE) -# hide_value_columns_on_receipt :boolean default(FALSE) -# intake_location :integer -# invitation_text :text -# latitude :float -# longitude :float -# name :string -# one_step_partner_invite :boolean default(FALSE), not null -# partner_form_fields :text default([]), is an Array -# reminder_day :integer -# repackage_essentials :boolean default(FALSE), not null -# short_name :string -# signature_for_distribution_pdf :boolean default(FALSE) -# state :string -# street :string -# url :string -# ytd_on_distribution_printout :boolean default(TRUE), not null -# zipcode :string -# created_at :datetime not null -# updated_at :datetime not null -# account_request_id :integer -# ndbn_member_id :bigint +# id :integer not null, primary key +# city :string +# deadline_day :integer +# default_storage_location :integer +# distribute_monthly :boolean default(FALSE), not null +# email :string +# enable_child_based_requests :boolean default(TRUE), not null +# enable_individual_requests :boolean default(TRUE), not null +# enable_quantity_based_requests :boolean default(TRUE), not null +# hide_package_column_on_receipt :boolean default(FALSE) +# hide_value_columns_on_receipt :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE) +# intake_location :integer +# invitation_text :text +# latitude :float +# longitude :float +# name :string +# one_step_partner_invite :boolean default(FALSE), not null +# partner_form_fields :text default([]), is an Array +# reminder_day :integer +# repackage_essentials :boolean default(FALSE), not null +# short_name :string +# signature_for_distribution_pdf :boolean default(FALSE) +# state :string +# street :string +# url :string +# ytd_on_distribution_printout :boolean default(TRUE), not null +# zipcode :string +# created_at :datetime not null +# updated_at :datetime not null +# account_request_id :integer +# ndbn_member_id :bigint # require 'seeds' diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index 4838d82120..5f9ab17326 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -2,37 +2,38 @@ # # Table name: organizations # -# id :integer not null, primary key -# city :string -# deadline_day :integer -# default_storage_location :integer -# distribute_monthly :boolean default(FALSE), not null -# email :string -# enable_child_based_requests :boolean default(TRUE), not null -# enable_individual_requests :boolean default(TRUE), not null -# enable_quantity_based_requests :boolean default(TRUE), not null -# hide_package_column_on_receipt :boolean default(FALSE) -# hide_value_columns_on_receipt :boolean default(FALSE) -# intake_location :integer -# invitation_text :text -# latitude :float -# longitude :float -# name :string -# one_step_partner_invite :boolean default(FALSE), not null -# partner_form_fields :text default([]), is an Array -# reminder_day :integer -# repackage_essentials :boolean default(FALSE), not null -# short_name :string -# signature_for_distribution_pdf :boolean default(FALSE) -# state :string -# street :string -# url :string -# ytd_on_distribution_printout :boolean default(TRUE), not null -# zipcode :string -# created_at :datetime not null -# updated_at :datetime not null -# account_request_id :integer -# ndbn_member_id :bigint +# id :integer not null, primary key +# city :string +# deadline_day :integer +# default_storage_location :integer +# distribute_monthly :boolean default(FALSE), not null +# email :string +# enable_child_based_requests :boolean default(TRUE), not null +# enable_individual_requests :boolean default(TRUE), not null +# enable_quantity_based_requests :boolean default(TRUE), not null +# hide_package_column_on_receipt :boolean default(FALSE) +# hide_value_columns_on_receipt :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE) +# intake_location :integer +# invitation_text :text +# latitude :float +# longitude :float +# name :string +# one_step_partner_invite :boolean default(FALSE), not null +# partner_form_fields :text default([]), is an Array +# reminder_day :integer +# repackage_essentials :boolean default(FALSE), not null +# short_name :string +# signature_for_distribution_pdf :boolean default(FALSE) +# state :string +# street :string +# url :string +# ytd_on_distribution_printout :boolean default(TRUE), not null +# zipcode :string +# created_at :datetime not null +# updated_at :datetime not null +# account_request_id :integer +# ndbn_member_id :bigint # RSpec.describe Organization, type: :model do diff --git a/spec/services/exports/export_distributions_csv_service_spec.rb b/spec/services/exports/export_distributions_csv_service_spec.rb index 35588b4312..a5658905c7 100644 --- a/spec/services/exports/export_distributions_csv_service_spec.rb +++ b/spec/services/exports/export_distributions_csv_service_spec.rb @@ -48,7 +48,6 @@ let(:total_item_quantities) do template = all_org_items.pluck(:name).index_with(0) - items_lists.map do |items_list| row = template.dup items_list.each do |(item, quantity)| @@ -76,27 +75,76 @@ let(:expected_headers) { non_item_headers + all_org_items.pluck(:name) } - it 'should match the expected content for the csv' do - expect(subject[0]).to eq(expected_headers) - - distributions.zip(total_item_quantities).each_with_index do |(distribution, total_item_quantity), idx| - row = [ - distribution.partner.name, - distribution.created_at.strftime("%m/%d/%Y"), - distribution.issued_at.strftime("%m/%d/%Y"), - distribution.storage_location.name, - distribution.line_items.where(item_id: item_id).total, - distribution.cents_to_dollar(distribution.line_items.total_value), - distribution.delivery_method, - "$#{distribution.shipping_cost.to_f}", - distribution.state, - distribution.agency_rep, - distribution.comment - ] - - row += total_item_quantity - - expect(subject[idx + 1]).to eq(row) + context 'while "Include in-kind value in donation and distribution exports?" is set to no' do + it 'should match the expected content without in-kind value of each item for the csv' do + expect(subject[0]).to eq(expected_headers) + + distributions.zip(total_item_quantities).each_with_index do |(distribution, total_item_quantity), idx| + row = [ + distribution.partner.name, + distribution.created_at.strftime("%m/%d/%Y"), + distribution.issued_at.strftime("%m/%d/%Y"), + distribution.storage_location.name, + distribution.line_items.where(item_id: item_id).total, + distribution.line_items.total_value.to_f, + distribution.delivery_method, + "$#{distribution.shipping_cost.to_f}", + distribution.state, + distribution.agency_rep, + distribution.comment + ] + + row += total_item_quantity + + expect(subject[idx + 1]).to eq(row) + end + end + end + + context 'while "Include in-kind value in donation and distribution exports?" is set to yes' do + let(:expected_headers) { non_item_headers + all_org_items.pluck(:name).flat_map { |h| [h, "#{h} In-Kind Value"] } } + + let(:quantities_and_values_of_items) do + template = all_org_items.pluck(:name).index_with({quantity: 0, value: Money.new(0), item_id: nil}) + + items_lists.map do |items_list| + row = template.deep_dup + items_list.each do |(item, quantity)| + row[item.name][:quantity] += quantity + row[item.name][:value] += Money.new(item.value_in_cents * quantity) + row[item.name][:item_id] = item.id + end + row.values + end + end + + it 'should match the expected content with in-kind value of each item for the csv' do + allow(organization).to receive(:include_in_kind_values_in_exported_files).and_return(true) + expect(subject[0]).to eq(expected_headers) + + distributions.zip(quantities_and_values_of_items).each_with_index do |(distribution, quantities_and_values_of_item), idx| + total_items_count = distribution.line_items.where(item_id: item_id).total + total_value = distribution.line_items.where(item_id: item_id).total_value.to_f + row = [ + distribution.partner.name, + distribution.created_at.strftime("%m/%d/%Y"), + distribution.issued_at.strftime("%m/%d/%Y"), + distribution.storage_location.name, + total_items_count, + total_value, + distribution.delivery_method, + "$#{distribution.shipping_cost.to_f}", + distribution.state, + distribution.agency_rep, + distribution.comment + ] + + row += quantities_and_values_of_item.flat_map { |item| [item[:quantity], item[:value].to_f] } + + expect(quantities_and_values_of_item.map { |item| (item[:item_id] == item_id) ? item[:quantity] : 0 }.sum).to eq(total_items_count) + expect(quantities_and_values_of_item.map { |item| (item[:item_id] == item_id) ? item[:value] : 0 }.sum.to_f).to eq(total_value) + expect(subject[idx + 1]).to eq(row) + end end end @@ -124,7 +172,7 @@ distribution.issued_at.strftime("%m/%d/%Y"), distribution.storage_location.name, distribution.line_items.where(item_id: item_id).total, - distribution.cents_to_dollar(distribution.line_items.total_value), + distribution.line_items.total_value.to_f, distribution.delivery_method, "$#{distribution.shipping_cost.to_f}", distribution.state, diff --git a/spec/services/exports/export_donations_csv_service_spec.rb b/spec/services/exports/export_donations_csv_service_spec.rb index 7990929f63..9a5274b7bd 100644 --- a/spec/services/exports/export_donations_csv_service_spec.rb +++ b/spec/services/exports/export_donations_csv_service_spec.rb @@ -1,8 +1,9 @@ RSpec.describe Exports::ExportDonationsCSVService do + let(:organization) { create(:organization) } describe '#generate_csv_data' do - subject { described_class.new(donation_ids: donation_ids).generate_csv_data } + subject { described_class.new(donation_ids: donation_ids, organization: organization).generate_csv_data } let(:donation_ids) { donations.map(&:id) } - let(:duplicate_item) { FactoryBot.create(:item) } + let(:duplicate_item) { FactoryBot.create(:item, value_in_cents: 10) } let(:items_lists) do [ [ @@ -12,7 +13,7 @@ ], *(Array.new(3) do |i| [[FactoryBot.create( - :item, name: "item_#{i}" + :item, name: "item_#{i}", value_in_cents: i + 1 ), i + 1]] end) ] @@ -51,12 +52,12 @@ "Storage Location", "Quantity of Items", "Variety of Items", - "In-Kind Value", + "In-Kind Total", "Comments" ] + expected_item_headers end - let(:total_item_quantities) do + let(:quantities_of_items) do template = item_names.index_with(0) items_lists.map do |items_list| @@ -68,30 +69,80 @@ end end - let(:expected_item_headers) do - expect(item_names).not_to be_empty + context 'while "Include in-kind value in donation and distribution exports?" is set to no' do + let(:expected_item_headers) do + expect(item_names).not_to be_empty - item_names + item_names + end + it 'should match the expected content without in-kind value of each item for the csv' do + expect(subject[0]).to eq(expected_headers) + + donations.zip(quantities_of_items).each_with_index do |(donation, item_quantities), idx| + total_item_quantity = donation.line_items.total + row = [ + donation.source, + donation.issued_at.strftime("%F"), + donation.details, + donation.storage_view, + total_item_quantity, + item_quantities.count(&:positive?), + donation.in_kind_value_money.to_f, + donation.comment + ] + + row += item_quantities + + expect(item_quantities.sum).to eq(total_item_quantity) + expect(subject[idx + 1]).to eq(row) + end + end end - it 'should match the expected content for the csv' do - expect(subject[0]).to eq(expected_headers) + context 'while "Include in-kind value in donation and distribution exports?" is set to yes' do + let(:expected_item_headers) do + expect(item_names).not_to be_empty + + item_names.flat_map { |name| [name, "#{name} In-Kind Value"] } + end + + let(:quantities_and_values_of_items) do + template = item_names.index_with({quantity: 0, value: Money.new(0)}) - donations.zip(total_item_quantities).each_with_index do |(donation, total_item_quantity), idx| - row = [ - donation.source, - donation.issued_at.strftime("%F"), - donation.details, - donation.storage_view, - donation.line_items.total, - total_item_quantity.count(&:positive?), - donation.in_kind_value_money, - donation.comment - ] + items_lists.map do |items_list| + row = template.deep_dup + items_list.each do |(item, quantity)| + row[item.name][:quantity] += quantity + row[item.name][:value] += Money.new(item.value_in_cents * quantity) + end + row.values + end + end - row += total_item_quantity + it 'should match the expected content with in-kind value of each item for the csv' do + allow(organization).to receive(:include_in_kind_values_in_exported_files).and_return(true) + expect(subject[0]).to eq(expected_headers) - expect(subject[idx + 1]).to eq(row) + donations.zip(quantities_and_values_of_items).each_with_index do |(donation, quantities_and_values_of_item), idx| + total_item_count = donation.line_items.total + total_in_kind_value = donation.in_kind_value_money.to_f + row = [ + donation.source, + donation.issued_at.strftime("%F"), + donation.details, + donation.storage_view, + total_item_count, + quantities_and_values_of_item.map { |item| item[:quantity] }.count(&:positive?), + total_in_kind_value, + donation.comment + ] + + row += quantities_and_values_of_item.flat_map { |item| [item[:quantity], item[:value].to_f] } + + expect(quantities_and_values_of_item.map { |item| item[:quantity] }.sum).to eq(total_item_count) + expect(quantities_and_values_of_item.map { |item| item[:value] }.sum.to_f).to eq(total_in_kind_value) + expect(subject[idx + 1]).to eq(row) + end end end end From 556a41dba50714c7d049a970ddfaa6e1ede39504 Mon Sep 17 00:00:00 2001 From: Nozomi R Date: Fri, 17 Jan 2025 22:35:47 +0800 Subject: [PATCH 2/2] Make include_in_kind_values_in_exported_files not null. --- app/models/organization.rb | 2 +- ...include_in_kind_values_in_exported_files_to_organizations.rb | 2 +- db/schema.rb | 2 +- spec/factories/organizations.rb | 2 +- spec/models/organization_spec.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/organization.rb b/app/models/organization.rb index bc3bb54d67..586d7940f5 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -13,7 +13,7 @@ # enable_quantity_based_requests :boolean default(TRUE), not null # hide_package_column_on_receipt :boolean default(FALSE) # hide_value_columns_on_receipt :boolean default(FALSE) -# include_in_kind_values_in_exported_files :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE), not null # intake_location :integer # invitation_text :text # latitude :float diff --git a/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb b/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb index 4c4184afc3..b8b2e14251 100644 --- a/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb +++ b/db/migrate/20241230044238_add_include_in_kind_values_in_exported_files_to_organizations.rb @@ -1,6 +1,6 @@ class AddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[7.2] def change - add_column :organizations, :include_in_kind_values_in_exported_files, :boolean + add_column :organizations, :include_in_kind_values_in_exported_files, :boolean, null: false change_column_default :organizations, :include_in_kind_values_in_exported_files, false end end diff --git a/db/schema.rb b/db/schema.rb index 7fe9578f35..9000851220 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -491,7 +491,7 @@ t.boolean "hide_value_columns_on_receipt", default: false t.boolean "hide_package_column_on_receipt", default: false t.boolean "signature_for_distribution_pdf", default: false - t.boolean "include_in_kind_values_in_exported_files", default: false + t.boolean "include_in_kind_values_in_exported_files", default: false, null: false t.index ["latitude", "longitude"], name: "index_organizations_on_latitude_and_longitude" t.index ["short_name"], name: "index_organizations_on_short_name" end diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb index afdbeb2dd7..2ad9210b94 100644 --- a/spec/factories/organizations.rb +++ b/spec/factories/organizations.rb @@ -13,7 +13,7 @@ # enable_quantity_based_requests :boolean default(TRUE), not null # hide_package_column_on_receipt :boolean default(FALSE) # hide_value_columns_on_receipt :boolean default(FALSE) -# include_in_kind_values_in_exported_files :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE), not null # intake_location :integer # invitation_text :text # latitude :float diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index 5f9ab17326..5950dd5681 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -13,7 +13,7 @@ # enable_quantity_based_requests :boolean default(TRUE), not null # hide_package_column_on_receipt :boolean default(FALSE) # hide_value_columns_on_receipt :boolean default(FALSE) -# include_in_kind_values_in_exported_files :boolean default(FALSE) +# include_in_kind_values_in_exported_files :boolean default(FALSE), not null # intake_location :integer # invitation_text :text # latitude :float