diff --git a/app/models/contract.rb b/app/models/contract.rb index 9ddb58f..ba23fdf 100644 --- a/app/models/contract.rb +++ b/app/models/contract.rb @@ -38,12 +38,6 @@ class Contract < ActiveRecord::Base define_method(mthd) { "TODO in later release" } end - [:total_spent, - :profit_spent - ].each do |mthd| - define_method(mthd) { "TODO" } - end - # OPTIMIZE: N+1 def labor_budget deliverables.inject(0) {|total, deliverable| total += deliverable.labor_budget_total } @@ -81,11 +75,22 @@ class Contract < ActiveRecord::Base deliverables.inject(0) {|total, deliverable| total += deliverable.total } end + # OPTIMIZE: N+1 + def total_spent + deliverables.inject(0) {|total, deliverable| total += deliverable.total_spent } + end + # OPTIMIZE: N+1 def profit_budget deliverables.inject(0) {|total, deliverable| total += deliverable.profit_budget } end + # OPTIMIZE: N+1 + def profit_left + deliverables.inject(0) {|total, deliverable| total += deliverable.profit_left } + end + alias_method :profit_spent, :profit_left + PaymentTerms = { :net_0 => :text_payment_terms_net_0, :net_15 => :text_payment_terms_net_15, diff --git a/app/models/fixed_deliverable.rb b/app/models/fixed_deliverable.rb index a856bc0..c944114 100644 --- a/app/models/fixed_deliverable.rb +++ b/app/models/fixed_deliverable.rb @@ -15,12 +15,23 @@ class FixedDeliverable < Deliverable read_attribute(:total) || 0.0 end + # Fixed deliverables are always 100% spent + def total_spent + total + end + # The amount of profit that is budgeted for this deliverable. # Profit = Total - ( Labor + Overhead + Fixed + Markup ) def profit_budget budgets = labor_budget_total + overhead_budget_total (total || 0.0) - budgets end + + # The amount of money remaining after expenses have been taken out + # Profit left = Total - Labor spent - Overhead spent + def profit_left + total_spent - labor_budget_spent - overhead_spent + end # Hardcoded value used as a wrapper for the old Budget plugin API. # diff --git a/app/models/hourly_deliverable.rb b/app/models/hourly_deliverable.rb index f32d372..ddaa8c5 100644 --- a/app/models/hourly_deliverable.rb +++ b/app/models/hourly_deliverable.rb @@ -19,8 +19,23 @@ class HourlyDeliverable < Deliverable return 0 if contract.billable_rate.blank? return 0 if labor_budgets.count == 0 && overhead_budgets.count == 0 - hours = labor_budgets.sum(:hours) + overhead_budgets.sum(:hours) - return contract.billable_rate * hours + return contract.billable_rate * labor_budgets.sum(:hours) + end + + # Total amount to be billed on the deliverable, using the total time logged + # and the contract rate + def total_spent + return 0 if contract.nil? + return 0 if contract.billable_rate.blank? + return 0 unless self.issues.count > 0 + + time_logs = self.issues.collect(&:time_entries).flatten + hours = time_logs.inject(0) {|total, time_entry| + total += time_entry.hours if time_entry.billable? + total + } + + return hours * contract.billable_rate end # Block setting the total on HourlyDeliverables @@ -38,4 +53,10 @@ class HourlyDeliverable < Deliverable budgets = labor_budget_total + overhead_budget_total (total || 0.0) - budgets end + + # The amount of money remaining after expenses have been taken out + # Profit left = Total - Labor spent - Overhead spent + def profit_left + total_spent - labor_budget_spent - overhead_spent + end end diff --git a/test/unit/contract_test.rb b/test/unit/contract_test.rb index a269d92..bb02ef0 100644 --- a/test/unit/contract_test.rb +++ b/test/unit/contract_test.rb @@ -163,7 +163,7 @@ class ContractTest < ActiveSupport::TestCase LaborBudget.generate!(:deliverable => @deliverable_2, :hours => 10) OverheadBudget.generate!(:deliverable => @deliverable_2, :hours => 20) - assert_equal 10_000 + (30 * 100), contract.total_budget + assert_equal 10_000 + (10 * 100), contract.total_budget end end @@ -180,9 +180,79 @@ class ContractTest < ActiveSupport::TestCase OverheadBudget.generate!(:deliverable => @deliverable_2, :hours => 20) assert_equal 10_000 - 4000, @deliverable_1.profit_budget - assert_equal (30 * 100.0) - 0, @deliverable_2.profit_budget - assert_equal 9000, contract.profit_budget + assert_equal (10 * 100.0) - 0, @deliverable_2.profit_budget + assert_equal 7000, contract.profit_budget end end + + context "#total_spent" do + should "sum all of the total spents on the Deliverables" do + configure_overhead_plugin + + contract = Contract.generate!(:billable_rate => 150.0) + + @project = Project.generate! + @developer = User.generate! + @role = Role.generate! + User.add_to_project(@developer, @project, @role) + + contract.deliverables << @deliverable_1 = FixedDeliverable.generate!(:total => 10_000) + contract.deliverables << @deliverable_2 = HourlyDeliverable.generate!(:contract => contract) + @deliverable_2.issues << @issue1 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue1, :project => @project, + :activity => @billable_activity, + :user => @developer) + + assert_equal 10_000, @deliverable_1.total_spent + assert_equal 2250, @deliverable_2.total_spent + assert_equal 12_250, contract.total_spent + end + end + + context "#profit_left" do + should "sum all of the profit left on all of the Deliverables" do + configure_overhead_plugin + + contract = Contract.generate!(:billable_rate => 150.0) + + @project = Project.generate! + @developer = User.generate! + @manager = User.generate! + @role = Role.generate! + User.add_to_project(@developer, @project, @role) + User.add_to_project(@manager, @project, @role) + @rate = Rate.generate!(:project => @project, + :user => @developer, + :date_in_effect => Date.yesterday, + :amount => 55) + @rate = Rate.generate!(:project => @project, + :user => @manager, + :date_in_effect => Date.yesterday, + :amount => 75) + + contract.deliverables << @deliverable_1 = FixedDeliverable.generate!(:total => 2000) + @deliverable_1.issues << @issue1 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue1, :project => @project, + :activity => @billable_activity, + :user => @developer) + TimeEntry.generate!(:hours => 4, :issue => @issue1, :project => @project, + :activity => @non_billable_activity, + :user => @manager) + + contract.deliverables << @deliverable_2 = HourlyDeliverable.generate!(:contract => contract) + @deliverable_2.issues << @issue2 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue2, :project => @project, + :activity => @billable_activity, + :user => @developer) + TimeEntry.generate!(:hours => 4, :issue => @issue2, :project => @project, + :activity => @non_billable_activity, + :user => @manager) + + assert_equal 875, @deliverable_1.profit_left + assert_equal 1125, @deliverable_2.profit_left + assert_equal 2000, contract.profit_left + end + + end end diff --git a/test/unit/fixed_deliverable_test.rb b/test/unit/fixed_deliverable_test.rb index f46cc01..b480d76 100644 --- a/test/unit/fixed_deliverable_test.rb +++ b/test/unit/fixed_deliverable_test.rb @@ -21,4 +21,47 @@ class FixedDeliverableTest < ActiveSupport::TestCase assert_equal 0, FixedDeliverable.generate(:total => nil).profit_budget end end + + context "#total_spent" do + should "equal the budgeted total" do + assert_equal 1000, FixedDeliverable.generate(:total => 1_000).total_spent + end + end + + context "#profit_left" do + should "be the total_spent minus the labor budget spent minus the overhead budget spent" do + configure_overhead_plugin + + @project = Project.generate! + @developer = User.generate! + @manager = User.generate! + @role = Role.generate! + User.add_to_project(@developer, @project, @role) + User.add_to_project(@manager, @project, @role) + @rate = Rate.generate!(:project => @project, + :user => @developer, + :date_in_effect => Date.yesterday, + :amount => 55) + @rate = Rate.generate!(:project => @project, + :user => @manager, + :date_in_effect => Date.yesterday, + :amount => 75) + + @deliverable_1 = FixedDeliverable.generate!(:total => 2000) + @deliverable_1.issues << @issue1 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue1, :project => @project, + :activity => @billable_activity, + :user => @developer) + TimeEntry.generate!(:hours => 4, :issue => @issue1, :project => @project, + :activity => @non_billable_activity, + :user => @manager) + + # Check intermediate values + assert_equal 825, @deliverable_1.labor_budget_spent + assert_equal 300, @deliverable_1.overhead_spent + + assert_equal 875, @deliverable_1.profit_left + + end + end end diff --git a/test/unit/hourly_deliverable_test.rb b/test/unit/hourly_deliverable_test.rb index 63856d1..9c51191 100644 --- a/test/unit/hourly_deliverable_test.rb +++ b/test/unit/hourly_deliverable_test.rb @@ -20,13 +20,33 @@ class HourlyDeliverableTest < ActiveSupport::TestCase assert_equal 0, d.total end - should "multiply the total number of budgeted hours by the contract billable rate" do + should "multiply the total number of labor budget hours by the contract billable rate" do contract = Contract.generate!(:billable_rate => 100.0) d = HourlyDeliverable.generate!(:contract => contract) d.labor_budgets << LaborBudget.generate!(:hours => 10) d.overhead_budgets << OverheadBudget.generate!(:hours => 20) - assert_equal 100.0 * 30, d.total + assert_equal 100.0 * 10, d.total + end + end + + context "#total_spent" do + should "be equal to the number of hours used multipled by the contract rate" do + configure_overhead_plugin + + contract = Contract.generate!(:billable_rate => 150.0) + @project = Project.generate! + @developer = User.generate! + @role = Role.generate! + User.add_to_project(@developer, @project, @role) + + d = HourlyDeliverable.generate!(:contract => contract) + d.issues << @issue1 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue1, :project => @project, + :activity => @billable_activity, + :user => @developer) + + assert_equal 2250, d.total_spent end end @@ -52,7 +72,7 @@ class HourlyDeliverableTest < ActiveSupport::TestCase context "#profit_budget" do setup do - @contract = Contract.generate!(:billable_rate => 100.0) + @contract = Contract.generate!(:billable_rate => 150.0) @deliverable = HourlyDeliverable.generate!(:contract => @contract) end @@ -63,12 +83,50 @@ class HourlyDeliverableTest < ActiveSupport::TestCase end should "be the total minus the sum of all of the budgets' amounts" do - LaborBudget.generate!(:deliverable => @deliverable, :hours => 10, :budget => 2000) - LaborBudget.generate!(:deliverable => @deliverable, :hours => 5, :budget => 1000) - OverheadBudget.generate!(:deliverable => @deliverable, :hours => 15, :budget => 2000) + LaborBudget.generate!(:deliverable => @deliverable, :hours => 5, :budget => 250) + LaborBudget.generate!(:deliverable => @deliverable, :hours => 5, :budget => 250) + OverheadBudget.generate!(:deliverable => @deliverable, :hours => 3, :budget => 225) - assert_equal 30 * 100, @deliverable.total - assert_equal 3000 - (2000 + 1000 + 2000), @deliverable.profit_budget + assert_equal 1500, @deliverable.total + assert_equal 1500 - (225 + 250 + 250), @deliverable.profit_budget end end + + context "#profit_left" do + should "be equal to the total to bill (total_spent) minus the labor budget spent minus the overhead spent" do + configure_overhead_plugin + + contract = Contract.generate!(:billable_rate => 150.0) + @project = Project.generate! + @developer = User.generate! + @manager = User.generate! + @role = Role.generate! + User.add_to_project(@developer, @project, @role) + User.add_to_project(@manager, @project, @role) + @rate = Rate.generate!(:project => @project, + :user => @developer, + :date_in_effect => Date.yesterday, + :amount => 55) + @rate = Rate.generate!(:project => @project, + :user => @manager, + :date_in_effect => Date.yesterday, + :amount => 75) + + @deliverable_1 = HourlyDeliverable.generate!(:contract => contract) + @deliverable_1.issues << @issue1 = Issue.generate_for_project!(@project) + TimeEntry.generate!(:hours => 15, :issue => @issue1, :project => @project, + :activity => @billable_activity, + :user => @developer) + TimeEntry.generate!(:hours => 4, :issue => @issue1, :project => @project, + :activity => @non_billable_activity, + :user => @manager) + + # Check intermediate values + assert_equal 825, @deliverable_1.labor_budget_spent + assert_equal 300, @deliverable_1.overhead_spent + + assert_equal 1125, @deliverable_1.profit_left + end + end + end