Skip to content

Commit 4986d86

Browse files
authored
Merge pull request #4582 from jadekstewart3/4238_period_supplies
4283 period supplies
2 parents c1fec92 + 5417ec2 commit 4986d86

File tree

2 files changed

+137
-43
lines changed

2 files changed

+137
-43
lines changed

app/services/reports/period_supply_report_service.rb

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ def initialize(year:, organization:)
1414
def report
1515
@report ||= {name: "Period Supplies",
1616
entries: {
17-
"Period supplies distributed" => number_with_delimiter(distributed_supplies),
18-
"Period supplies per adult per month" => monthly_supplies&.round || 0,
17+
"Period supplies distributed" => number_with_delimiter(total_distributed_period_supplies),
1918
"Period supplies" => types_of_supplies,
2019
"% period supplies donated" => "#{percent_donated.round}%",
2120
"% period supplies bought" => "#{percent_bought.round}%",
@@ -24,7 +23,7 @@ def report
2423
end
2524

2625
# @return [Integer]
27-
def distributed_supplies
26+
def distributed_loose_period_supplies
2827
@distributed_supplies ||= organization
2928
.distributions
3029
.for_year(year)
@@ -33,17 +32,12 @@ def distributed_supplies
3332
.sum("line_items.quantity")
3433
end
3534

36-
# @return [Integer]
37-
def monthly_supplies
38-
# NOTE: This is asking "per adult per month" but there doesn't seem to be much difference
39-
# in calculating per month or per any other time frame, since all it's really asking
40-
# is the value of the `distribution_quantity` field for the items we're giving out.
41-
organization
42-
.distributions
43-
.for_year(year)
44-
.joins(line_items: :item)
45-
.merge(Item.period_supplies)
46-
.average("COALESCE(items.distribution_quantity, 50)")
35+
def distributed_period_supplies_from_kits
36+
kit_items_calculation("distributions", "Distribution")
37+
end
38+
39+
def total_distributed_period_supplies
40+
distributed_loose_period_supplies + distributed_period_supplies_from_kits
4741
end
4842

4943
def types_of_supplies
@@ -54,14 +48,14 @@ def types_of_supplies
5448
def percent_donated
5549
return 0.0 if total_supplies.zero?
5650

57-
(donated_supplies / total_supplies.to_f) * 100
51+
(total_donated_supplies / total_supplies.to_f) * 100
5852
end
5953

6054
# @return [Float]
6155
def percent_bought
6256
return 0.0 if total_supplies.zero?
6357

64-
(purchased_supplies / total_supplies.to_f) * 100
58+
(total_purchased_supplies / total_supplies.to_f) * 100
6559
end
6660

6761
# @return [String]
@@ -72,24 +66,67 @@ def money_spent_on_supplies
7266
###### HELPER METHODS ######
7367

7468
# @return [Integer]
75-
def purchased_supplies
69+
def total_purchased_supplies
7670
@purchased_supplies ||= LineItem.joins(:item)
7771
.merge(Item.period_supplies)
7872
.where(itemizable: organization.purchases.for_year(year))
7973
.sum(:quantity)
74+
75+
@purchased_supplies + purchased_supplies_from_kits
76+
end
77+
78+
def purchased_supplies_from_kits
79+
kit_items_calculation("purchases", "Purchase")
8080
end
8181

8282
# @return [Integer]
8383
def total_supplies
84-
@total_supplies ||= purchased_supplies + donated_supplies
84+
@total_supplies ||= total_purchased_supplies + total_donated_supplies
8585
end
8686

8787
# @return [Integer]
88-
def donated_supplies
89-
@donated_supplies ||= LineItem.joins(:item)
88+
def total_donated_supplies
89+
loose_donated_supplies = LineItem.joins(:item)
9090
.merge(Item.period_supplies)
9191
.where(itemizable: organization.donations.for_year(year))
9292
.sum(:quantity)
93+
94+
loose_donated_supplies + donated_supplies_from_kits
95+
end
96+
97+
def donated_supplies_from_kits
98+
kit_items_calculation("donations", "Donation")
99+
end
100+
101+
private
102+
103+
def kit_items_calculation(itemizable_type, string_itemizable_type)
104+
organization_id = @organization.id
105+
year = @year
106+
107+
# Sanitize and validate inputs
108+
itemizable_type = ActiveRecord::Base.connection.quote_table_name(itemizable_type)
109+
string_itemizable_type = ActiveRecord::Base.connection.quote(string_itemizable_type)
110+
111+
sql_query = <<-SQL
112+
SELECT SUM(line_items.quantity * kit_line_items.quantity)
113+
FROM #{itemizable_type}
114+
INNER JOIN line_items ON line_items.itemizable_type = #{string_itemizable_type} AND line_items.itemizable_id = #{itemizable_type}.id
115+
INNER JOIN items ON items.id = line_items.item_id
116+
INNER JOIN kits ON kits.id = items.kit_id
117+
INNER JOIN line_items AS kit_line_items ON kits.id = kit_line_items.itemizable_id
118+
INNER JOIN items AS kit_items ON kit_items.id = kit_line_items.item_id
119+
INNER JOIN base_items ON base_items.partner_key = kit_items.partner_key
120+
WHERE #{itemizable_type}.organization_id = ?
121+
AND EXTRACT(year FROM issued_at) = ?
122+
AND LOWER(base_items.category) LIKE '%menstrual supplies%'
123+
AND NOT (LOWER(base_items.category) LIKE '%diaper%' OR LOWER(base_items.name) LIKE '%cloth%')
124+
AND kit_line_items.itemizable_type = 'Kit';
125+
SQL
126+
127+
sanitized_sql = ActiveRecord::Base.send(:sanitize_sql_array, [sql_query, organization_id, year])
128+
result = ActiveRecord::Base.connection.execute(sanitized_sql)
129+
result.first["sum"].to_i
93130
end
94131
end
95132
end

spec/services/reports/period_supply_report_service_spec.rb

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@
77
end
88

99
describe "#report" do
10-
it "should report zero values" do
11-
expect(report.report[:entries]).to match(hash_including({
12-
"% period supplies bought" => "0%",
13-
"% period supplies donated" => "0%",
14-
"Period supplies distributed" => "0",
15-
"Period supplies per adult per month" => 0,
16-
"Money spent purchasing period supplies" => "$0.00"
17-
}))
18-
expect(report.report[:entries]["Period supplies"].split(", "))
19-
.to contain_exactly("Tampons", "Pads", "Liners (Menstrual)")
10+
context "with no values" do
11+
it "should report zero values" do
12+
expect(report.report[:entries]).to match(hash_including({
13+
"% period supplies bought" => "0%",
14+
"% period supplies donated" => "0%",
15+
"Period supplies distributed" => "0",
16+
"Money spent purchasing period supplies" => "$0.00"
17+
}))
18+
expect(report.report[:entries]["Period supplies"].split(", "))
19+
.to contain_exactly("Tampons", "Pads", "Liners (Menstrual)")
20+
end
2021
end
2122

22-
describe "with values" do
23+
context "with values" do
2324
before(:each) do
2425
Organization.seed_items(organization)
2526

@@ -32,6 +33,49 @@
3233
# We will create data both within and outside our date range, and both period_supplies and non period_supplies.
3334
# Spec will ensure that only the required data is included.
3435

36+
# Kits
37+
period_supplies_kit = create(:kit, :with_item, organization: organization)
38+
another_period_supply_kit = create(:kit, :with_item, organization: organization)
39+
donated_period_supply_kit = create(:kit, :with_item, organization: organization)
40+
purchased_period_supply_kit = create(:kit, :with_item, organization: organization)
41+
pad_and_tampon_kit = create(:kit, :with_item, organization: organization)
42+
43+
create(:base_item, name: "Adult Pads", partner_key: "adult pads", category: "Menstrual Supplies")
44+
create(:base_item, name: "Adult Tampons", partner_key: "adult tampons", category: "Menstrual Supplies")
45+
46+
period_supplies_kit_item = create(:item, name: "Adult Pads", partner_key: "adult pads")
47+
another_period_supplies_kit_item = create(:item, name: "Adult Tampons", partner_key: "adult tampons")
48+
purchased_period_supplies_kit_item = create(:item, name: "Liners", partner_key: "adult tampons")
49+
50+
period_supplies_kit.line_items.first.update!(item_id: period_supplies_kit_item.id, quantity: 5)
51+
another_period_supply_kit.line_items.first.update!(item_id: another_period_supplies_kit_item.id, quantity: 5)
52+
donated_period_supply_kit.line_items.first.update!(item_id: another_period_supplies_kit_item.id, quantity: 5)
53+
purchased_period_supply_kit.line_items.first.update!(item_id: purchased_period_supplies_kit_item.id, quantity: 5)
54+
55+
pad_and_tampon_kit.line_items.first.update!(item_id: period_supplies_kit_item.id, quantity: 10)
56+
pad_and_tampon_kit.line_items.first.update!(item_id: another_period_supplies_kit_item.id, quantity: 10)
57+
58+
period_supplies_kit_distribution = create(:distribution, organization: organization, issued_at: within_time)
59+
another_period_supplies_kit_distribution = create(:distribution, organization: organization, issued_at: within_time)
60+
pad_and_tampon_kit_distribution = create(:distribution, organization: organization, issued_at: within_time)
61+
62+
kit_donation = create(:donation, product_drive: nil, issued_at: within_time, money_raised: 1000, organization: organization)
63+
64+
kit_purchase = create(:purchase, issued_at: within_time, organization: organization, purchased_from: "TikTok Shop", amount_spent_in_cents: 1000, amount_spent_on_period_supplies_cents: 1000, line_items: [
65+
create(:line_item, :purchase, item: period_supplies_kit_item, quantity: 5),
66+
create(:line_item, :purchase, item: purchased_period_supplies_kit_item, quantity: 5)
67+
])
68+
69+
create(:line_item, :distribution, quantity: 10, item: period_supplies_kit.item, itemizable: period_supplies_kit_distribution)
70+
create(:line_item, :distribution, quantity: 10, item: another_period_supply_kit.item, itemizable: another_period_supplies_kit_distribution)
71+
72+
create(:line_item, :distribution, quantity: 10, item: pad_and_tampon_kit.item, itemizable: pad_and_tampon_kit_distribution)
73+
create(:line_item, :distribution, quantity: 10, item: pad_and_tampon_kit.item, itemizable: pad_and_tampon_kit_distribution)
74+
75+
create(:line_item, :donation, quantity: 10, item: donated_period_supply_kit.item, itemizable: kit_donation)
76+
77+
create(:line_item, :purchase, quantity: 30, item: purchased_period_supply_kit.item, itemizable: kit_purchase)
78+
3579
# Distributions
3680
distributions = create_list(:distribution, 2, issued_at: within_time, organization: organization)
3781
outside_distributions = create_list(:distribution, 2, issued_at: outside_time, organization: organization)
@@ -84,19 +128,32 @@
84128
end
85129
end
86130

87-
it "should report normal values" do
88-
organization.items.period_supplies.first.update!(distribution_quantity: 20)
131+
describe "with values" do
132+
it "should report normal values" do
133+
organization.items.period_supplies.first.update!(distribution_quantity: 20)
89134

90-
expect(report.report[:name]).to eq("Period Supplies")
91-
expect(report.report[:entries]).to match(hash_including({
92-
"% period supplies bought" => "60%",
93-
"% period supplies donated" => "40%",
94-
"Period supplies distributed" => "2,000",
95-
"Period supplies per adult per month" => 20,
96-
"Money spent purchasing period supplies" => "$30.00"
97-
}))
98-
expect(report.report[:entries]["Period supplies"].split(", "))
99-
.to contain_exactly("Tampons", "Pads", "Liners (Menstrual)")
135+
expect(report.report[:name]).to eq("Period Supplies")
136+
expect(report.report[:entries]).to match(hash_including({
137+
"% period supplies bought" => "67%",
138+
"% period supplies donated" => "33%",
139+
"Period supplies distributed" => "2,300",
140+
"Money spent purchasing period supplies" => "$40.00"
141+
}))
142+
expect(report.report[:entries]["Period supplies"].split(", "))
143+
.to contain_exactly("Adult Pads", "Adult Tampons", "Liners", "Liners (Menstrual)", "Pads", "Tampons")
144+
end
145+
146+
it "returns the correct quantity of period supplies from kits" do
147+
expect(report.distributed_period_supplies_from_kits).to eq(300)
148+
end
149+
150+
it "returns the correct quantity of donated period supplies from kits" do
151+
expect(report.donated_supplies_from_kits).to eq(50)
152+
end
153+
154+
it "returns the correct quantity of purchased items in kits" do
155+
expect(report.purchased_supplies_from_kits).to eq(150)
156+
end
100157
end
101158
end
102159
end

0 commit comments

Comments
 (0)