diff --git a/app/models/cost_entry.rb b/app/models/cost_entry.rb index 667e54cbc1..3ee6fa8f71 100644 --- a/app/models/cost_entry.rb +++ b/app/models/cost_entry.rb @@ -58,6 +58,25 @@ class CostEntry < ActiveRecord::Base .includes([:project, :user]) } + def self.costs_of(work_packages:) + # N.B. Because of an AR quirks the code below uses statements like + # where(work_package_id: ids) + # You would expect to be able to simply write those as + # where(work_package: work_packages) + # However, AR (Rails 4.2) will not expand :includes + :references inside a subquery, + # which will render the query invalid. Therefore we manually extract the IDs in a separate (pluck) query. + ids = if work_packages.respond_to?(:pluck) + work_packages.pluck(:id) + else + Array(work_packages).map { |wp| wp.id } + end + CostEntry.where(work_package_id: ids) + .joins(work_package: :project) + .visible_costs + .sum("COALESCE(#{CostEntry.table_name}.overridden_costs, + #{CostEntry.table_name}.costs)").to_f + end + def after_initialize if new_record? && cost_type.nil? if default_cost_type = CostType.default diff --git a/lib/open_project/costs/patches/query_patch.rb b/lib/open_project/costs/patches/query_patch.rb index 2f5183c038..f6eaca8cb1 100644 --- a/lib/open_project/costs/patches/query_patch.rb +++ b/lib/open_project/costs/patches/query_patch.rb @@ -22,6 +22,13 @@ module OpenProject::Costs::Patches::QueryPatch include ActionView::Helpers::NumberHelper alias :super_value :value + def initialize(name, options = {}) + super + + @sum_function = options[:summable] + self.summable = @sum_function.respond_to?(:call) + end + def value(work_package) number_to_currency(work_package.send(name)) end @@ -39,7 +46,7 @@ module OpenProject::Costs::Patches::QueryPatch end def sum_of(work_packages) - work_packages.map { |wp| real_value(wp) }.compact.reduce(:+) + @sum_function.call(work_packages) end end @@ -51,9 +58,26 @@ module OpenProject::Costs::Patches::QueryPatch # Same as typing in the class base.class_eval do add_available_column(QueryColumn.new(:cost_object_subject)) - add_available_column(CurrencyQueryColumn.new(:material_costs, summable: true)) - add_available_column(CurrencyQueryColumn.new(:labor_costs, summable: true)) - add_available_column(CurrencyQueryColumn.new(:overall_costs, summable: true)) + + add_available_column(CurrencyQueryColumn.new( + :material_costs, + summable: -> (work_packages) { + CostEntry.costs_of(work_packages: work_packages) + })) + + add_available_column(CurrencyQueryColumn.new( + :labor_costs, + summable: -> (work_packages) { + TimeEntry.costs_of(work_packages: work_packages) + })) + + add_available_column(CurrencyQueryColumn.new( + :overall_costs, + summable: -> (work_packages) { + labor_costs = TimeEntry.costs_of(work_packages: work_packages) + material_costs = CostEntry.costs_of(work_packages: work_packages) + labor_costs + material_costs + })) Queries::WorkPackages::Filter.add_filter_type_by_field('cost_object_id', 'list_optional') diff --git a/lib/open_project/costs/patches/time_entry_patch.rb b/lib/open_project/costs/patches/time_entry_patch.rb index cae29dbd0f..d7c3cc9750 100644 --- a/lib/open_project/costs/patches/time_entry_patch.rb +++ b/lib/open_project/costs/patches/time_entry_patch.rb @@ -53,6 +53,25 @@ module OpenProject::Costs::Patches::TimeEntryPatch includes(:project, :user) .where([view_time_entries, view_hourly_rates].join(' AND ')) } + + def self.costs_of(work_packages:) + # N.B. Because of an AR quirks the code below uses statements like + # where(work_package_id: ids) + # You would expect to be able to simply write those as + # where(work_package: work_packages) + # However, AR (Rails 4.2) will not expand :includes + :references inside a subquery, + # which will render the query invalid. Therefore we manually extract the IDs in a separate (pluck) query. + ids = if work_packages.respond_to?(:pluck) + work_packages.pluck(:id) + else + Array(work_packages).map { |wp| wp.id } + end + TimeEntry.where(work_package_id: ids) + .joins(work_package: :project) + .visible_costs + .sum("COALESCE(#{TimeEntry.table_name}.overridden_costs, + #{TimeEntry.table_name}.costs)").to_f + end end end diff --git a/lib/open_project/costs/patches/work_package_patch.rb b/lib/open_project/costs/patches/work_package_patch.rb index c11de5cd6a..46519d53b2 100644 --- a/lib/open_project/costs/patches/work_package_patch.rb +++ b/lib/open_project/costs/patches/work_package_patch.rb @@ -107,19 +107,11 @@ module OpenProject::Costs::Patches::WorkPackagePatch end def material_costs - @material_costs ||= cost_entries.visible_costs(User.current, project).sum("CASE - WHEN #{CostEntry.table_name}.overridden_costs IS NULL THEN - #{CostEntry.table_name}.costs - ELSE - #{CostEntry.table_name}.overridden_costs END").to_f + CostEntry.costs_of(work_packages: self) end def labor_costs - @labor_costs ||= time_entries.visible_costs(User.current, project).sum("CASE - WHEN #{TimeEntry.table_name}.overridden_costs IS NULL THEN - #{TimeEntry.table_name}.costs - ELSE - #{TimeEntry.table_name}.overridden_costs END").to_f + TimeEntry.costs_of(work_packages: self) end def overall_costs