Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix children served with kits calculation #5027

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions app/services/reports/children_served_report_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(year:, organization:)
def report
@report ||= { name: 'Children Served',
entries: {
'Average children served monthly' => number_with_delimiter(average_children_monthly.round),
'Average children served monthly' => number_with_delimiter(average_children_monthly),
'Total children served' => number_with_delimiter(total_children_served),
'Repackages diapers?' => organization.repackage_essentials? ? 'Y' : 'N',
'Monthly diaper distributions?' => organization.distribute_monthly? ? 'Y' : 'N'
Expand All @@ -28,7 +28,7 @@ def total_children_served

# @return [Float]
def average_children_monthly
total_children_served / 12.0
(total_children_served / 12.0).round(2)
end

private
Expand All @@ -38,19 +38,30 @@ def total_children_served_with_loose_disposables
.distributions
.for_year(year)
.joins(line_items: :item)
.merge(Item.disposable)
.sum('line_items.quantity / COALESCE(items.distribution_quantity, 50)')
.merge(Item.loose.disposable)
.pick(Arel.sql("CEILING(SUM(line_items.quantity::numeric / COALESCE(items.distribution_quantity, 50)))"))
.to_i
end

# These joins look circular but are needed due to polymorphic relationships.
# A distribution has many line_items, items, and base_items but kits also
# have the same relationships and we want to perform calculations on the
# items in the kits not the kit items themselves.
def children_served_with_kits_containing_disposables
organization
.distributions
.for_year(year)
.joins(line_items: {item: :kit})
.merge(Item.disposable)
.where.not(items: {kit_id: nil})
.distinct
.count("kits.id")
kits_subquery = organization
.distributions
.for_year(year)
.joins(line_items: { item: { kit: { line_items: {item: :base_item} }}})
.where("base_items.category ILIKE '%diaper%' AND
NOT base_items.category ILIKE '%cloth%' OR base_items.name ILIKE '%cloth%' AND
NOT base_items.category ILIKE '%adult%'")
.select("DISTINCT ON (distributions.id, line_items.id, kits.id) line_items.quantity, items.distribution_quantity")
.to_sql

Distribution
.from("(#{kits_subquery}) AS q")
.pick(Arel.sql("CEILING(SUM(q.quantity::numeric / COALESCE(q.distribution_quantity, 1)))"))
.to_i
end
end
end
135 changes: 96 additions & 39 deletions spec/services/reports/children_served_report_service_spec.rb
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
RSpec.describe Reports::ChildrenServedReportService, type: :service do
let(:year) { 2020 }
let(:within_time) { Time.zone.parse("2020-05-31 14:00:00") }
let(:outside_time) { Time.zone.parse("2019-05-31 14:00:00") }

describe '#report' do
it 'should report zero values' do
organization = create(:organization)
report = described_class.new(organization: organization, year: year).report
expect(report).to eq({
name: 'Children Served',
entries: {
'Average children served monthly' => "0",
'Total children served' => "0",
'Repackages diapers?' => 'N',
'Monthly diaper distributions?' => 'N'
}
})
name: 'Children Served',
entries: {
'Average children served monthly' => "0.0",
'Total children served' => "0",
'Repackages diapers?' => 'N',
'Monthly diaper distributions?' => 'N'
}
})
end

it 'should report normal values' do
organization = create(:organization, :with_items, distribute_monthly: true, repackage_essentials: true)

within_time = Time.zone.parse("2020-05-31 14:00:00")
outside_time = Time.zone.parse("2019-05-31 14:00:00")

disposable_item = organization.items.disposable.first
disposable_item.update!(distribution_quantity: 20)
non_disposable_item = organization.items.where.not(id: organization.items.disposable).first

# Kits
kit = create(:kit, organization: organization)

create(:base_item, name: "Toddler Disposable Diaper", partner_key: "toddler diapers", category: "disposable diaper")
create(:base_item, name: "Infant Disposable Diaper", partner_key: "infant diapers", category: "infant disposable diaper")

toddler_disposable_kit_item = create(:item, name: "Toddler Disposable Diapers", partner_key: "toddler diapers", kit: kit)
infant_disposable_kit_item = create(:item, name: "Infant Disposable Diapers", partner_key: "infant diapers", kit: kit)
toddler_disposable_kit_item = create(:item, name: "Toddler Disposable Diapers", partner_key: "toddler diapers")
infant_disposable_kit_item = create(:item, name: "Infant Disposable Diapers", partner_key: "infant diapers")

kit_1 = create(:kit, organization: organization, line_items: [
create(:line_item, item: toddler_disposable_kit_item),
create(:line_item, item: infant_disposable_kit_item)
])

kit_2 = create(:kit, organization: organization, line_items: [
create(:line_item, item: toddler_disposable_kit_item),
create(:line_item, item: infant_disposable_kit_item)
])

create(:item, name: "Kit 1", kit: kit_1, organization:)
create(:item, name: "Kit 2", kit: kit_2, organization:)

# Distributions
distributions = create_list(:distribution, 2, issued_at: within_time, organization: organization)
Expand All @@ -46,19 +56,21 @@
infant_distribution = create(:distribution, organization: organization, issued_at: within_time)
toddler_distribution = create(:distribution, organization: organization, issued_at: within_time)

create(:line_item, :distribution, quantity: 10, item: toddler_disposable_kit_item, itemizable: infant_distribution)
create(:line_item, :distribution, quantity: 10, item: infant_disposable_kit_item, itemizable: toddler_distribution)
create(:line_item, quantity: 5, item: kit_1.item, itemizable: infant_distribution)
create(:line_item, quantity: 5, item: kit_1.item, itemizable: toddler_distribution)
create(:line_item, quantity: 5, item: kit_2.item, itemizable: infant_distribution)
create(:line_item, quantity: 5, item: kit_2.item, itemizable: toddler_distribution)

report = described_class.new(organization: organization, year: within_time.year).report
expect(report).to eq({
name: 'Children Served',
entries: {
'Average children served monthly' => "8",
'Total children served' => "101",
'Repackages diapers?' => 'Y',
'Monthly diaper distributions?' => 'Y'
}
})
name: 'Children Served',
entries: {
'Average children served monthly' => "10.0",
'Total children served' => "120", # 100 normal and 20 from kits
'Repackages diapers?' => 'Y',
'Monthly diaper distributions?' => 'Y'
}
})
end

it 'should work with no distribution_quantity' do
Expand All @@ -71,13 +83,18 @@
non_disposable_item = organization.items.where.not(id: organization.items.disposable).first

# Kits
kit = create(:kit, organization: organization)

create(:base_item, name: "Toddler Disposable Diaper", partner_key: "toddler diapers", category: "disposable diaper")
create(:base_item, name: "Infant Disposable Diaper", partner_key: "infant diapers", category: "infant disposable diaper")

toddler_disposable_kit_item = create(:item, name: "Toddler Disposable Diapers", partner_key: "toddler diapers", kit: kit)
infant_disposable_kit_item = create(:item, name: "Infant Disposable Diapers", partner_key: "infant diapers", kit: kit)
toddler_disposable_kit_item = create(:item, name: "Toddler Disposable Diapers", partner_key: "toddler diapers")
infant_disposable_kit_item = create(:item, name: "Infant Disposable Diapers", partner_key: "infant diapers")

kit = create(:kit, organization: organization, line_items: [
create(:line_item, item: toddler_disposable_kit_item),
create(:line_item, item: infant_disposable_kit_item)
])

create(:item, name: "Kit 1", kit:, organization:)

# Distributions
distributions = create_list(:distribution, 2, issued_at: within_time, organization: organization)
Expand All @@ -90,19 +107,59 @@
infant_distribution = create(:distribution, organization: organization, issued_at: within_time)
toddler_distribution = create(:distribution, organization: organization, issued_at: within_time)

create(:line_item, :distribution, quantity: 10, item: toddler_disposable_kit_item, itemizable: infant_distribution)
create(:line_item, :distribution, quantity: 10, item: infant_disposable_kit_item, itemizable: toddler_distribution)
create(:line_item, quantity: 10, item: kit.item, itemizable: infant_distribution)
create(:line_item, quantity: 10, item: kit.item, itemizable: toddler_distribution)

report = described_class.new(organization: organization, year: within_time.year).report
expect(report).to eq({
name: 'Children Served',
entries: {
'Average children served monthly' => "5.0",
'Total children served' => "60", # 40 normal and 20 from kits
'Repackages diapers?' => 'Y',
'Monthly diaper distributions?' => 'Y'
}
})
end

it "rounds children served to integer ceiling" do
organization = create(:organization, :with_items)

create(:base_item, name: "Toddler Disposable Diaper", partner_key: "toddler diapers", category: "disposable diaper")
create(:base_item, name: "Infant Disposable Diaper", partner_key: "infant diapers", category: "infant disposable diaper")
create(:base_item, name: "Adult Diaper", partner_key: "adult diapers", category: "adult diaper")

toddler_disposable_kit_item = create(:item, name: "Toddler Disposable Diapers", partner_key: "toddler diapers")
infant_disposable_kit_item = create(:item, name: "Infant Disposable Diapers", partner_key: "infant diapers")
not_disposable_kit_item = create(:item, name: "Adult Diapers", partner_key: "adult diapers")

# this quantity shouldn't matter so I'm setting it to a high number to ensure it isn't used
kit = create(:kit, organization: organization, line_items: [
create(:line_item, quantity: 1000, item: toddler_disposable_kit_item),
create(:line_item, quantity: 1000, item: infant_disposable_kit_item),
create(:line_item, quantity: 1000, item: not_disposable_kit_item)
])

create(:item, name: "Kit 1", kit:, organization:, distribution_quantity: 3)

# Distributions
toddler_distribution = create(:distribution, organization: organization, issued_at: within_time)
infant_distribution = create(:distribution, organization: organization, issued_at: within_time)

[toddler_distribution, infant_distribution].each do |distribution|
create(:line_item, quantity: 2, item: kit.item, itemizable: distribution)
end

report = described_class.new(organization: organization, year: within_time.year).report
expect(report).to eq({
name: 'Children Served',
entries: {
'Average children served monthly' => "3",
'Total children served' => "41",
'Repackages diapers?' => 'Y',
'Monthly diaper distributions?' => 'Y'
}
})
name: "Children Served",
entries: {
"Average children served monthly" => "0.17",
"Total children served" => "2", # 2 kits / 3 distribution_quantity = 1 child served * 2 distributions = 2
"Repackages diapers?" => "N",
"Monthly diaper distributions?" => "N"
}
})
end
end
end