Skip to content

Commit d8051fe

Browse files
committed
Add an option to control whether "in-kind value" of each item should be added in export of donations and distributions
1 parent 220af87 commit d8051fe

17 files changed

+330
-150
lines changed

app/controllers/donations_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def index
4848
respond_to do |format|
4949
format.html
5050
format.csv do
51-
send_data Exports::ExportDonationsCSVService.new(donation_ids: @donations.map(&:id)).generate_csv, filename: "Donations-#{Time.zone.today}.csv"
51+
send_data Exports::ExportDonationsCSVService.new(donation_ids: @donations.map(&:id), organization: current_organization).generate_csv, filename: "Donations-#{Time.zone.today}.csv"
5252
end
5353
end
5454
end

app/controllers/organizations_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def organization_params
9898
:enable_individual_requests, :enable_quantity_based_requests,
9999
:ytd_on_distribution_printout, :one_step_partner_invite,
100100
:hide_value_columns_on_receipt, :hide_package_column_on_receipt,
101-
:signature_for_distribution_pdf,
101+
:signature_for_distribution_pdf, :include_in_kind_values_in_exported_files,
102102
partner_form_fields: [],
103103
request_unit_names: []
104104
)

app/models/organization.rb

+32-31
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,38 @@
22
#
33
# Table name: organizations
44
#
5-
# id :integer not null, primary key
6-
# city :string
7-
# deadline_day :integer
8-
# default_storage_location :integer
9-
# distribute_monthly :boolean default(FALSE), not null
10-
# email :string
11-
# enable_child_based_requests :boolean default(TRUE), not null
12-
# enable_individual_requests :boolean default(TRUE), not null
13-
# enable_quantity_based_requests :boolean default(TRUE), not null
14-
# hide_package_column_on_receipt :boolean default(FALSE)
15-
# hide_value_columns_on_receipt :boolean default(FALSE)
16-
# intake_location :integer
17-
# invitation_text :text
18-
# latitude :float
19-
# longitude :float
20-
# name :string
21-
# one_step_partner_invite :boolean default(FALSE), not null
22-
# partner_form_fields :text default([]), is an Array
23-
# reminder_day :integer
24-
# repackage_essentials :boolean default(FALSE), not null
25-
# short_name :string
26-
# signature_for_distribution_pdf :boolean default(FALSE)
27-
# state :string
28-
# street :string
29-
# url :string
30-
# ytd_on_distribution_printout :boolean default(TRUE), not null
31-
# zipcode :string
32-
# created_at :datetime not null
33-
# updated_at :datetime not null
34-
# account_request_id :integer
35-
# ndbn_member_id :bigint
5+
# id :integer not null, primary key
6+
# city :string
7+
# deadline_day :integer
8+
# default_storage_location :integer
9+
# distribute_monthly :boolean default(FALSE), not null
10+
# email :string
11+
# enable_child_based_requests :boolean default(TRUE), not null
12+
# enable_individual_requests :boolean default(TRUE), not null
13+
# enable_quantity_based_requests :boolean default(TRUE), not null
14+
# hide_package_column_on_receipt :boolean default(FALSE)
15+
# hide_value_columns_on_receipt :boolean default(FALSE)
16+
# include_in_kind_values_in_exported_files :boolean default(FALSE)
17+
# intake_location :integer
18+
# invitation_text :text
19+
# latitude :float
20+
# longitude :float
21+
# name :string
22+
# one_step_partner_invite :boolean default(FALSE), not null
23+
# partner_form_fields :text default([]), is an Array
24+
# reminder_day :integer
25+
# repackage_essentials :boolean default(FALSE), not null
26+
# short_name :string
27+
# signature_for_distribution_pdf :boolean default(FALSE)
28+
# state :string
29+
# street :string
30+
# url :string
31+
# ytd_on_distribution_printout :boolean default(TRUE), not null
32+
# zipcode :string
33+
# created_at :datetime not null
34+
# updated_at :datetime not null
35+
# account_request_id :integer
36+
# ndbn_member_id :bigint
3637
#
3738

3839
class Organization < ApplicationRecord

app/services/exports/export_distributions_csv_service.rb

+19-4
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def base_table
8282
end
8383
},
8484
"Total Value" => ->(distribution) {
85-
distribution.cents_to_dollar(distribution.line_items.total_value)
85+
Money.from_cents(distribution.line_items.total_value)
8686
},
8787
"Delivery Method" => ->(distribution) {
8888
distribution.delivery_method
@@ -119,22 +119,37 @@ def item_headers
119119
return @item_headers if @item_headers
120120

121121
@item_headers = @organization.items.select("DISTINCT ON (LOWER(name)) items.name").order("LOWER(name) ASC").map(&:name)
122+
@item_headers = @item_headers.flat_map { |h| [h, "#{h} In-Kind Value"] } if @organization.include_in_kind_values_in_exported_files
123+
124+
@item_headers
122125
end
123126

124127
def build_row_data(distribution)
125128
row = base_table.values.map { |closure| closure.call(distribution) }
126129

127-
row += Array.new(item_headers.size, 0)
128-
130+
row += make_item_quantity_and_value_slots
129131
distribution.line_items.each do |line_item|
130132
item_name = line_item.item.name
131133
item_column_idx = headers_with_indexes[item_name]
132134
next unless item_column_idx
133135

134136
row[item_column_idx] += line_item.quantity
137+
row[item_column_idx + 1] += Money.new(line_item.value_per_line_item) if @organization.include_in_kind_values_in_exported_files
135138
end
136139

137-
row
140+
convert_to_dollar(row)
141+
end
142+
143+
def make_item_quantity_and_value_slots
144+
slots = Array.new(item_headers.size, 0)
145+
slots = slots.map.with_index { |value, index| index.odd? ? Money.new(0) : value } if @organization.include_in_kind_values_in_exported_files
146+
slots
147+
end
148+
149+
def convert_to_dollar(row)
150+
row.map do |column|
151+
column.is_a?(Money) ? column.to_f : column
152+
end
138153
end
139154
end
140155
end

app/services/exports/export_donations_csv_service.rb

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Exports
22
class ExportDonationsCSVService
3-
def initialize(donation_ids:)
3+
def initialize(donation_ids:, organization:)
44
# Use a where lookup so that I can eager load all the resources
55
# needed rather than depending on external code to do it for me.
66
# This makes this code more self contained and efficient!
@@ -13,6 +13,7 @@ def initialize(donation_ids:)
1313
).where(
1414
id: donation_ids,
1515
).order(created_at: :asc)
16+
@organization = organization
1617
end
1718

1819
def generate_csv
@@ -80,7 +81,7 @@ def base_table
8081
"Variety of Items" => ->(donation) {
8182
donation.line_items.map(&:name).uniq.size
8283
},
83-
"In-Kind Value" => ->(donation) {
84+
"In-Kind Total" => ->(donation) {
8485
donation.in_kind_value_money
8586
},
8687
"Comments" => ->(donation) {
@@ -105,20 +106,36 @@ def item_headers
105106
end
106107

107108
@item_headers = item_names.sort
109+
@item_headers = @item_headers.flat_map { |h| [h, "#{h} In-Kind Value"] } if @organization.include_in_kind_values_in_exported_files
110+
111+
@item_headers
108112
end
109113

110114
def build_row_data(donation)
111115
row = base_table.values.map { |closure| closure.call(donation) }
112116

113-
row += Array.new(item_headers.size, 0)
117+
row += make_item_quantity_and_value_slots
114118

115119
donation.line_items.each do |line_item|
116120
item_name = line_item.item.name
117121
item_column_idx = headers_with_indexes[item_name]
118122
row[item_column_idx] += line_item.quantity
123+
row[item_column_idx + 1] += Money.new(line_item.value_per_line_item) if @organization.include_in_kind_values_in_exported_files
119124
end
120125

121-
row
126+
convert_to_dollar(row)
127+
end
128+
129+
def make_item_quantity_and_value_slots
130+
slots = Array.new(item_headers.size, 0)
131+
slots = slots.map.with_index { |value, index| index.odd? ? Money.new(0) : value } if @organization.include_in_kind_values_in_exported_files
132+
slots
133+
end
134+
135+
def convert_to_dollar(row)
136+
row.map do |column|
137+
column.is_a?(Money) ? column.to_f : column
138+
end
122139
end
123140
end
124141
end

app/views/organizations/_details.html.erb

+6
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@
194194
<%= humanize_boolean(@organization.hide_package_column_on_receipt) %>
195195
</p>
196196
</div>
197+
<div class="mb-4">
198+
<h3 class='font-bold'>Include in-kind value in donation and distribution exports:</h3>
199+
<p>
200+
<%= humanize_boolean(@organization.include_in_kind_values_in_exported_files) %>
201+
</p>
202+
</div>
197203
<% if @organization.logo.attached? %>
198204
<div class="mb-4">
199205
<h3 class='font-bold'>Logo</h3>

app/views/organizations/edit.html.erb

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
<%= 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 %>
132132
<%= 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 %>
133133
<%= 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 %>
134+
<%= 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 %>
134135

135136
<% default_email_text_hint = "You can use the variables <code>%{partner_name}</code>, <code>%{delivery_method}</code>, <code>%{distribution_date}</code>, and <code>%{comment}</code> to include the partner's name, delivery method, distribution date, and comments sent in the request." %>
136137
<%= f.input :default_email_text, label: "Distribution Email Content", hint: default_email_text_hint.html_safe do %>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class AddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[7.2]
2+
def change
3+
add_column :organizations, :include_in_kind_values_in_exported_files, :boolean
4+
change_column_default :organizations, :include_in_kind_values_in_exported_files, false
5+
end
6+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class BackfillAddIncludeInKindValuesInExportedFilesToOrganizations < ActiveRecord::Migration[7.2]
2+
disable_ddl_transaction!
3+
def change
4+
Organization.unscoped.in_batches do |relation|
5+
relation.update_all include_in_kind_values_in_exported_files: false
6+
sleep(0.01)
7+
end
8+
end
9+
end

db/schema.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema[7.2].define(version: 2024_12_20_020009) do
13+
ActiveRecord::Schema[7.2].define(version: 2024_12_30_044251) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "plpgsql"
1616

@@ -491,6 +491,7 @@
491491
t.boolean "hide_value_columns_on_receipt", default: false
492492
t.boolean "hide_package_column_on_receipt", default: false
493493
t.boolean "signature_for_distribution_pdf", default: false
494+
t.boolean "include_in_kind_values_in_exported_files", default: false
494495
t.index ["latitude", "longitude"], name: "index_organizations_on_latitude_and_longitude"
495496
t.index ["short_name"], name: "index_organizations_on_short_name"
496497
end

docs/user_guide/bank/exports.md

+12
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ For each of the distributions in the filtered list:
129129

130130
[!NOTE] This includes inactive Items as well as active ones.
131131

132+
### Add In-Kind Value for each item
133+
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.
134+
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".
135+
136+
[!NOTE] Setting this affects both the donation and distribution exports.
137+
132138
## Donations
133139

134140
### Navigating to export Donations
@@ -157,6 +163,12 @@ For each of the Donations in the filtered list:
157163
- Comments,
158164
- and the quantity of each of your organization's Items in the Donations.
159165

166+
### Add In-Kind Value for each item
167+
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.
168+
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".
169+
170+
[!NOTE] Setting this affects both the donation and distribution exports.
171+
160172
## Donation Sites
161173
### Navigating to export Donation Sites
162174
Click "Community", then "Donation Sites" in the left hand menu. Then click "Export Donation Sites"

docs/user_guide/bank/getting_started_customization.md

+10
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ Partners can't submit Requests until they are approved by the bank.
182182
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).
183183
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.
184184

185+
## Configuring exports
186+
187+
### Add In-Kind Value for each item
188+
189+
You can configure whether the export file includes 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".
190+
191+
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".
192+
193+
[!NOTE] Setting this affects both the donation and distribution exports.
194+
185195
#### Distribution Email Content
186196
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.
187197
You can customize this quite a bit!

spec/factories/items.rb

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
sequence(:name) { |n| "#{n}T Diapers" }
2727
organization { Organization.try(:first) || create(:organization) }
2828
partner_key { BaseItem.first&.partner_key || create(:base_item).partner_key }
29+
value_in_cents { 0 }
2930
kit { nil }
3031

3132
trait :active do

spec/factories/organizations.rb

+32-31
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,38 @@
22
#
33
# Table name: organizations
44
#
5-
# id :integer not null, primary key
6-
# city :string
7-
# deadline_day :integer
8-
# default_storage_location :integer
9-
# distribute_monthly :boolean default(FALSE), not null
10-
# email :string
11-
# enable_child_based_requests :boolean default(TRUE), not null
12-
# enable_individual_requests :boolean default(TRUE), not null
13-
# enable_quantity_based_requests :boolean default(TRUE), not null
14-
# hide_package_column_on_receipt :boolean default(FALSE)
15-
# hide_value_columns_on_receipt :boolean default(FALSE)
16-
# intake_location :integer
17-
# invitation_text :text
18-
# latitude :float
19-
# longitude :float
20-
# name :string
21-
# one_step_partner_invite :boolean default(FALSE), not null
22-
# partner_form_fields :text default([]), is an Array
23-
# reminder_day :integer
24-
# repackage_essentials :boolean default(FALSE), not null
25-
# short_name :string
26-
# signature_for_distribution_pdf :boolean default(FALSE)
27-
# state :string
28-
# street :string
29-
# url :string
30-
# ytd_on_distribution_printout :boolean default(TRUE), not null
31-
# zipcode :string
32-
# created_at :datetime not null
33-
# updated_at :datetime not null
34-
# account_request_id :integer
35-
# ndbn_member_id :bigint
5+
# id :integer not null, primary key
6+
# city :string
7+
# deadline_day :integer
8+
# default_storage_location :integer
9+
# distribute_monthly :boolean default(FALSE), not null
10+
# email :string
11+
# enable_child_based_requests :boolean default(TRUE), not null
12+
# enable_individual_requests :boolean default(TRUE), not null
13+
# enable_quantity_based_requests :boolean default(TRUE), not null
14+
# hide_package_column_on_receipt :boolean default(FALSE)
15+
# hide_value_columns_on_receipt :boolean default(FALSE)
16+
# include_in_kind_values_in_exported_files :boolean default(FALSE)
17+
# intake_location :integer
18+
# invitation_text :text
19+
# latitude :float
20+
# longitude :float
21+
# name :string
22+
# one_step_partner_invite :boolean default(FALSE), not null
23+
# partner_form_fields :text default([]), is an Array
24+
# reminder_day :integer
25+
# repackage_essentials :boolean default(FALSE), not null
26+
# short_name :string
27+
# signature_for_distribution_pdf :boolean default(FALSE)
28+
# state :string
29+
# street :string
30+
# url :string
31+
# ytd_on_distribution_printout :boolean default(TRUE), not null
32+
# zipcode :string
33+
# created_at :datetime not null
34+
# updated_at :datetime not null
35+
# account_request_id :integer
36+
# ndbn_member_id :bigint
3637
#
3738
require 'seeds'
3839

0 commit comments

Comments
 (0)