diff --git a/app/services/reports/children_served_report_service.rb b/app/services/reports/children_served_report_service.rb index 70886d5e00..e2828b440b 100644 --- a/app/services/reports/children_served_report_service.rb +++ b/app/services/reports/children_served_report_service.rb @@ -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' @@ -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 @@ -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 diff --git a/spec/services/reports/children_served_report_service_spec.rb b/spec/services/reports/children_served_report_service_spec.rb index 84a05ec7f7..216ecc0fa4 100644 --- a/spec/services/reports/children_served_report_service_spec.rb +++ b/spec/services/reports/children_served_report_service_spec.rb @@ -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) @@ -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 @@ -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) @@ -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