Compare commits
65 Commits
v0.1.0
...
4477-fixed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8270498f3 | ||
|
|
2c04ef4f45 | ||
|
|
d78e7b1dc3 | ||
|
|
67dd75a980 | ||
|
|
55c9f93c6c | ||
|
|
081df7874d | ||
|
|
ed7536897e | ||
|
|
86be29cecc | ||
|
|
294c88f11d | ||
|
|
edc8db7fe2 | ||
|
|
feee5e8b43 | ||
|
|
07da5d98d8 | ||
|
|
7f7e5b3042 | ||
|
|
d315252565 | ||
|
|
6b38dfca91 | ||
|
|
75046cfdd1 | ||
|
|
12fffee314 | ||
|
|
362610dc77 | ||
|
|
1b08487a0d | ||
|
|
5ef7d0271d | ||
|
|
0a1bb9169d | ||
|
|
91d8c1818e | ||
|
|
394236c741 | ||
|
|
82dec74eb7 | ||
|
|
c8b86db4c4 | ||
|
|
0cc2523084 | ||
|
|
7ed21f7fe2 | ||
|
|
ad6766fd2f | ||
|
|
d3052d7d54 | ||
|
|
9f8f1238aa | ||
|
|
df53f27114 | ||
|
|
6a0c6348ed | ||
|
|
dfab7b5714 | ||
|
|
abc4d30346 | ||
|
|
994e9b0332 | ||
|
|
e5853100d5 | ||
|
|
918561d667 | ||
|
|
ede645e359 | ||
|
|
9858b87fff | ||
|
|
0a33cdefbb | ||
|
|
8f92dcc870 | ||
|
|
cb27ae2cc8 | ||
|
|
57d3830084 | ||
|
|
f46d222f4a | ||
|
|
2969afeaf4 | ||
|
|
bd3cd89dd0 | ||
|
|
4b12d9d9c8 | ||
|
|
16f8152599 | ||
|
|
c12a33e25d | ||
|
|
0c38fe2c37 | ||
|
|
d48addbcb0 | ||
|
|
c13f31b1b5 | ||
|
|
c3c472b3b1 | ||
|
|
40d4029cd1 | ||
|
|
9a4b4f2b43 | ||
|
|
fc28f8351f | ||
|
|
7d43ab7d74 | ||
|
|
f899b4e623 | ||
|
|
4b162f85bf | ||
|
|
1120b39ed6 | ||
|
|
7d19c10714 | ||
|
|
3e30629d44 | ||
|
|
8321903598 | ||
|
|
f18d259c3c | ||
|
|
9d52bcfbe2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
webrat*
|
||||
tmp/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
55
app/models/fixed_budget.rb
Normal file
55
app/models/fixed_budget.rb
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
402
app/models/retainer_deliverable.rb
Normal file
402
app/models/retainer_deliverable.rb
Normal 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
|
||||
@@ -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>
|
||||
|
||||
130
app/views/deliverables/_details_row.html.erb
Normal file
130
app/views/deliverables/_details_row.html.erb
Normal 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>
|
||||
|
||||
101
app/views/deliverables/_finance_form.html.erb
Normal file
101
app/views/deliverables/_finance_form.html.erb
Normal 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 %>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
9
db/migrate/011_add_frequency_to_deliverables.rb
Normal file
9
db/migrate/011_add_frequency_to_deliverables.rb
Normal 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
|
||||
14
db/migrate/012_add_year_and_month_to_labor_budgets.rb
Normal file
14
db/migrate/012_add_year_and_month_to_labor_budgets.rb
Normal 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
|
||||
14
db/migrate/013_add_year_and_month_to_overhead_budgets.rb
Normal file
14
db/migrate/013_add_year_and_month_to_overhead_budgets.rb
Normal 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
|
||||
9
db/migrate/014_remove_frequency_from_deliverables.rb
Normal file
9
db/migrate/014_remove_frequency_from_deliverables.rb
Normal 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
|
||||
19
db/migrate/015_create_fixed_budgets.rb
Normal file
19
db/migrate/015_create_fixed_budgets.rb
Normal 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
|
||||
14
db/migrate/016_add_year_and_month_to_fixed_budgets.rb
Normal file
14
db/migrate/016_add_year_and_month_to_fixed_budgets.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
49
test/integration/deliverable_details_test.rb
Normal file
49
test/integration/deliverable_details_test.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
33
test/unit/fixed_budget_test.rb
Normal file
33
test/unit/fixed_budget_test.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
553
test/unit/retainer_deliverable_test.rb
Normal file
553
test/unit/retainer_deliverable_test.rb
Normal 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
|
||||
Reference in New Issue
Block a user