65 Commits

Author SHA1 Message Date
Eric Davis
f8270498f3 [#4477] Add a simple title/tooltip for the FixedBudget description 2010-09-17 15:37:30 -07:00
Eric Davis
2c04ef4f45 [#4477] Add FixedBudget items into the total and profit Deliverable calculations 2010-09-17 15:22:53 -07:00
Eric Davis
d78e7b1dc3 [#4477] Scope fixed budget items by period too (for Retainers) 2010-09-17 14:51:48 -07:00
Eric Davis
67dd75a980 [#4477] Skip blank FixedBudget items in the deliverable details 2010-09-17 14:23:29 -07:00
Eric Davis
55c9f93c6c [#4477] Change Retainers to scope #fixed_budget_total and #fixed_markup_budget_total by date 2010-09-17 14:15:08 -07:00
Eric Davis
081df7874d [#4477] Added the total FixedBudget markup to the deliverable details 2010-09-17 13:43:35 -07:00
Eric Davis
ed7536897e [#4477] Show each fixed budget item in the deliverable details 2010-09-17 13:38:44 -07:00
Eric Davis
86be29cecc [#4477] Show the total fixed budget in the deliverable row 2010-09-17 13:27:35 -07:00
Eric Davis
294c88f11d [#4477] Hook up FixedBudgets to Contract#fixed_budget and #fixed_markup_budget 2010-09-17 13:16:59 -07:00
Eric Davis
edc8db7fe2 [#4477] Hook FixedBudgets into Retainer's periods. 2010-09-09 17:27:32 -07:00
Eric Davis
feee5e8b43 [#4477] Added FixedBudgets to the Deliverable finance form. 2010-09-09 17:06:35 -07:00
Eric Davis
07da5d98d8 [#4477] Added the FixedBudget markup calculations. 2010-09-09 16:12:48 -07:00
Eric Davis
7f7e5b3042 [#4477] Added model and migration for FixedBudget. 2010-09-09 15:57:58 -07:00
Eric Davis
d315252565 Refactor: use #scope_date_status. 2010-09-09 15:44:36 -07:00
Eric Davis
6b38dfca91 Refactor: use utility method. 2010-09-09 15:43:38 -07:00
Eric Davis
75046cfdd1 Refactor: extract utility method. 2010-09-09 15:42:12 -07:00
Eric Davis
12fffee314 Refactor: extract scope_date_status method to find how to scope the periods. 2010-09-09 15:39:25 -07:00
Eric Davis
362610dc77 [#4420] Updated the flash messages for Deliverables. 2010-09-09 15:12:01 -07:00
Eric Davis
1b08487a0d [#4420] Add a message for new Retainers that Finances are for a single month. 2010-09-09 15:04:26 -07:00
Eric Davis
5ef7d0271d [#4420] Display per period Retainer values on the Contract details page. 2010-09-09 14:56:20 -07:00
Eric Davis
0a1bb9169d [#4420] Hook up Retainer's total_spent and profit_left for periods. 2010-09-09 14:22:25 -07:00
Eric Davis
91d8c1818e [#4420] Hook up Retainer's overhead_spent and labor_budget_spent for periods 2010-09-09 13:50:50 -07:00
Eric Davis
394236c741 WIP: need to finish up the backend still, some frontend is here for integration testing. 2010-09-08 17:54:59 -07:00
Eric Davis
82dec74eb7 [#4420] Changed the approach for Retainer calculations by using an optional parameter. 2010-09-08 17:49:51 -07:00
Eric Davis
c8b86db4c4 [#4420] Added a second Retainer calculation method for specific dates. 2010-09-08 14:54:36 -07:00
Eric Davis
0cc2523084 [#4420] Adding Retainer calculation methods for specific dates. 2010-09-08 14:50:25 -07:00
Eric Davis
7ed21f7fe2 [#4420] When a Retainer's period is changed, it will now reload it's details. 2010-09-08 14:03:06 -07:00
Eric Davis
ad6766fd2f [#4420] Add the options for Retainers to select which month to display. (UI only). 2010-09-08 11:57:46 -07:00
Eric Davis
d3052d7d54 [#4420] Implemented RetainerDeliverable#current_period 2010-09-08 11:46:59 -07:00
Eric Davis
9f8f1238aa [#4420] Add a Javascript prompt when extending or shrinking a Deliverable's period. 2010-09-08 10:57:09 -07:00
Eric Davis
df53f27114 [#4420] Remove Deliverable#frequency, assumed to be monthly. 2010-09-01 20:24:29 -07:00
Eric Davis
6a0c6348ed [#4420] Missing locale string. 2010-09-01 20:10:20 -07:00
Eric Davis
dfab7b5714 [#4420] Use empty budgets for Retainers that are missing them so they appear on the form. 2010-09-01 19:28:18 -07:00
Eric Davis
abc4d30346 [#4420] Display a message in the form when a Retainer has no effective months. 2010-09-01 18:50:24 -07:00
Eric Davis
994e9b0332 [#4420] Handle edge case when a retainer has no start or end date set. 2010-09-01 18:49:55 -07:00
Eric Davis
e5853100d5 [#4420] Added some Javascript to prompt the user if they try to create a Retainer without a date range. 2010-09-01 18:44:30 -07:00
Eric Davis
918561d667 [#4420] Handle edge cases if a Retainer has no start or end date to extend. 2010-09-01 18:19:43 -07:00
Eric Davis
ede645e359 [#4420] Added test for changing Retainer's start/end date without it's period. 2010-09-01 18:14:48 -07:00
Eric Davis
9858b87fff [#4420] When a Retainer's period is shrunk, budgets outside the period are destroyed. 2010-09-01 18:12:40 -07:00
Eric Davis
0a33cdefbb Remove dead test code. 2010-09-01 17:57:49 -07:00
Eric Davis
8f92dcc870 [#4420] Changed Retainers to support extending multiple Budget items. 2010-09-01 17:56:16 -07:00
Eric Davis
cb27ae2cc8 [#4420] Throw away single tests for start and end extensions. 2010-09-01 15:33:46 -07:00
Eric Davis
57d3830084 [#4420] Added a test for extending both the start and end dates. 2010-09-01 15:33:08 -07:00
Eric Davis
f46d222f4a [#4420] Refactor: extract method. 2010-09-01 15:29:37 -07:00
Eric Davis
2969afeaf4 [#4420] Refactor: extract method. 2010-09-01 15:24:33 -07:00
Eric Davis
bd3cd89dd0 [#4420] Refactor: extract method. 2010-09-01 15:20:28 -07:00
Eric Davis
4b12d9d9c8 [#4420] Retainers extended before the start date will duplicate the month's first budget items. 2010-09-01 15:15:16 -07:00
Eric Davis
16f8152599 [#4420] Retainers extended past the end date will duplicate the month's last budget items. 2010-09-01 15:09:36 -07:00
Eric Davis
c12a33e25d Merge branch 'master' into 4420-retainers 2010-09-01 14:53:32 -07:00
Eric Davis
0c38fe2c37 [#4466] Added deprecated methods to support more of the Budget plugin API. 2010-09-01 14:32:09 -07:00
Eric Davis
d48addbcb0 [#4420] Added support for overriding each period's finances on Retainers. 2010-08-30 10:51:37 -07:00
Eric Davis
c13f31b1b5 [#4420] Added a Deliverable finance form for each Retainer month. 2010-08-30 10:30:59 -07:00
Eric Davis
c3c472b3b1 [#4420] Extract the Deliverable finances form to a partial. 2010-08-30 10:09:13 -07:00
Eric Davis
40d4029cd1 [#4420] Make #months handle more edge cases. 2010-08-30 09:50:24 -07:00
Eric Davis
9a4b4f2b43 [#4420] Added OverheadBudget creation for Retainers. 2010-08-30 09:25:24 -07:00
Eric Davis
fc28f8351f [#4420] Fixed the after_save callback for Retainers. 2010-08-30 09:18:37 -07:00
Eric Davis
7d43ab7d74 [#4420] Working on creating labor budgets for each retainer month. 2010-08-23 16:15:57 -07:00
Eric Davis
f899b4e623 [#4420] Started on the Retainer's periods, only pending tests for now. 2010-08-16 15:06:08 -07:00
Eric Davis
4b162f85bf [#4420] Refactor: extract duplicated data to query. 2010-08-16 14:53:08 -07:00
Eric Davis
1120b39ed6 [#4420] Hook up Retainers so they can be saved. 2010-08-16 14:49:03 -07:00
Eric Davis
7d19c10714 [#4420] Show/hide the frequency field based on the selected Deliverable type. 2010-08-16 14:48:35 -07:00
Eric Davis
3e30629d44 [#4420] Add the frequency field for Retainers to the Model and View. 2010-08-16 14:47:47 -07:00
Eric Davis
8321903598 [#4420] Add frequency to deliverables for Retainers. 2010-08-16 14:08:04 -07:00
Eric Davis
f18d259c3c [#4420] Add a RetainerDeliverable model. 2010-08-16 13:59:42 -07:00
Eric Davis
9d52bcfbe2 Ignore tmp 2010-08-16 13:59:15 -07:00
31 changed files with 2295 additions and 233 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
webrat*
tmp/

View File

@@ -14,24 +14,29 @@ class DeliverablesController < InheritedResources::Base
def create
@deliverable = begin_of_association_chain.deliverables.build(params[:deliverable])
if params[:deliverable] && params[:deliverable][:type] && ['FixedDeliverable','HourlyDeliverable'].include?(params[:deliverable][:type])
if params[:deliverable] && params[:deliverable][:type] && Deliverable.valid_types.include?(params[:deliverable][:type])
@deliverable.type = params[:deliverable][:type]
end
create! { contract_url(@project, @contract) }
create!(:notice => l(:text_flash_deliverable_created, :name => @deliverable.title)) { contract_url(@project, @contract) }
end
def update
@deliverable = begin_of_association_chain.deliverables.find_by_id(params[:id])
params[:deliverable] = params[:fixed_deliverable] || params[:hourly_deliverable]
update! { contract_url(@project, @contract) }
params[:deliverable] = params[:fixed_deliverable] || params[:hourly_deliverable] || params[:retainer_deliverable]
update!(:notice => l(:text_flash_deliverable_updated, :name => @deliverable.title)) { contract_url(@project, @contract) }
end
def show
redirect_to contract_url(@project, @contract)
if show_partial?
@period = extract_period(params[:period])
render :partial => 'deliverables/details_row', :locals => {:contract => @contract, :deliverable => @contract.deliverables.find(params[:id]), :period => @period}
else
redirect_to contract_url(@project, @contract)
end
end
def destroy
destroy! { contract_url(@project, @contract) }
destroy!(:notice => l(:text_flash_deliverable_destroyed, :name => resource.title)) { contract_url(@project, @contract) }
end
protected
@@ -39,6 +44,11 @@ class DeliverablesController < InheritedResources::Base
def begin_of_association_chain
@contract
end
# Is only a partial requested?
def show_partial?
params[:format] == 'js' && params[:as] == 'deliverable_details_row'
end
private
@@ -47,4 +57,13 @@ class DeliverablesController < InheritedResources::Base
@project = @contract.project
end
def extract_period(param)
period = nil
if param.present? && param.match(/\A\d{4}-\d{2}\z/) # "YYYY-MM"
year, month = param.split('-')
period = Date.new(year.to_i, month.to_i, 1)
end
period
end
end

View File

@@ -3,6 +3,7 @@ module ContractsHelper
returning(deliverable) do |d|
d.labor_budgets.build if d.labor_budgets.empty?
d.overhead_budgets.build if d.overhead_budgets.empty?
d.fixed_budgets.build if d.fixed_budgets.empty?
end
end
@@ -85,4 +86,21 @@ module ContractsHelper
def format_value_field_for_contracts(value)
number_with_precision(value, :precision => Contract::ViewPrecision, :delimiter => ',')
end
def retainer_period_options(deliverable, method_options={})
selected = method_options[:selected]
if selected && selected.is_a?(Date)
selected = selected.strftime("%Y-%m")
end
options = []
options << content_tag(:option, l(:label_all).capitalize, :value => '')
deliverable.months.collect do |month|
value = month.strftime("%Y-%m")
options << content_tag(:option, month.strftime("%B %Y"), :value => value, :selected => (selected == value) ? 'selected' : nil)
end
options
end
end

View File

@@ -36,8 +36,8 @@ class Contract < ActiveRecord::Base
named_scope :by_name, {:order => "#{Contract.table_name}.name ASC"}
[:status, :contract_type,
:fixed_spent, :fixed_budget,
:markup_spent, :markup_budget,
:fixed_spent,
:fixed_markup_spent,
:discount_spent, :discount_budget
].each do |mthd|
define_method(mthd) { "TODO in later release" }
@@ -96,6 +96,16 @@ class Contract < ActiveRecord::Base
end
alias_method :profit_spent, :profit_left
# OPTIMIZE: N+1
def fixed_budget
deliverables.inject(0) {|total, deliverable| total += deliverable.fixed_budget_total }
end
# OPTIMIZE: N+1
def fixed_markup_budget
deliverables.inject(0) {|total, deliverable| total += deliverable.fixed_markup_budget_total }
end
def after_initialize
self.executed = false unless self.executed.present?
end

View File

@@ -8,10 +8,12 @@ class Deliverable < ActiveRecord::Base
belongs_to :manager, :class_name => 'User', :foreign_key => 'manager_id'
has_many :labor_budgets
has_many :overhead_budgets
has_many :fixed_budgets
has_many :issues
accepts_nested_attributes_for :labor_budgets
accepts_nested_attributes_for :overhead_budgets
accepts_nested_attributes_for :fixed_budgets
# Validations
validates_presence_of :title
@@ -22,12 +24,27 @@ class Deliverable < ActiveRecord::Base
delegate :name, :to => :contract, :prefix => true, :allow_nil => true
# Callbacks
# Register callbacks here, on new records the class isn't set so class-specific
# callbacks don't fire.
def after_save
if type == "RetainerDeliverable"
self.becomes(self.type.constantize).create_budgets_for_periods
end
end
named_scope :by_title, {:order => "#{Deliverable.table_name}.title ASC"}
def short_type
''
end
# Deliverable's aren't dated. Subclasses may override this for period behavior.
def current_date
nil
end
def to_s
title
end
@@ -44,14 +61,22 @@ class Deliverable < ActiveRecord::Base
end
end
def labor_budget_total
def labor_budget_total(date=nil)
labor_budgets.sum(:budget)
end
def overhead_budget_total
def overhead_budget_total(date=nil)
overhead_budgets.sum(:budget)
end
def profit_budget(date=nil)
nil
end
def labor_budget_hours(date=nil)
labor_budgets.sum(:hours)
end
# Total number of hours estimated in the Deliverable's budgets
def estimated_hour_budget_total
(labor_budgets.sum(:hours) || 0.0) +
@@ -63,11 +88,65 @@ class Deliverable < ActiveRecord::Base
issues.inject(0) {|total, issue| total += issue.spent_hours }
end
# Wrapper for the old Budget plugins' API
def fixed_budget_total(date=nil)
fixed_budgets.sum(:budget)
end
# OPTIMIZE: N+1
def fixed_markup_budget_total(date=nil)
fixed_budgets.inject(0) {|total, fixed_budget| total += fixed_budget.markup_value }
end
def filter_by_date(date=nil, &block)
block.call
end
def retainer?
type == "RetainerDeliverable"
end
def self.valid_types
['FixedDeliverable','HourlyDeliverable','RetainerDeliverable']
end
def self.valid_types_to_select
valid_types.inject([]) do |types, type|
types << [type.gsub(/Deliverable/i,''), type]
types
end
end
# Accessors from the budget plugin that need to be wrapped
def subject
warn "[DEPRECATION] Deliverable#subject is deprecated. Please use Deliverable#title instead."
title
end
def due
warn "[DEPRECATION] Deliverable#due is deprecated. Please use Deliverable#end_date instead."
end_date
end
def hours_used
warn "[DEPRECATION] Deliverable#hours_used is deprecated. Please use Deliverable#hours_spent_total instead."
hours_spent_total
end
def spent
warn "[DEPRECATION] Deliverable#spent is deprecated. Please use Deliverable#total_spent instead."
total_spent
end
def total_hours
warn "[DEPRECATION] Deliverable#total_hours is deprecated. Please use Deliverable#estimated_hour_budget_total instead."
estimated_hour_budget_total
end
def labor_budget
warn "[DEPRECATION] Deliverable#labor_budget is deprecated. Please use Deliverable#labor_budget_total instead."
labor_budget_total
end
if Rails.env.test?
generator_for :title, :method => :next_title

View File

@@ -0,0 +1,55 @@
class FixedBudget < ActiveRecord::Base
unloadable
# Associations
belongs_to :deliverable
# Validations
# Accessors
def budget=(v)
if v.is_a? String
write_attribute(:budget, v.gsub(/[$ ,]/, ''))
else
write_attribute(:budget, v)
end
end
named_scope :by_period, lambda {|date|
if date
{
:conditions => {:year => date.year, :month => date.month}
}
end
}
def markup_value
return 0 if budget.blank? || markup.blank?
case
when percent_markup?
percent = markup.gsub('%','').to_f
return budget.to_f * (percent / 100)
when straight_markup?
markup.gsub('$','').gsub(',','').to_f
else
0 # Invalid markup
end
end
def percent_markup?
markup && markup.match(/%/)
end
def straight_markup?
markup && markup.match(/\$/)
end
# Is this a blank budget item. Retainers will create blank ones when
# they are copied. (RetainerDeliverable#create_budgets_for_periods)
def blank_record?
return true if new_record?
return title.blank? && budget.blank? && markup.blank?
end
end

View File

@@ -11,26 +11,26 @@ class FixedDeliverable < Deliverable
'F'
end
def total
def total(date=nil)
read_attribute(:total) || 0.0
end
# Fixed deliverables are always 100% spent
def total_spent
def total_spent(date=nil)
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
def profit_budget(date=nil)
budgets = labor_budget_total(date) + overhead_budget_total(date) + fixed_budget_total(date) + fixed_markup_budget_total(date)
(total(date) || 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
def profit_left(date=nil)
total_spent(date) - labor_budget_spent(date) - overhead_spent(date)
end
# Hardcoded value used as a wrapper for the old Budget plugin API.

View File

@@ -14,17 +14,19 @@ class HourlyDeliverable < Deliverable
'H'
end
def total
# Total = ( Labor Hours * Billing Rate ) + ( Fixed + Markup )
def total(date=nil)
return 0 if contract.nil?
return 0 if contract.billable_rate.blank?
return 0 if labor_budgets.count == 0 && overhead_budgets.count == 0
return contract.billable_rate * labor_budgets.sum(:hours)
fixed_budget_amount = fixed_budget_total(date) + fixed_markup_budget_total(date)
return (contract.billable_rate * labor_budget_hours(date)) + fixed_budget_amount
end
# Total amount to be billed on the deliverable, using the total time logged
# and the contract rate
def total_spent
def total_spent(date=nil)
return 0 if contract.nil?
return 0 if contract.billable_rate.blank?
return 0 unless self.issues.count > 0
@@ -49,14 +51,14 @@ class HourlyDeliverable < Deliverable
# 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
def profit_budget(date=nil)
budgets = labor_budget_total(date) + overhead_budget_total(date) + fixed_budget_total(date) + fixed_markup_budget_total(date)
(total(date) || 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
def profit_left(date=nil)
total_spent(date) - labor_budget_spent(date) - overhead_spent(date)
end
end

View File

@@ -0,0 +1,402 @@
# A RetainerDeliverable is an HourlyDeliverable that is renewed at
# regular calendar periods. The Company bills a regular number of
# hours for a hourly rate whereby the budgets are reset over a
# regular cyclical period (monthly).
class RetainerDeliverable < HourlyDeliverable
unloadable
# Associations
# Validations
# Accessors
# Callbacks
before_update :check_for_extended_period
before_update :check_for_shrunk_period
def short_type
'R'
end
def current_date
Date.today
end
def current_period
current_date.strftime("%B %Y")
end
def beginning_date
start_date && start_date.beginning_of_month.to_date
end
def ending_date
end_date && end_date.end_of_month.to_date
end
def date_range
if beginning_date && ending_date && beginning_date <= ending_date
(beginning_date..ending_date)
else
[]
end
end
def within_date_range?(date)
date_range.include?(date)
end
def months
month_acc = []
current_date = beginning_date
return [] if current_date.nil? || ending_date.nil?
while current_date < ending_date do
month_acc << current_date
current_date = current_date.advance(:months => 1)
end
month_acc
end
# Returns the months used by the Deliverable that are before date
def months_before_date(date)
months.select {|m| m < date }
end
# Returns the months used by the Deliverable that are after date
def months_after_date(date)
months.select {|m| m > date }
end
def labor_budgets_for_date(date)
budgets = labor_budgets.all(:conditions => {:year => date.year, :month => date.month})
budgets = [labor_budgets.build(:year => date.year, :month => date.month)] if budgets.empty?
budgets
end
def overhead_budgets_for_date(date)
budgets = overhead_budgets.all(:conditions => {:year => date.year, :month => date.month})
budgets = [overhead_budgets.build(:year => date.year, :month => date.month)] if budgets.empty?
budgets
end
def fixed_budgets_for_date(date)
budgets = fixed_budgets.all(:conditions => {:year => date.year, :month => date.month})
budgets = [fixed_budgets.build(:year => date.year, :month => date.month)] if budgets.empty?
budgets
end
def labor_budget_total(date=nil)
case scope_date_status(date)
when :in
labor_budgets.sum(:budget, :conditions => {:year => date.year, :month => date.month})
when :out
0
else
super
end
end
def overhead_budget_total(date=nil)
case scope_date_status(date)
when :in
overhead_budgets.sum(:budget, :conditions => {:year => date.year, :month => date.month})
when :out
0
else
super
end
end
def labor_budget_hours(date=nil)
case scope_date_status(date)
when :in
labor_budgets.sum(:hours, :conditions => {:year => date.year, :month => date.month})
when :out
0
else
super
end
end
def fixed_budget_total(date=nil)
case scope_date_status(date)
when :in
fixed_budgets.sum(:budget, :conditions => {:year => date.year, :month => date.month})
when :out
0
else
super
end
end
def fixed_markup_budget_total(date=nil)
case scope_date_status(date)
when :in
fixed_budgets.
all(:conditions => {:year => date.year, :month => date.month}).
inject(0) {|total, fixed_budget| total += fixed_budget.markup_value }
when :out
0
else
super
end
end
def total_spent(date=nil)
case scope_date_status(date)
when :in
# TODO: duplicated on HourlyDeliverable#total_spent
return 0 if contract.nil?
return 0 if contract.billable_rate.blank?
return 0 unless self.issues.count > 0
issue_ids = self.issues.collect(&:id)
time_logs = time_entries_for_date_and_issue_ids(date, issue_ids)
hours = time_logs.inject(0) {|total, time_entry|
total += time_entry.hours if time_entry.billable?
total
}
return hours * contract.billable_rate
when :out
0
else
super
end
end
# TODO: stolen directly from redmine_overhead but with a block option
def labor_budget_spent_with_filter(&block)
return 0.0 unless self.issues.size > 0
total = 0.0
# Get all timelogs assigned
if block_given?
time_logs = block.call
else
time_logs = self.issues.collect(&:time_entries).flatten
end
return time_logs.collect {|time_log|
if time_log.billable?
time_log.cost
else
0.0
end
}.sum
end
# TODO: stolen directly from redmine_overhead but with a block option
def overhead_spent_with_filter(&block)
if block_given?
time_logs = block.call
else
time_logs = issues.collect(&:time_entries).flatten
end
return time_logs.collect {|time_entry|
if time_entry.billable?
0
else
time_entry.cost
end
}.sum
end
def labor_budget_spent(date=nil)
case scope_date_status(date)
when :in
labor_budget_spent_with_filter do
issue_ids = self.issues.collect(&:id)
time_entries_for_date_and_issue_ids(date, issue_ids)
end
when :out
0
else
labor_budget_spent_with_filter
end
end
def overhead_spent(date=nil)
case scope_date_status(date)
when :in
overhead_spent_with_filter do
issue_ids = self.issues.collect(&:id)
time_entries_for_date_and_issue_ids(date, issue_ids)
end
when :out
0
else
overhead_spent_with_filter
end
end
def create_budgets_for_periods
# For each month in the time span
months.each do |month|
# Iterate over all un-dated budgets, created dated versions
undated_labor_budgets = labor_budgets.all(:conditions => ["#{LaborBudget.table_name}.year IS NULL AND #{LaborBudget.table_name}.month IS NULL"])
undated_labor_budgets.each do |template_budget|
labor_budgets.create(template_budget.attributes.merge(:year => month.year, :month => month.month))
end
undated_overhead_budgets = overhead_budgets.all(:conditions => ["#{OverheadBudget.table_name}.year IS NULL AND #{OverheadBudget.table_name}.month IS NULL"])
undated_overhead_budgets.each do |template_budget|
overhead_budgets.create(template_budget.attributes.merge(:year => month.year, :month => month.month))
end
undated_fixed_budgets = fixed_budgets.all(:conditions => ["#{FixedBudget.table_name}.year IS NULL AND #{FixedBudget.table_name}.month IS NULL"])
undated_fixed_budgets.each do |template_budget|
fixed_budgets.create(template_budget.attributes.merge(:year => month.year, :month => month.month))
end
end
# Destroy origional un-dated budgets
labor_budgets.all(:conditions => ["#{LaborBudget.table_name}.year IS NULL AND #{LaborBudget.table_name}.month IS NULL"]).collect(&:destroy)
overhead_budgets.all(:conditions => ["#{OverheadBudget.table_name}.year IS NULL AND #{OverheadBudget.table_name}.month IS NULL"]).collect(&:destroy)
fixed_budgets.all(:conditions => ["#{FixedBudget.table_name}.year IS NULL AND #{FixedBudget.table_name}.month IS NULL"]).collect(&:destroy)
end
def check_for_extended_period
# TODO: brute force. Alternative would be to check end_date_changes to see if the period actually shifted
if end_date_changed?
extend_period_to_new_end_date
end
# TODO: brute force. Alternative would be to check start_date_changes to see if the period actually shifted
if start_date_changed?
extend_period_to_new_start_date
end
end
def check_for_shrunk_period
if end_date_changed? || start_date_changed?
shrink_budgets_to_new_period
end
end
private
def shrink_budgets_to_new_period
return if beginning_date.nil? || ending_date.nil?
labor_budgets.all.each do |labor_budget|
# Purge un-dated budgets, should not be saved at all
labor_budget.destroy unless labor_budget.year.present?
labor_budget.destroy unless labor_budget.month.present?
# Purge budgets outside the new beginning/ending range
unless (beginning_date..ending_date).to_a.include?(Date.new(labor_budget.year, labor_budget.month, 1))
labor_budget.destroy
end
end
overhead_budgets.all.each do |overhead_budget|
# Purge un-dated budgets, should not be saved at all
overhead_budget.destroy unless overhead_budget.year.present?
overhead_budget.destroy unless overhead_budget.month.present?
# Purge budgets outside the new beginning/ending range
unless (beginning_date..ending_date).to_a.include?(Date.new(overhead_budget.year, overhead_budget.month, 1))
overhead_budget.destroy
end
end
fixed_budgets.all.each do |fixed_budget|
# Purge un-dated budgets, should not be saved at all
fixed_budget.destroy unless fixed_budget.year.present?
fixed_budget.destroy unless fixed_budget.month.present?
# Purge budgets outside the new beginning/ending range
unless (beginning_date..ending_date).to_a.include?(Date.new(fixed_budget.year, fixed_budget.month, 1))
fixed_budget.destroy
end
end
true
end
def extend_period_to_new_end_date
return if end_date_change[0].nil? # No previous end date, so it will not have budgets
old_end_date = end_date_change[0]
last_labor_budgets = labor_budgets.all(:conditions => {:year => old_end_date.year, :month => old_end_date.month})
last_overhead_budgets = overhead_budgets.all(:conditions => {:year => old_end_date.year, :month => old_end_date.month})
last_fixed_budgets = fixed_budgets.all(:conditions => {:year => old_end_date.year, :month => old_end_date.month})
months_after_date(old_end_date.end_of_month.to_date).each do |new_period|
create_budgets_for_new_period(new_period, last_labor_budgets, last_overhead_budgets, last_fixed_budgets)
end
end
def extend_period_to_new_start_date
return if start_date_change[0].nil? # No previous start date, so it will not have budgets
old_start_date = start_date_change[0]
first_labor_budgets = labor_budgets.all(:conditions => {:year => old_start_date.year, :month => old_start_date.month})
first_overhead_budgets = overhead_budgets.all(:conditions => {:year => old_start_date.year, :month => old_start_date.month})
first_fixed_budgets = fixed_budgets.all(:conditions => {:year => old_start_date.year, :month => old_start_date.month})
months_before_date(old_start_date.beginning_of_month.to_date).each do |new_period|
create_budgets_for_new_period(new_period, first_labor_budgets, first_overhead_budgets, first_fixed_budgets)
end
end
def create_budgets_for_new_period(new_period, labor_budgets_to_copy, overhead_budgets_to_copy, fixed_budgets_to_copy)
labor_budgets_to_copy.each do |labor_budget_to_copy|
create_new_labor_budget_based_on_existing_budget(labor_budget_to_copy, 'year' => new_period.year, 'month' => new_period.month)
end
overhead_budgets_to_copy.each do |overhead_budget_to_copy|
create_new_overhead_budget_based_on_existing_budget(overhead_budget_to_copy, 'year' => new_period.year, 'month' => new_period.month)
end
fixed_budgets_to_copy.each do |fixed_budget_to_copy|
create_new_fixed_budget_based_on_existing_budget(fixed_budget_to_copy, 'year' => new_period.year, 'month' => new_period.month)
end
end
def create_new_labor_budget_based_on_existing_budget(existing_labor_budget, attributes={})
labor_budgets.create(existing_labor_budget.attributes.except('id').merge(attributes))
end
def create_new_overhead_budget_based_on_existing_budget(existing_overhead_budget, attributes={})
overhead_budgets.create(existing_overhead_budget.attributes.except('id').merge(attributes))
end
def create_new_fixed_budget_based_on_existing_budget(existing_fixed_budget, attributes={})
fixed_budgets.create(existing_fixed_budget.attributes.except('id').merge(attributes))
end
def scope_date_status(date)
if date
if within_date_range?(date)
status = :in
else
status = :out # outside of range
end
else
status = :no_date
end
status
end
def time_entries_for_date_and_issue_ids(date, issue_ids)
if issue_ids.present?
TimeEntry.all(:conditions => ["#{Issue.table_name}.id IN (:issue_ids) AND tyear = (:year) AND tmonth = (:month)",
{:issue_ids => issue_ids,
:year => date.year,
:month => date.month}
],
:include => :issue)
else
[]
end
end
end

View File

@@ -44,7 +44,7 @@
<%= show_budget_field(resource, :labor_spent, :labor_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '46%'}) %>
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-overhead'}) %>
<%= show_budget_field(resource, :fixed_spent, :fixed_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-fixed'}) %>
<%= show_budget_field(resource, :markup_spent, :markup_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-markup'}) %>
<%= show_budget_field(resource, :fixed_markup_spent, :fixed_markup_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-markup'}) %>
<%= show_budget_field(resource, :profit_spent, :profit_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-profit'}) %>
<%= show_budget_field(resource, :discount_spent, :discount_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-discount'}) %>
@@ -104,108 +104,11 @@
<td width="15%" class="manager"><%= h deliverable.manager.try(:name) %></td>
<%= format_budget_for_deliverable(deliverable, deliverable.labor_budget_spent, deliverable.labor_budget_total, :class => 'labor') %>
<%= format_budget_for_deliverable(deliverable, deliverable.overhead_spent, deliverable.overhead_budget_total, :class => 'overhead') %>
<%= format_budget_for_deliverable(deliverable, 0, 0) %><%# TODO: Release 2, Fixed Budgets %>
<%= format_budget_for_deliverable(deliverable, 0, deliverable.fixed_budget_total, :class => 'fixed') %>
<% end %>
<tr id="deliverable_details_<%= h(deliverable.id) %>" class="ign">
<td colspan="11">
<div class="expanded">
<div class="info">
<div class="title">
<%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, resource, deliverable), :class => 'icon icon-edit') %>
<%= link_to(l(:button_delete), contract_deliverable_path(@project, resource, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
</div>
<%= textilizable(deliverable, :notes) %>
<table>
<%= show_field(deliverable, :start_date, :format => :format_date, :html_options => {:class => 'deliverable-start-date'}) %>
<%= show_field(deliverable, :end_date, :format => :format_date, :html_options => {:class => 'deliverable-end-date'}) %>
</table>
</div>
<%# TODO: Release 2, skinning port %>
<div class="finance">
<table>
<thead>
<tr>
<th> </th>
<th>Spent</th>
<th>Budget</th>
<th>Hours</th>
</tr>
</thead>
<tfoot>
<tr class="fill">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tfoot>
<tbody>
<tr>
<td class="l"><a href="#"><strong>Labor</strong></a></td>
<td><%= h(format_value_field_for_contracts(deliverable.labor_budget_spent)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.labor_budget_total)) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<tr>
<td class="l"><a href="#"><strong>Overhead</strong></a></td>
<td><%= h(format_value_field_for_contracts(deliverable.overhead_spent)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.overhead_budget_total)) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<%# TODO: Release 2, Fixed %>
<%# TODO: Release 2, Markup %>
<tr>
<td class="l">Profit</td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_left)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_budget)) %></td>
<td></td>
</tr>
<tr class="total">
<td class="l"><strong>Total:</strong></td>
<td><strong><%= h(format_value_field_for_contracts(deliverable.total_spent)) %></strong></td>
<td><strong><%= h(format_value_field_for_contracts(deliverable.total)) %></strong></td>
<td><strong>TODO: Release 2</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="issue_status">
<table>
<thead>
<tr>
<th colspan="2"><%= l(:label_issue_status_plural) %></th>
</tr>
</thead>
<%# TODO: Release 2 Issue status counters
<tr>
<td>Proposed</td>
<td class="number">1</td>
</tr>
<tr>
<td>On Hold</td>
<td class="number">1</td>
</tr>
<tr>
<td>Complete</td>
<td class="number">14</td>
</tr>
<tr>
<td><strong>All</strong></td>
<td class="number">16</td>
</tr>
%>
</table>
</div>
<div class="clear"></div>
</div>
</td>
<%= render :partial => 'deliverables/details_row', :locals => {:deliverable => deliverable, :contract => resource, :period => deliverable.current_date} %>
</tr>
<% end %>
</tbody>

View File

@@ -0,0 +1,130 @@
<% period ||= nil %>
<td colspan="11" class="deliverable_details_outer_wrapper_<%= h(deliverable.id) %>">
<div class="expanded">
<div class="info">
<div class="title">
<%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, contract, deliverable), :class => 'icon icon-edit') %>
<%= link_to(l(:button_delete), contract_deliverable_path(@project, contract, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
</div>
<%= textilizable(deliverable, :notes) %>
<% if deliverable.retainer? %>
<form method="get" action="<%= contract_deliverable_path(@project, contract, deliverable, :format => 'js', :as => 'deliverable_details_row') %>">
<fieldset>
<select name="period" id="retainer_period_change_<%= h(deliverable.id) %>" class="retainer_period_change">
<%= retainer_period_options(deliverable, :selected => period) %>
</select>
</fieldset>
</form>
<% end %>
<table>
<%= show_field(deliverable, :current_period, :html_options => {:class => 'deliverable-current-period'}) if deliverable.retainer? %>
<%= show_field(deliverable, :start_date, :format => :format_date, :html_options => {:class => 'deliverable-start-date'}) %>
<%= show_field(deliverable, :end_date, :format => :format_date, :html_options => {:class => 'deliverable-end-date'}) %>
</table>
</div>
<%# TODO: Release 2, skinning port %>
<div class="finance">
<table>
<thead>
<tr>
<th> </th>
<th>Spent</th>
<th>Budget</th>
<th>Hours</th>
</tr>
</thead>
<tfoot>
<tr class="fill">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tfoot>
<tbody>
<tr>
<td class="l"><a href="#"><strong>Labor</strong></a></td>
<td class="labor_budget_spent"><%= h(format_value_field_for_contracts(deliverable.labor_budget_spent(period))) %></td>
<td class="labor_budget_total"><%= h(format_value_field_for_contracts(deliverable.labor_budget_total(period))) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<tr>
<td class="l"><a href="#"><strong>Overhead</strong></a></td>
<td class="overhead_budget_spent"><%= h(format_value_field_for_contracts(deliverable.overhead_spent(period))) %></td>
<td class="overhead_budget_total"><%= h(format_value_field_for_contracts(deliverable.overhead_budget_total(period))) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<% deliverable.fixed_budgets.by_period(period).each do |fixed_budget| %>
<% next if fixed_budget.blank_record? %>
<tr id="fixed_budget_<%= fixed_budget.id %>">
<td class="l fixed_title" title="<%= h(fixed_budget.description) %>"><%= h(fixed_budget.title) %></td>
<td class="fixed_budget_spent">0</td>
<td class="fixed_budget_total"><%= h(format_value_field_for_contracts(fixed_budget.budget)) %></td>
<td></td>
</tr>
<% end %>
<tr>
<td class="l"><%= l(:field_markup) %></td>
<td class="fixed_markup_budget_spent">0</td>
<td class="fixed_markup_budget_total"><%= h(format_value_field_for_contracts(deliverable.fixed_markup_budget_total(period))) %></td>
<td></td>
</tr>
<tr>
<td class="l">Profit</td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_left(period))) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_budget(period))) %></td>
<td></td>
</tr>
<tr class="total">
<td class="l"><strong>Total:</strong></td>
<td class="total_spent"><strong><%= h(format_value_field_for_contracts(deliverable.total_spent(period))) %></strong></td>
<td class="total"><strong><%= h(format_value_field_for_contracts(deliverable.total(period))) %></strong></td>
<td><strong>TODO: Release 2</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="issue_status">
<table>
<thead>
<tr>
<th colspan="2"><%= l(:label_issue_status_plural) %></th>
</tr>
</thead>
<%# TODO: Release 2 Issue status counters
<tr>
<td>Proposed</td>
<td class="number">1</td>
</tr>
<tr>
<td>On Hold</td>
<td class="number">1</td>
</tr>
<tr>
<td>Complete</td>
<td class="number">14</td>
</tr>
<tr>
<td><strong>All</strong></td>
<td class="number">16</td>
</tr>
%>
</table>
</div>
<div class="clear"></div>
</div>
</td>

View File

@@ -0,0 +1,101 @@
<% form.inputs :name => label, :class => "deliverable-finances #{fieldset_class}" do %>
<li style="display:none;" id='retainer-finances-message'>
<%= content_tag(:p, l(:text_retainer_monthly_message)) %>
</li>
<li class="numeric optional">
<%= content_tag(:label, l(:field_labor)) %>
<table id="deliverable-labor" class="deliverable_finance_table">
<% form.fields_for :labor_budgets, labor_budgets do |labor_budget| %>
<%= labor_budget.hidden_field(:year) %>
<%= labor_budget.hidden_field(:month) %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:hours, l(:text_short_hours)) %></p>
<%= labor_budget.text_field(:hours, :value => format_deliverable_value_fields(labor_budget.object.hours), :size => 10) %>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= labor_budget.text_field(:budget, :value => format_deliverable_value_fields(labor_budget.object.budget), :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
</table>
</li>
<li class="numeric optional">
<%= content_tag(:label, l(:field_overhead)) %>
<table id="deliverable-overhead" class="deliverable_finance_table">
<% form.fields_for :overhead_budgets, overhead_budgets do |overhead_budget| %>
<%= overhead_budget.hidden_field(:year) %>
<%= overhead_budget.hidden_field(:month) %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:hours, l(:text_short_hours)) %></p>
<%= overhead_budget.text_field(:hours, :value => format_deliverable_value_fields(overhead_budget.object.hours),:size => 10) %>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= overhead_budget.text_field(:budget, :value => format_deliverable_value_fields(overhead_budget.object.budget), :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
</table>
</li>
<li class="numeric optional">
<div id="deliverable-fixed" class="fixed-item-form">
<label for="contract_discount">Fixed</label>
<% form.fields_for :fixed_budgets, fixed_budgets do |fixed_budget| %>
<%= fixed_budget.hidden_field(:year) %>
<%= fixed_budget.hidden_field(:month) %>
<p class="inline-hints" style="display:none;"><%= fixed_budget.label(:title, l(:field_title))%></p>
<%= fixed_budget.text_field(:title) %>
<p class="inline-hints">
<%= fixed_budget.label(:budget, l(:field_budget), :style => 'display: none;')%> <%# Hidden label :| %>
<%= l(:text_dollar_sign) %>
</p>
<%= fixed_budget.text_field(:budget) %>
<p class="inline-hints">
<%= fixed_budget.label(:markup, l(:field_markup), :style => 'display: none;')%> <%# Hidden label :| %>
<%= l(:field_discount_hint) %>
</p>
<%= fixed_budget.text_field(:markup) %>
<p class="inline-hints" style="display:none;"><%= fixed_budget.label(:description, l(:field_description), :for => "fixed-description#{fixed_budget.object.object_id}")%></p>
<%= fixed_budget.text_area(:description, :class => 'wiki-edit', :rows => '5', :id => "fixed-description#{fixed_budget.object.object_id}") %>
<%= wikitoolbar_for "fixed-description#{fixed_budget.object.object_id}" %>
<p>Todo: Add button (Release 3)</p>
<% end %>
</div>
</li>
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'}, :hint => l(:text_dollar_sign) %>
<% end %>

View File

@@ -1,14 +1,28 @@
<%= javascript_tag("var i18nStartDateEmpty = '#{l(:text_start_date_empty)}'") %>
<%= javascript_tag("var i18nEndDateEmpty = '#{l(:text_end_date_empty)}'") %>
<%= javascript_tag("var i18nChangedPeriodMessage = '#{l(:text_changed_period_message)}'") %>
<div class="box tabular">
<% form.inputs :name => l(:text_deliverable_details_legend) do %>
<% form.inputs :name => l(:text_deliverable_details_legend), :id => 'deliverable-details' do %>
<%# Used by jquery to check if this is a new or existing record %>
<%= hidden_field_tag('deliverable_id', h(resource.id), :id => 'deliverable_stored_id') %>
<%= form.input :title, :required => true %>
<% if resource.new_record? %>
<%= form.input(:type, :required => true, :as => :select, :collection => [["Fixed", "FixedDeliverable"],["Hourly", "HourlyDeliverable"]], :include_blank => false, :input_html => {:class => 'type'}) %>
<li class="select required" id="deliverable_type_input">
<%= form.label(:type, l(:field_type)) %>
<%= form.select(:type, Deliverable.valid_types_to_select, {:include_blank => false}, {:class => 'type'}) %>
</li>
<% else %>
<%= form.input :type, :as => :hidden, :class => 'type' %>
<% end %>
<%= form.input :manager, :required => true, :collection => @project.users.sort %>
<%= form.input :start_date, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('deliverable_start_date') %>
<%= form.input :end_date, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('deliverable_end_date') %>
<%= form.input :start_date, :as => :string, :input_html => {:size => 10, :class => 'start-date'}, :hint => calendar_for('deliverable_start_date') %>
<%= hidden_field_tag('deliverable_stored_start_date', h(resource.start_date), :id => 'deliverable_stored_start_date') %>
<%= form.input :end_date, :as => :string, :input_html => {:size => 10, :class => 'end-date'}, :hint => calendar_for('deliverable_end_date') %>
<%= hidden_field_tag('deliverable_stored_end_date', h(resource.end_date), :id => 'deliverable_stored_end_date') %>
<%= form.input :notes, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
<% unless resource.new_record? %>
@@ -23,64 +37,16 @@
<% end %>
<% end %>
<% form.inputs :name => l(:text_deliverable_finances), :id => 'deliverable-finances' do %>
<li class="numeric optional">
<%= content_tag(:label, l(:field_labor)) %>
<table id="deliverable-labor" class="deliverable_finance_table">
<% form.fields_for :labor_budgets do |labor_budget| %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:hours, l(:text_short_hours)) %></p>
<%= labor_budget.text_field(:hours, :value => format_deliverable_value_fields(labor_budget.object.hours), :size => 10) %>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= labor_budget.text_field(:budget, :value => format_deliverable_value_fields(labor_budget.object.budget), :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
</table>
</li>
<li class="numeric optional">
<%= content_tag(:label, l(:field_overhead)) %>
<table id="deliverable-overhead" class="deliverable_finance_table">
<% form.fields_for :overhead_budgets do |overhead_budget| %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:hours, l(:text_short_hours)) %></p>
<%= overhead_budget.text_field(:hours, :value => format_deliverable_value_fields(overhead_budget.object.hours),:size => 10) %>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= overhead_budget.text_field(:budget, :value => format_deliverable_value_fields(overhead_budget.object.budget), :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
</table>
</li>
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'}, :hint => l(:text_dollar_sign) %>
<% if resource.retainer? && resource.respond_to?(:months) %>
<% if resource.months.present? %>
<% resource.months.each do |month| %>
<%= render :partial => 'finance_form', :locals => {:form => form, :labor_budgets => resource.labor_budgets_for_date(month), :overhead_budgets => resource.overhead_budgets_for_date(month), :fixed_budgets => resource.fixed_budgets_for_date(month), :label => l(:text_deliverable_finances_date, :date => month.strftime("%B, %Y")), :fieldset_class => 'date-' + month.strftime('%Y-%m') } %>
<% end %>
<% else %>
<%= content_tag(:p, l(:text_missing_period), :class => 'nodata') %>
<% end %>
<% else %>
<%= render :partial => 'finance_form', :locals => {:form => form, :labor_budgets => resource.labor_budgets, :overhead_budgets => resource.overhead_budgets, :fixed_budgets => resource.fixed_budgets, :label => l(:text_deliverable_finances), :fieldset_class => '' } %>
<% end %>
</div>

View File

@@ -1,4 +1,6 @@
jQuery(function($) {
$("#ajax-indicator").ajaxStart(function(){ $(this).show(); });
$("#ajax-indicator").ajaxStop(function(){ $(this).hide(); });
var right_align = $('#contract-terms .finance tr td:nth-child ~ td, .c_overview table.right tr td:nth-child ~ td, #deliverables table tr.click td:nth-child(5) ~ td, .deliverable_finance_table tr.aright td:nth-child ~ td');
@@ -22,16 +24,45 @@ jQuery(function($) {
$(this).toggleClass('alt');
});
showDeliverableTotal = function() {
$('.deliverable_total_input').show();
},
hideDeliverableTotal = function() {
$('.deliverable_total_input').
children('input').val('').end().
hide();
},
showDeliverableFrequency = function() {
$('#deliverable_frequency').show();
},
hideDeliverableFrequency = function() {
$('#deliverable_frequency').hide();
},
toggleSpecificDeliverableFields = function(form) {
var deliverableType = form.find('.type').val();
if (deliverableType == 'FixedDeliverable') {
$('.deliverable_total_input').show();
} else {
$('.deliverable_total_input').
children('input').val('').end().
hide();
}
showDeliverableTotal();
hideDeliverableFrequency();
$('#retainer-finances-message').hide();
} else if(deliverableType == "HourlyDeliverable") {
hideDeliverableTotal();
hideDeliverableFrequency();
$('#retainer-finances-message').hide();
} else if(deliverableType == "RetainerDeliverable") {
hideDeliverableTotal();
showDeliverableFrequency();
if ($('form.deliverable #deliverable_stored_id').val() == '') {
$('#retainer-finances-message').show();
} else {
$('#retainer-finances-message').hide();
}
}
},
toggleSpecificDeliverableFields($('form.deliverable'));
@@ -39,6 +70,34 @@ jQuery(function($) {
$('select#deliverable_type').change(function() {
toggleSpecificDeliverableFields($('form.deliverable'));
});
$('form.deliverable').submit(function() {
var deliverableType = $('form.deliverable').find('.type').val();
if (deliverableType == 'RetainerDeliverable') {
if ($('form.deliverable .start-date[value!=""]').length == 0) {
return confirm(i18nStartDateEmpty);
}
if ($('form.deliverable .end-date[value!=""]').length == 0) {
return confirm(i18nEndDateEmpty);
}
if ($('form.deliverable #deliverable_stored_id').val() != '') {
if ($('form.deliverable .start-date').val() != $('#deliverable_stored_start_date').val()) {
return confirm(i18nChangedPeriodMessage);
}
if ($('form.deliverable .end-date').val() != $('#deliverable_stored_end_date').val()) {
return confirm(i18nChangedPeriodMessage);
}
}
}
});
$('select.retainer_period_change').live('change', function() {
var deliverable_url = $(this).closest('form').attr('action');
$(this).closest('tr').load(deliverable_url, this.form.serialize());
});
});
/* Jquery Table Expander Plugin */

View File

@@ -28,6 +28,7 @@ en:
field_feature_sign_off: Feature Sign Off
field_warranty_sign_off: Warranty Sign Off
text_deliverable_finances: Deliverable Finances
text_deliverable_finances_date: "Deliverable Finances - {{date}}"
text_short_hours: hrs
text_dollar_sign: '$'
field_client_point_of_contact: "Point of Contact"
@@ -39,8 +40,8 @@ en:
field_fixed_spent: "Fixed"
field_labor_budget: "Labor"
field_labor_spent: "Labor"
field_markup_budget: "Markup"
field_markup_spent: "Markup"
field_fixed_markup_budget: "Markup"
field_fixed_markup_spent: "Markup"
field_overhead_budget: "Overhead"
field_overhead_spent: "Overhead"
field_profit_budget: "Profit"
@@ -60,3 +61,14 @@ en:
field_deliverable_title: "Deliverable"
field_contract_name: "Contract"
field_contract: "Contract"
text_start_date_empty: "The start date is empty. If this form is submitted, no budget items will be created."
text_end_date_empty: "The end date is empty. If this form is submitted, no budget items will be created."
text_missing_period: "This deliverable is missing a date range so it cannot have budget items. Please save start and end dates before adding any budget items."
text_changed_period_message: "The period for this deliverable has been changed. Would you like to expand/shrink the Deliverable Finances?"
field_current_period: "Current period"
text_retainer_monthly_message: "For a single month"
text_flash_deliverable_created: "Deliverable: {{name}} was successfully created."
text_flash_deliverable_updated: "Deliverable: {{name}} was successfully updated."
text_flash_deliverable_destroyed: "Deliverable: {{name}} was successfully destroyed."
field_budget: Budget
field_markup: Markup

View File

@@ -0,0 +1,9 @@
class AddFrequencyToDeliverables < ActiveRecord::Migration
def self.up
add_column :deliverables, :frequency, :string
end
def self.down
remove_column :deliverables, :frequency
end
end

View File

@@ -0,0 +1,14 @@
class AddYearAndMonthToLaborBudgets < ActiveRecord::Migration
def self.up
add_column :labor_budgets, :year, :integer
add_index :labor_budgets, :year
add_column :labor_budgets, :month, :integer
add_index :labor_budgets, :month
end
def self.down
remove_column :labor_budgets, :year
remove_column :labor_budgets, :month
end
end

View File

@@ -0,0 +1,14 @@
class AddYearAndMonthToOverheadBudgets < ActiveRecord::Migration
def self.up
add_column :overhead_budgets, :year, :integer
add_index :overhead_budgets, :year
add_column :overhead_budgets, :month, :integer
add_index :overhead_budgets, :month
end
def self.down
remove_column :overhead_budgets, :year
remove_column :overhead_budgets, :month
end
end

View File

@@ -0,0 +1,9 @@
class RemoveFrequencyFromDeliverables < ActiveRecord::Migration
def self.up
remove_column :deliverables, :frequency
end
def self.down
add_column :deliverables, :frequency, :string
end
end

View File

@@ -0,0 +1,19 @@
class CreateFixedBudgets < ActiveRecord::Migration
def self.up
create_table :fixed_budgets do |t|
t.string :title
t.decimal :budget, :precision => 15, :scale => 4
t.string :markup
t.text :description
t.references :deliverable
t.timestamps
end
add_index :fixed_budgets, :deliverable_id
end
def self.down
drop_table :fixed_budgets
end
end

View File

@@ -0,0 +1,14 @@
class AddYearAndMonthToFixedBudgets < ActiveRecord::Migration
def self.up
add_column :fixed_budgets, :year, :integer
add_index :fixed_budgets, :year
add_column :fixed_budgets, :month, :integer
add_index :fixed_budgets, :month
end
def self.down
remove_column :fixed_budgets, :year
remove_column :fixed_budgets, :month
end
end

View File

@@ -209,4 +209,101 @@ class ContractsShowTest < ActionController::IntegrationTest
end
end
should "show the total fixed budget for a Deliverable" do
@manager = User.generate!
@deliverable1 = FixedDeliverable.generate!(:contract => @contract, :manager => @manager)
FixedBudget.generate!(:deliverable => @deliverable1, :budget => '$1,000', :markup => '$100')
FixedBudget.generate!(:deliverable => @deliverable1, :budget => '$2,000', :markup => '200%')
visit_contract_page(@contract)
assert_select "table#deliverables" do
assert_select "td.fixed", :text => /3,000/
end
end
should "QUESTION: show the total fixed budget spent for a Deliverable"
should "show each fixed budget item in the details for the Deliverable" do
@manager = User.generate!
@deliverable1 = FixedDeliverable.generate!(:contract => @contract, :manager => @manager)
@budget1 = FixedBudget.generate!(:deliverable => @deliverable1, :title => 'Item 1', :budget => '$1,000', :markup => '$100')
@budget2 = FixedBudget.generate!(:deliverable => @deliverable1, :title => 'Item 2', :budget => '$2,000', :markup => '200%')
visit_contract_page(@contract)
assert_select "table#deliverables" do
assert_select "#deliverable_details_#{@deliverable1.id}" do
assert_select "tr#fixed_budget_#{@budget1.id}" do
assert_select 'td.fixed_title', :text => /#{@budget1.title}/
assert_select 'td.fixed_budget_spent', :text => '0'
assert_select 'td.fixed_budget_total', :text => '1,000'
end
assert_select "tr#fixed_budget_#{@budget2.id}" do
assert_select 'td.fixed_title', :text => /#{@budget2.title}/
assert_select 'td.fixed_budget_spent', :text => '0'
assert_select 'td.fixed_budget_total', :text => '2,000'
end
end
end
end
should "show the total fixed markup budget in the details for the Deliverable" do
@manager = User.generate!
@deliverable1 = FixedDeliverable.generate!(:contract => @contract, :manager => @manager)
@budget1 = FixedBudget.generate!(:deliverable => @deliverable1, :title => 'Item 1', :budget => '$1,000', :markup => '$100')
@budget2 = FixedBudget.generate!(:deliverable => @deliverable1, :title => 'Item 2', :budget => '$2,000', :markup => '200%')
visit_contract_page(@contract)
assert_select "table#deliverables" do
assert_select "#deliverable_details_#{@deliverable1.id}" do
assert_select 'td.fixed_markup_budget_spent', :text => '0'
assert_select 'td.fixed_markup_budget_total', :text => '4,100'
end
end
end
should "show the current period for a Retainer" do
today_mock = Date.new(2010,2,15)
Date.stubs(:today).returns(today_mock)
@manager = User.generate!
@retainer_deliverable = RetainerDeliverable.generate!(:contract => @contract, :manager => @manager, :title => "Retainer", :start_date => '2010-01-01', :end_date => '2010-03-31')
visit_contract_page(@contract)
assert_select "table#deliverables" do
assert_select "#deliverable_details_#{@retainer_deliverable.id}" do
assert_select ".deliverable-current-period", :text => /February 2010/i
end
end
end
should "show a selector for changing the currently shown Retainer's period" do
@manager = User.generate!
@retainer_deliverable = RetainerDeliverable.generate!(:contract => @contract, :manager => @manager, :title => "Retainer", :start_date => '2010-01-01', :end_date => '2010-03-31')
visit_contract_page(@contract)
assert_select "table#deliverables" do
assert_select "#deliverable_details_#{@retainer_deliverable.id}" do
assert_select "select#retainer_period_change_#{@retainer_deliverable.id}" do
assert_select "option", /All/
assert_select "option", /January 2010/
assert_select "option", /February 2010/
assert_select "option", /March 2010/
end
end
end
end
end

View File

@@ -0,0 +1,49 @@
require 'test_helper'
class DeliverableDetailsShowTest < ActionController::IntegrationTest
include Redmine::I18n
def setup
@project = Project.generate!(:identifier => 'main').reload
@contract = Contract.generate!(:project => @project, :billable_rate => 10)
@manager = User.generate!
@deliverable1 = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer", :start_date => '2010-01-01', :end_date => '2010-03-31')
@deliverable1.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable1.overhead_budgets << OverheadBudget.spawn(:budget => 200, :hours => 10)
@deliverable1.save!
end
context "for a JS request" do
should "render the details for the deliverable" do
visit "/projects/#{@project.id}/contracts/#{@contract.id}/deliverables/#{@deliverable1.id}", :get, {:format => 'js', :as => 'deliverable_details_row'}
assert_response :success
assert_select ".deliverable_details_outer_wrapper_#{@deliverable1.id}"
assert_select "table#deliverables", :count => 0 # Not the full table
assert_select "tr#deliverable_details_#{@deliverable1.id}", :count => 0 # Not the wrapper tr
end
should "filter the details based on the period" do
assert_equal 300, @deliverable1.labor_budget_total
assert_equal 600, @deliverable1.overhead_budget_total
assert_equal 300, @deliverable1.total # Contract rate * 30 hours (labor)
visit "/projects/#{@project.id}/contracts/#{@contract.id}/deliverables/#{@deliverable1.id}", :get, {:format => 'js', :as => 'deliverable_details_row', :period => '2010-02'}
assert_response :success
assert_select ".deliverable_details_outer_wrapper_#{@deliverable1.id}" do
assert_select "td.labor_budget_total", '100'
assert_select "td.overhead_budget_total", '200'
assert_select "td.total", '100'
assert_select "select.retainer_period_change" do
assert_select "option[selected=selected]", "February 2010"
end
end
end
end
end

View File

@@ -27,10 +27,11 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_select "select#fixed_deliverable_type", :count => 0 # Not editable
assert js("jQuery('#fixed_deliverable_total_input').is(':visible')"), "Total is hidden when it should be visible"
fill_in "Title", :with => 'An updated title'
check "Feature Sign Off"
check "Warranty Sign Off"
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
check "Feature Sign Off"
check "Warranty Sign Off"
end
click_button "Save"
assert_response :success
@@ -56,9 +57,11 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_select "select#hourly_deliverable_type", :count => 0 # Not editable
assert js("jQuery('#hourly_deliverable_total_input').is(':hidden')"), "Total is visible when it should be hidden"
fill_in "Title", :with => 'An updated title'
check "Feature Sign Off"
check "Warranty Sign Off"
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
check "Feature Sign Off"
check "Warranty Sign Off"
end
within("#deliverable-labor") do
fill_in "hrs", :with => '20'
@@ -90,4 +93,361 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal 10, @overhead_budget.hours
assert_equal 1000.0, @overhead_budget.budget
end
should "show allow editing the Deliverable Finances section for each Retainer period" do
@retainer_deliverable = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer")
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.fixed_budgets << @fixed_budget = FixedBudget.spawn(:deliverable => @retainer_deliverable, :title => 'Printing supplies', :budget => 100, :markup => 0)
@retainer_deliverable.start_date = '2010-01-01'
@retainer_deliverable.end_date = '2010-12-31'
@retainer_deliverable.save!
assert_equal 12, @retainer_deliverable.months.length
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@retainer_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
assert_select 'fieldset.deliverable-finances', :count => 12
within ".date-2010-01" do
within "#deliverable-labor" do
fill_in "hrs", :with => '20'
fill_in "$", :with => '2000'
end
within "#deliverable-overhead" do
fill_in "hrs", :with => '100'
fill_in "$", :with => '100'
end
within "#deliverable-fixed" do
fill_in "title", :with => 'Flight to NYC'
fill_in "budget", :with => '$600'
fill_in "markup", :with => '50%'
fill_in "description", :with => 'Need to fly to NYC for the week'
end
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@labor_budgets = @retainer_deliverable.reload.labor_budgets
assert_equal 12, @labor_budgets.length
@labor_budgets.each do |labor_budget|
if labor_budget.year == 2010 && labor_budget.month == 1
# Specific month's budget updated?
assert_equal 20.0, labor_budget.hours
assert_equal 2000.0, labor_budget.budget
else
assert_equal 10.0, labor_budget.hours
assert_equal 1000.0, labor_budget.budget
end
end
@overhead_budgets = @retainer_deliverable.reload.overhead_budgets
assert_equal 12, @overhead_budgets.length
@overhead_budgets.each do |overhead_budget|
if overhead_budget.year == 2010 && overhead_budget.month == 1
# Specific month's budget updated?
assert_equal 100.0, overhead_budget.hours
assert_equal 100.0, overhead_budget.budget
else
assert_equal 10.0, overhead_budget.hours
assert_equal 1000.0, overhead_budget.budget
end
end
@fixed_budgets = @retainer_deliverable.reload.fixed_budgets
assert_equal 12, @fixed_budgets.length
@fixed_budgets.each do |fixed_budget|
if fixed_budget.year == 2010 && fixed_budget.month == 1
# Specific month's budget updated?
assert_equal 600, fixed_budget.budget
assert_equal '50%', fixed_budget.markup
assert_equal 300, fixed_budget.markup_value
else
assert_equal 100, fixed_budget.budget
assert_equal '0', fixed_budget.markup
end
end
end
should "allow extending a Retainer's start and end months" do
labor_budget_hours_1 = 10
labor_budget_hours_2 = 20
labor_budget_amount_1 = 1000
labor_budget_amount_2 = 2000
overhead_budget_hours_1 = 10
overhead_budget_hours_2 = 20
overhead_budget_amount_1 = 1000
overhead_budget_amount_2 = 2000
@retainer_deliverable = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer")
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => labor_budget_amount_1, :hours => labor_budget_hours_1)
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => labor_budget_amount_2, :hours => labor_budget_hours_2)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => overhead_budget_amount_1, :hours => overhead_budget_hours_1)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => overhead_budget_amount_2, :hours => overhead_budget_hours_2)
@retainer_deliverable.fixed_budgets << @fixed_budget = FixedBudget.spawn(:deliverable => @retainer_deliverable, :title => 'Printing supplies', :budget => 100, :markup => 0)
@retainer_deliverable.start_date = '2010-01-01'
@retainer_deliverable.end_date = '2010-12-31'
@retainer_deliverable.save!
assert_equal 12, @retainer_deliverable.months.length
assert_equal 24, @retainer_deliverable.reload.labor_budgets.count # 12 months * 2 records
assert_equal 24, @retainer_deliverable.reload.overhead_budgets.count # 12 months * 2 records
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@retainer_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
# Extend the period
fill_in "Start", :with => '2009-01-13' # 12 new months
fill_in "End Date", :with => '2011-12-01' # 12 new months
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@retainer_deliverable.reload
assert_equal 36, @retainer_deliverable.months.length
@labor_budgets = @retainer_deliverable.reload.labor_budgets
assert_equal 72, @labor_budgets.length # 36 months * 2 records
@labor_budgets_for_2009 = @labor_budgets.select {|l| l.year == 2009 }
@labor_budgets_for_2010 = @labor_budgets.select {|l| l.year == 2010 }
@labor_budgets_for_2011 = @labor_budgets.select {|l| l.year == 2011 }
assert_equal 24, @labor_budgets_for_2009.length
assert_equal 24, @labor_budgets_for_2010.length
assert_equal 24, @labor_budgets_for_2011.length
@labor_budgets_for_2009.each do |labor_budget|
assert_equal 2009, labor_budget.year
assert [labor_budget_hours_1, labor_budget_hours_2].include?(labor_budget.hours), "Extended labor budget hours not matching template budget"
assert [labor_budget_amount_1, labor_budget_amount_2].include?(labor_budget.budget), "Extended labor budget dollars not matching template budget"
end
@labor_budgets_for_2010.each do |labor_budget|
assert_equal 2010, labor_budget.year
assert [labor_budget_hours_1, labor_budget_hours_2].include?(labor_budget.hours), "Extended labor budget hours not matching template budget"
assert [labor_budget_amount_1, labor_budget_amount_2].include?(labor_budget.budget), "Extended labor budget dollars not matching template budget"
end
@labor_budgets_for_2011.each do |labor_budget|
assert_equal 2011, labor_budget.year
assert [labor_budget_hours_1, labor_budget_hours_2].include?(labor_budget.hours), "Extended labor budget hours not matching template budget"
assert [labor_budget_amount_1, labor_budget_amount_2].include?(labor_budget.budget), "Extended labor budget dollars not matching template budget"
end
@overhead_budgets = @retainer_deliverable.reload.overhead_budgets
assert_equal 72, @overhead_budgets.length # 36 months * 2 records
@overhead_budgets_for_2009 = @overhead_budgets.select {|l| l.year == 2009 }
@overhead_budgets_for_2010 = @overhead_budgets.select {|l| l.year == 2010 }
@overhead_budgets_for_2011 = @overhead_budgets.select {|l| l.year == 2011 }
assert_equal 24, @overhead_budgets_for_2009.length
assert_equal 24, @overhead_budgets_for_2010.length
assert_equal 24, @overhead_budgets_for_2011.length
@overhead_budgets_for_2009.each do |overhead_budget|
assert_equal 2009, overhead_budget.year
assert [overhead_budget_hours_1, overhead_budget_hours_2].include?(overhead_budget.hours), "Extended overhead budget hours not matching template budget"
assert [overhead_budget_amount_1, overhead_budget_amount_2].include?(overhead_budget.budget), "Extended overhead budget dollars not matching template budget"
end
@overhead_budgets_for_2010.each do |overhead_budget|
assert_equal 2010, overhead_budget.year
assert [overhead_budget_hours_1, overhead_budget_hours_2].include?(overhead_budget.hours), "Extended overhead budget hours not matching template budget"
assert [overhead_budget_amount_1, overhead_budget_amount_2].include?(overhead_budget.budget), "Extended overhead budget dollars not matching template budget"
end
@overhead_budgets_for_2011.each do |overhead_budget|
assert_equal 2011, overhead_budget.year
assert [overhead_budget_hours_1, overhead_budget_hours_2].include?(overhead_budget.hours), "Extended overhead budget hours not matching template budget"
assert [overhead_budget_amount_1, overhead_budget_amount_2].include?(overhead_budget.budget), "Extended overhead budget dollars not matching template budget"
end
@fixed_budgets = @retainer_deliverable.reload.fixed_budgets
assert_equal 36, @fixed_budgets.length # 36 months * 1 record
@fixed_budgets_for_2009 = @fixed_budgets.select {|l| l.year == 2009 }
@fixed_budgets_for_2010 = @fixed_budgets.select {|l| l.year == 2010 }
@fixed_budgets_for_2011 = @fixed_budgets.select {|l| l.year == 2011 }
assert_equal 12, @fixed_budgets_for_2009.length
assert_equal 12, @fixed_budgets_for_2010.length
assert_equal 12, @fixed_budgets_for_2011.length
end
should "allow shrinking a Retainer's start and end months" do
@retainer_deliverable = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer")
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => 2000, :hours => 20)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => 2000, :hours => 20)
@retainer_deliverable.fixed_budgets << @fixed_budget = FixedBudget.spawn(:deliverable => @retainer_deliverable, :title => 'Printing supplies', :budget => 100, :markup => 0)
@retainer_deliverable.start_date = '2010-01-01'
@retainer_deliverable.end_date = '2010-12-31'
@retainer_deliverable.save!
assert_equal 12, @retainer_deliverable.months.length
assert_equal 24, @retainer_deliverable.reload.labor_budgets.count # 12 months * 2 records
assert_equal 24, @retainer_deliverable.reload.overhead_budgets.count # 12 months * 2 records
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@retainer_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
# Shrink the period to only 6 months
fill_in "Start", :with => '2010-02-13'
fill_in "End Date", :with => '2010-07-01'
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@retainer_deliverable.reload
assert_equal 6, @retainer_deliverable.months.length
@labor_budgets = @retainer_deliverable.reload.labor_budgets
assert_equal 12, @labor_budgets.length # 6 months * 2 records
@overhead_budgets = @retainer_deliverable.reload.overhead_budgets
assert_equal 12, @overhead_budgets.length # 6 months * 2 records
@fixed_budgets = @retainer_deliverable.reload.fixed_budgets
assert_equal 6, @fixed_budgets.length # 6 months * 1 records
end
should "allow editing a Retainer's start and end months inside the current period" do
@retainer_deliverable = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer")
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.labor_budgets << @labor_budget = LaborBudget.spawn(:deliverable => @retainer_deliverable, :budget => 2000, :hours => 20)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => 1000, :hours => 10)
@retainer_deliverable.overhead_budgets << @overhead_budget = OverheadBudget.spawn(:deliverable => @retainer_deliverable, :budget => 2000, :hours => 20)
@retainer_deliverable.start_date = '2010-01-01'
@retainer_deliverable.end_date = '2010-12-31'
@retainer_deliverable.save!
assert_equal 12, @retainer_deliverable.months.length
assert_equal 24, @retainer_deliverable.reload.labor_budgets.count # 12 months * 2 records
assert_equal 24, @retainer_deliverable.reload.overhead_budgets.count # 12 months * 2 records
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@retainer_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
# Edit the dates without changing the period
fill_in "Start", :with => '2010-01-13'
fill_in "End Date", :with => '2010-12-01'
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@retainer_deliverable.reload
assert_equal 12, @retainer_deliverable.months.length
@labor_budgets = @retainer_deliverable.reload.labor_budgets
assert_equal 24, @labor_budgets.length # 12 months * 2 records
@overhead_budgets = @retainer_deliverable.reload.overhead_budgets
assert_equal 24, @overhead_budgets.length # 12 months * 2 records
end
should "show empty budget fields for a Retainer that has missing budgets" do
@retainer_deliverable = RetainerDeliverable.spawn(:contract => @contract, :manager => @manager, :title => "Retainer")
@retainer_deliverable.start_date = '2010-01-01'
@retainer_deliverable.end_date = '2010-03-31'
@retainer_deliverable.save!
assert_equal 3, @retainer_deliverable.months.length
assert_equal 0, @retainer_deliverable.reload.labor_budgets.count # 3 months * 0 records
assert_equal 0, @retainer_deliverable.reload.overhead_budgets.count # 3 months * 0 records
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@retainer_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
# Should show inputs:
# * labor hidden year
# * labor hidden month
# * labor hours
# * labor amount
# * overhead hidden year
# * overhead hidden month
# * overhead hours
# * overhead amount
# * fixed hidden year
# * fixed hidden month
# * fixed title
# * fixed budget
# * fixed markup
# * total (hidden)
assert_select ".date-2010-01" do
assert_select "input", :count => 14
assert_select "textarea.wiki-edit", :count => 1 # Fixed description
end
within ".date-2010-01" do
within "#deliverable-labor" do
fill_in "hrs", :with => '20'
fill_in "$", :with => '2000'
end
within "#deliverable-overhead" do
fill_in "hrs", :with => '100'
fill_in "$", :with => '100'
end
within "#deliverable-fixed" do
fill_in "title", :with => 'Flight to NYC'
fill_in "budget", :with => '$600'
fill_in "markup", :with => '50%'
fill_in "description", :with => 'Need to fly to NYC for the week'
end
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@retainer_deliverable.reload
assert_equal 3, @retainer_deliverable.labor_budgets.count
assert_equal [20, nil, nil], @retainer_deliverable.labor_budgets.collect(&:hours)
assert_equal [2000, nil, nil], @retainer_deliverable.labor_budgets.collect(&:budget)
assert_equal 3, @retainer_deliverable.overhead_budgets.count
assert_equal [100, nil, nil], @retainer_deliverable.overhead_budgets.collect(&:hours)
assert_equal [100, nil, nil], @retainer_deliverable.overhead_budgets.collect(&:budget)
assert_equal 3, @retainer_deliverable.fixed_budgets.count
assert_equal [600, nil, nil], @retainer_deliverable.fixed_budgets.collect(&:budget)
end
end

View File

@@ -50,12 +50,15 @@ class DeliverablesNewTest < ActionController::IntegrationTest
click_link 'Add New'
assert_response :success
fill_in "Title", :with => 'A New Deliverable'
select "Fixed", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Fixed", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
end
fill_in "Total", :with => '1,000.00'
# TODO: webrat can't trigger DOM events so it can't appear
# assert js("jQuery('#deliverable_total').is(':visible')"), "Total is hidden when it should be visible"
@@ -84,17 +87,19 @@ class DeliverablesNewTest < ActionController::IntegrationTest
click_link 'Add New'
assert_response :success
fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
end
fill_in "Total", :with => '1,000.00'
# Hide and clear the total
assert js("jQuery('#deliverable_total_input').is(':hidden')"),
"Total is visible when it should be hidden"
# # Hide and clear the total
# assert js("jQuery('#deliverable_total_input').is(':hidden')"),
# "Total is visible when it should be hidden"
click_button "Save"
@@ -111,7 +116,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
end
should "create new budget items for the deliverables" do
should "create a new Retainer deliverable" do
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
@@ -120,13 +125,15 @@ class DeliverablesNewTest < ActionController::IntegrationTest
click_link 'Add New'
assert_response :success
fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Retainer", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
end
within("#deliverable-labor") do
fill_in "hrs", :with => '20'
fill_in "$", :with => '$2,000'
@@ -142,6 +149,88 @@ class DeliverablesNewTest < ActionController::IntegrationTest
assert_response :success
assert_template 'contracts/show'
@deliverable = Deliverable.last
assert_equal "A New Deliverable", @deliverable.title
assert_equal @contract, @deliverable.contract
assert_equal "RetainerDeliverable", @deliverable.type
assert_equal '2010-01-01', @deliverable.start_date.to_s
assert_equal '2010-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager
# Budget items, one per month
labor_budgets = @deliverable.labor_budgets
assert_equal 12, labor_budgets.length
labor_budgets.each do |budget|
assert_equal 2000, budget.budget
assert_equal 20, budget.hours
end
# Budget dates
labor_budgets.each do |budget|
assert_equal 2010, budget.year
end
(1..12).each do |month_number|
assert_equal 1, labor_budgets.select {|b| b.month == month_number}.length
end
overhead_budgets = @deliverable.overhead_budgets
assert_equal 12, overhead_budgets.length
overhead_budgets.each do |budget|
assert_equal 1000, budget.budget
assert_equal 10, budget.hours
end
# Budget dates
overhead_budgets.each do |budget|
assert_equal 2010, budget.year
end
(1..12).each do |month_number|
assert_equal 1, overhead_budgets.select {|b| b.month == month_number}.length
end
end
should "create new budget items for the deliverables" do
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
visit_contract_page(@contract)
click_link 'Add New'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
fill_in "Notes", :with => 'Some notes on the deliverable'
end
within("#deliverable-labor") do
fill_in "hrs", :with => '20'
fill_in "$", :with => '$2,000'
end
within("#deliverable-overhead") do
fill_in "hrs", :with => '10'
fill_in "$", :with => '$1,000'
end
within("#deliverable-fixed") do
fill_in "title", :with => 'Flight to NYC'
fill_in "budget", :with => '$600'
fill_in "markup", :with => '50%'
fill_in "description", :with => 'Need to fly to NYC for the week'
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
@deliverable = Deliverable.last
assert_equal 1, @deliverable.labor_budgets.count
@@ -153,6 +242,14 @@ class DeliverablesNewTest < ActionController::IntegrationTest
@overhead_budget = @deliverable.overhead_budgets.first
assert_equal 10, @overhead_budget.hours
assert_equal 1000.0, @overhead_budget.budget
assert_equal 1, @deliverable.fixed_budgets.count
@fixed_budget = @deliverable.fixed_budgets.first
assert_equal "Flight to NYC", @fixed_budget.title
assert_equal 600, @fixed_budget.budget
assert_equal "50%", @fixed_budget.markup
assert_equal 300, @fixed_budget.markup_value # 600 * 50%
end
end

View File

@@ -26,8 +26,6 @@ class ContractTest < ActiveSupport::TestCase
end
end
should "QUESTION: name be unique"
should "default executed to false" do
@contract = Contract.new
@@ -256,4 +254,38 @@ class ContractTest < ActiveSupport::TestCase
end
end
context "#fixed_budget" do
should "sum all fixed budget amounts on the Deliverables" do
contract = Contract.generate!
contract.deliverables << @deliverable_1 = FixedDeliverable.generate!
FixedBudget.generate!(:deliverable => @deliverable_1, :budget => '$1,000')
contract.deliverables << @deliverable_2 = HourlyDeliverable.generate!
FixedBudget.generate!(:deliverable => @deliverable_2, :budget => '$2,000')
assert_equal 3000, contract.fixed_budget
end
end
context "#fixed_spent" do
should "QUESTION: how to compute the amount spent"
end
context "#fixed_markup_budget" do
should "sum all fixed budget markup values on the Deliverables" do
contract = Contract.generate!
contract.deliverables << @deliverable_1 = FixedDeliverable.generate!
FixedBudget.generate!(:deliverable => @deliverable_1, :budget => '$1,000', :markup => '$100')
contract.deliverables << @deliverable_2 = HourlyDeliverable.generate!
FixedBudget.generate!(:deliverable => @deliverable_2, :budget => '$2,000', :markup => '200%')
assert_equal (100) + (2.00 * 2000), contract.fixed_markup_budget
end
end
context "#fixed_markup_spent" do
should "QUESTION: how to compute the amount spent"
end
end

View File

@@ -5,6 +5,7 @@ class DeliverableTest < ActiveSupport::TestCase
should_belong_to :manager
should_have_many :labor_budgets
should_have_many :overhead_budgets
should_have_many :fixed_budgets
should_have_many :issues
should_validate_presence_of :title
@@ -33,4 +34,5 @@ class DeliverableTest < ActiveSupport::TestCase
assert_equal 20100.00, d.total.to_f
end
end
end

View File

@@ -0,0 +1,33 @@
require File.dirname(__FILE__) + '/../test_helper'
class FixedBudgetTest < ActiveSupport::TestCase
should_belong_to :deliverable
context "#markup_value" do
setup do
@fixed_budget = FixedBudget.new(:budget => 1000)
end
context "with no markup" do
should "be 0" do
assert_equal nil, @fixed_budget.markup
assert_equal 0, @fixed_budget.markup_value
end
end
context "with a % markup" do
should "equal the budget times the %" do
@fixed_budget.markup = '50%'
assert_equal 500, @fixed_budget.markup_value
end
end
context "with a $ markup" do
should "equal the $ markup (straight markup)" do
@fixed_budget.markup = '$4,000.57'
assert_equal 4000.57, @fixed_budget.markup_value
end
end
end
end

View File

@@ -13,8 +13,9 @@ class FixedDeliverableTest < ActiveSupport::TestCase
LaborBudget.generate!(:deliverable => deliverable, :budget => 200)
LaborBudget.generate!(:deliverable => deliverable, :budget => 200)
OverheadBudget.generate!(:deliverable => deliverable, :budget => 200)
FixedBudget.generate!(:deliverable => deliverable, :budget => '$100', :markup => '50%') # $50 markup
assert_equal 400, deliverable.profit_budget
assert_equal 400 - 150, deliverable.profit_budget
end
should "be 0 if there is no total" do

View File

@@ -20,13 +20,14 @@ class HourlyDeliverableTest < ActiveSupport::TestCase
assert_equal 0, d.total
end
should "multiply the total number of labor budget hours by the contract billable rate" do
should "multiply the total number of labor budget hours by the contract billable rate and add the fixed budget and markup" 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)
d.fixed_budgets << FixedBudget.generate!(:budget => '$100', :markup => '50%') # $50 markup
assert_equal 100.0 * 10, d.total
assert_equal (100.0 * 10) + (100 + 50), d.total
end
end
@@ -86,9 +87,10 @@ class HourlyDeliverableTest < ActiveSupport::TestCase
LaborBudget.generate!(:deliverable => @deliverable, :hours => 5, :budget => 250)
LaborBudget.generate!(:deliverable => @deliverable, :hours => 5, :budget => 250)
OverheadBudget.generate!(:deliverable => @deliverable, :hours => 3, :budget => 225)
FixedBudget.generate!(:deliverable => @deliverable, :budget => '$100', :markup => '50%') # $50 markup
assert_equal 1500, @deliverable.total
assert_equal 1500 - (225 + 250 + 250), @deliverable.profit_budget
assert_equal 1650, @deliverable.total # has the FixedBudget items added to the total also
assert_equal 1650 - (225 + 250 + 250 + 100 + 50), @deliverable.profit_budget
end
end

View File

@@ -0,0 +1,553 @@
require File.dirname(__FILE__) + '/../test_helper'
class RetainerDeliverableTest < ActiveSupport::TestCase
should "be a subclass of HourlyDeliverable" do
assert_equal HourlyDeliverable, RetainerDeliverable.superclass
end
context "#months" do
should "be an array of months the Deliverable is active in" do
d = RetainerDeliverable.new(:start_date => Date.today.beginning_of_month,
:end_date => 6.months.from_now)
assert_equal 7, d.months.length # 6 months + current month
d.months.each do |month|
assert_kind_of Date, month
end
end
should "return an empty array if start_date is missing" do
d = RetainerDeliverable.new(:start_date => nil,
:end_date => 6.months.from_now)
assert_equal [], d.months
end
should "return an empty array if end_date is missing" do
d = RetainerDeliverable.new(:start_date => Date.today.beginning_of_month,
:end_date => nil)
assert_equal [], d.months
end
should "return an empty array if the start_date and end_date are reversed" do
d = RetainerDeliverable.new(:start_date => 6.months.from_now,
:end_date => Date.today.beginning_of_month)
assert_equal [], d.months
end
end
context "#create_budgets_for_periods" do
setup do
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-10',
:end_date => '2010-10-10')
LaborBudget.generate!(:deliverable => @deliverable)
OverheadBudget.generate!(:deliverable => @deliverable)
OverheadBudget.generate!(:deliverable => @deliverable)
end
should "create a dated labor budget for each month" do
assert_equal 1, @deliverable.labor_budgets.count
@deliverable.reload.create_budgets_for_periods
assert_equal 10, @deliverable.labor_budgets.count
labor_budgets = @deliverable.labor_budgets
labor_budgets.each do |budget|
assert_equal 2010, budget.year
end
(1..10).each do |month_number|
assert_equal 1, labor_budgets.select {|b| b.month == month_number}.length
end
end
should "create a dated overhead budget for each month" do
assert_equal 2, @deliverable.overhead_budgets.count
@deliverable.reload.create_budgets_for_periods
assert_equal 20, @deliverable.overhead_budgets.count
overhead_budgets = @deliverable.overhead_budgets
overhead_budgets.each do |budget|
assert_equal 2010, budget.year
end
(1..10).each do |month_number|
assert_equal 2, overhead_budgets.select {|b| b.month == month_number}.length
end
end
end
context "#labor_budget_spent" do
setup do
@project = Project.generate!
@contract = Contract.generate!(:billable_rate => 100, :project => @project)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
configure_overhead_plugin
@issue1 = Issue.generate_for_project!(@project)
@time_entry1 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @billable_activity,
:spent_on => Date.new(2010,1,2),
:hours => 10,
:user => @manager)
@time_entry2 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @billable_activity,
:spent_on => Date.new(2010,2,1),
:hours => 20,
:user => @manager)
@rate = Rate.generate!(:project => @project,
:user => @manager,
:date_in_effect => Date.new(2010,1,1),
:amount => 100)
@deliverable.issues << @issue1
end
context "with a empty period" do
should "use all periods" do
assert_equal (10+20) * 100, @deliverable.labor_budget_spent(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records periods" do
assert_equal 0, @deliverable.labor_budget_spent(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.labor_budget_spent('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 20 * 100, @deliverable.labor_budget_spent(Date.new(2010,2,1))
end
end
end
context "#labor_budget_total" do
setup do
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31')
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
end
context "with a empty period" do
should "use all periods" do
assert_equal 300.0, @deliverable.labor_budget_total(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.labor_budget_total(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.labor_budget_total('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 100.0, @deliverable.labor_budget_total(Date.new(2010,2,1))
end
end
end
context "#overhead_spent" do
setup do
@project = Project.generate!
@contract = Contract.generate!(:billable_rate => 100, :project => @project)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
configure_overhead_plugin
@issue1 = Issue.generate_for_project!(@project)
@time_entry1 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @non_billable_activity,
:spent_on => Date.new(2010,1,2),
:hours => 10,
:user => @manager)
@time_entry2 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @non_billable_activity,
:spent_on => Date.new(2010,2,1),
:hours => 20,
:user => @manager)
@rate = Rate.generate!(:project => @project,
:user => @manager,
:date_in_effect => Date.new(2010,1,1),
:amount => 100)
@deliverable.issues << @issue1
end
context "with a empty period" do
should "use all periods" do
assert_equal (10+20) * 100, @deliverable.overhead_spent(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records periods" do
assert_equal 0, @deliverable.overhead_spent(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.overhead_spent('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 20 * 100, @deliverable.overhead_spent(Date.new(2010,2,1))
end
end
end
context "#overhead_budget_total" do
setup do
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31')
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
end
context "with a empty period" do
should "use all periods" do
assert_equal 300.0, @deliverable.overhead_budget_total(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.overhead_budget_total(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.overhead_budget_total('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 100.0, @deliverable.overhead_budget_total(Date.new(2010,2,1))
end
end
end
# (Labor used * contract rate) - (labor used * time rate) - (overhead used * time rate)
context "#profit_left" do
setup do
@project = Project.generate!
@contract = Contract.generate!(:billable_rate => 200, :project => @project)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
configure_overhead_plugin
@issue1 = Issue.generate_for_project!(@project)
@time_entry1 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @billable_activity,
:spent_on => Date.new(2010,1,2),
:hours => 10,
:user => @manager)
@time_entry2 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @non_billable_activity,
:spent_on => Date.new(2010,2,1),
:hours => 20,
:user => @manager)
@rate = Rate.generate!(:project => @project,
:user => @manager,
:date_in_effect => Date.new(2010,1,1),
:amount => 100)
@deliverable.issues << @issue1
end
context "with a empty period" do
should "use all periods" do
assert_equal (10 * 200) - (10 * 100) - (20 * 100), @deliverable.profit_left(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records periods" do
assert_equal 0, @deliverable.profit_left(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.profit_left('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal (0 * 200) - (0 * 100) - (20 * 100), @deliverable.profit_left(Date.new(2010,2,1))
end
end
end
context "#profit_budget" do
setup do
@contract = Contract.generate!(:billable_rate => 100)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
assert_equal 100 * 30, @deliverable.total
assert_equal 2400, @deliverable.profit_budget # 3000 - 300 - 300
end
context "with a empty period" do
should "use all periods" do
assert_equal 2400, @deliverable.profit_budget(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.profit_budget(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.profit_budget('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 1000 - 200, @deliverable.profit_budget(Date.new(2010,2,1))
end
end
end
context "#total_spent" do
setup do
@project = Project.generate!
@contract = Contract.generate!(:billable_rate => 200, :project => @project)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
configure_overhead_plugin
@issue1 = Issue.generate_for_project!(@project)
@time_entry1 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @billable_activity,
:spent_on => Date.new(2010,1,2),
:hours => 10,
:user => @manager)
@time_entry2 = TimeEntry.generate!(:issue => @issue1,
:project => @project,
:activity => @billable_activity,
:spent_on => Date.new(2010,2,1),
:hours => 20,
:user => @manager)
@rate = Rate.generate!(:project => @project,
:user => @manager,
:date_in_effect => Date.new(2010,1,1),
:amount => 100)
@deliverable.issues << @issue1
end
context "with a empty period" do
should "use all periods" do
# Labor used * contract rate
assert_equal (10+20) * 200, @deliverable.total_spent(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records periods" do
assert_equal 0, @deliverable.total_spent(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.total_spent('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 20 * 200, @deliverable.total_spent(Date.new(2010,2,1))
end
end
end
context "#total" do
setup do
@contract = Contract.generate!(:billable_rate => 100)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.labor_budgets << LaborBudget.spawn(:budget => 100, :hours => 10)
@deliverable.overhead_budgets << OverheadBudget.spawn(:budget => 100, :hours => 10)
@deliverable.save!
assert_equal 100 * 30, @deliverable.total
end
context "with a empty period" do
should "use all periods" do
assert_equal 3000, @deliverable.total(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.total(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.total('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 1000, @deliverable.total(Date.new(2010,2,1))
end
end
end
context "#fixed_budget_total" do
setup do
@contract = Contract.generate!(:billable_rate => 100)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.fixed_budgets << FixedBudget.spawn(:budget => 1000)
@deliverable.fixed_budgets << FixedBudget.spawn(:budget => 2000)
@deliverable.save!
assert_equal 3000 * 3, @deliverable.fixed_budget_total
end
context "with a empty period" do
should "use all periods" do
assert_equal 9000, @deliverable.fixed_budget_total(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.fixed_budget_total(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.fixed_budget_total('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 3000, @deliverable.fixed_budget_total(Date.new(2010,2,1))
end
end
end
context "#fixed_markup_budget_total" do
setup do
@contract = Contract.generate!(:billable_rate => 100)
@deliverable = RetainerDeliverable.generate!(:start_date => '2010-01-01', :end_date => '2010-03-31', :contract => @contract)
@deliverable.fixed_budgets << FixedBudget.spawn(:budget => 1000, :markup => '50%')
@deliverable.fixed_budgets << FixedBudget.spawn(:budget => 2000, :markup => '$1000')
@deliverable.save!
assert_equal (500 + 1000) * 3, @deliverable.fixed_markup_budget_total
end
context "with a empty period" do
should "use all periods" do
assert_equal 4500, @deliverable.fixed_markup_budget_total(nil)
end
end
context "with a period out of the retainer range" do
should "filter the records" do
assert_equal 0, @deliverable.fixed_markup_budget_total(Date.new(2011,1,1))
end
end
context "with an invalid period" do
should "return 0" do
assert_equal 0, @deliverable.fixed_markup_budget_total('1')
end
end
context "with a period in the retainer range" do
should "filter the records" do
assert_equal 1500, @deliverable.fixed_markup_budget_total(Date.new(2010,2,1))
end
end
end
end