From a15b1dedc145b6722f71f234d183365416dd547c Mon Sep 17 00:00:00 2001
From: Nozomi R
+ <%= humanize_boolean(@organization.include_in_kind_values_in_exported_files) %> +
+%{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