Author SHA1 Message Date
Eric Davis 394236c741 WIP: need to finish up the backend still, some frontend is here for integration testing. 2010-09-08 17:54:59 -07:00
Eric Davis 82dec74eb7 [#4420] Changed the approach for Retainer calculations by using an optional parameter. 2010-09-08 17:49:51 -07:00
Eric Davis c8b86db4c4 [#4420] Added a second Retainer calculation method for specific dates. 2010-09-08 14:54:36 -07:00
Eric Davis 0cc2523084 [#4420] Adding Retainer calculation methods for specific dates. 2010-09-08 14:50:25 -07:00
Eric Davis 7ed21f7fe2 [#4420] When a Retainer's period is changed, it will now reload it's details. 2010-09-08 14:03:06 -07:00
Eric Davis ad6766fd2f [#4420] Add the options for Retainers to select which month to display. (UI only). 2010-09-08 11:57:46 -07:00
Eric Davis d3052d7d54 [#4420] Implemented RetainerDeliverable#current_period 2010-09-08 11:46:59 -07:00
Eric Davis 9f8f1238aa [#4420] Add a Javascript prompt when extending or shrinking a Deliverable's period. 2010-09-08 10:57:09 -07:00
Eric Davis df53f27114 [#4420] Remove Deliverable#frequency, assumed to be monthly. 2010-09-01 20:24:29 -07:00
Eric Davis 6a0c6348ed [#4420] Missing locale string. 2010-09-01 20:10:20 -07:00
Eric Davis dfab7b5714 [#4420] Use empty budgets for Retainers that are missing them so they appear on the form. 2010-09-01 19:28:18 -07:00
Eric Davis abc4d30346 [#4420] Display a message in the form when a Retainer has no effective months. 2010-09-01 18:50:24 -07:00
Eric Davis 994e9b0332 [#4420] Handle edge case when a retainer has no start or end date set. 2010-09-01 18:49:55 -07:00
Eric Davis e5853100d5 [#4420] Added some Javascript to prompt the user if they try to create a Retainer without a date range. 2010-09-01 18:44:30 -07:00
Eric Davis 918561d667 [#4420] Handle edge cases if a Retainer has no start or end date to extend. 2010-09-01 18:19:43 -07:00
Eric Davis ede645e359 [#4420] Added test for changing Retainer's start/end date without it's period. 2010-09-01 18:14:48 -07:00
Eric Davis 9858b87fff [#4420] When a Retainer's period is shrunk, budgets outside the period are destroyed. 2010-09-01 18:12:40 -07:00
Eric Davis 0a33cdefbb Remove dead test code. 2010-09-01 17:57:49 -07:00
Eric Davis 8f92dcc870 [#4420] Changed Retainers to support extending multiple Budget items. 2010-09-01 17:56:16 -07:00
Eric Davis cb27ae2cc8 [#4420] Throw away single tests for start and end extensions. 2010-09-01 15:33:46 -07:00
Eric Davis 57d3830084 [#4420] Added a test for extending both the start and end dates. 2010-09-01 15:33:08 -07:00
Eric Davis f46d222f4a [#4420] Refactor: extract method. 2010-09-01 15:29:37 -07:00
Eric Davis 2969afeaf4 [#4420] Refactor: extract method. 2010-09-01 15:24:33 -07:00
Eric Davis bd3cd89dd0 [#4420] Refactor: extract method. 2010-09-01 15:20:28 -07:00
Eric Davis 4b12d9d9c8 [#4420] Retainers extended before the start date will duplicate the month's first budget items. 2010-09-01 15:15:16 -07:00
Eric Davis 16f8152599 [#4420] Retainers extended past the end date will duplicate the month's last budget items. 2010-09-01 15:09:36 -07:00
Eric Davis c12a33e25d Merge branch 'master' into 4420-retainers 2010-09-01 14:53:32 -07:00
Eric Davis 0c38fe2c37 [#4466] Added deprecated methods to support more of the Budget plugin API. 2010-09-01 14:32:09 -07:00
Eric Davis d48addbcb0 [#4420] Added support for overriding each period's finances on Retainers. 2010-08-30 10:51:37 -07:00
Eric Davis c13f31b1b5 [#4420] Added a Deliverable finance form for each Retainer month. 2010-08-30 10:30:59 -07:00
Eric Davis c3c472b3b1 [#4420] Extract the Deliverable finances form to a partial. 2010-08-30 10:09:13 -07:00
Eric Davis 40d4029cd1 [#4420] Make #months handle more edge cases. 2010-08-30 09:50:24 -07:00
Eric Davis 9a4b4f2b43 [#4420] Added OverheadBudget creation for Retainers. 2010-08-30 09:25:24 -07:00
Eric Davis fc28f8351f [#4420] Fixed the after_save callback for Retainers. 2010-08-30 09:18:37 -07:00
Eric Davis 7d43ab7d74 [#4420] Working on creating labor budgets for each retainer month. 2010-08-23 16:15:57 -07:00
Eric Davis f899b4e623 [#4420] Started on the Retainer's periods, only pending tests for now. 2010-08-16 15:06:08 -07:00
Eric Davis 4b162f85bf [#4420] Refactor: extract duplicated data to query. 2010-08-16 14:53:08 -07:00
Eric Davis 1120b39ed6 [#4420] Hook up Retainers so they can be saved. 2010-08-16 14:49:03 -07:00
Eric Davis 7d19c10714 [#4420] Show/hide the frequency field based on the selected Deliverable type. 2010-08-16 14:48:35 -07:00
Eric Davis 3e30629d44 [#4420] Add the frequency field for Retainers to the Model and View. 2010-08-16 14:47:47 -07:00
Eric Davis 8321903598 [#4420] Add frequency to deliverables for Retainers. 2010-08-16 14:08:04 -07:00
Eric Davis f18d259c3c [#4420] Add a RetainerDeliverable model. 2010-08-16 13:59:42 -07:00
Eric Davis 9d52bcfbe2 Ignore tmp 2010-08-16 13:59:15 -07:00
24 changed files with 1337 additions and 185 deletions
+1
View File
@@ -1 +1,2 @@
webrat*
tmp/
+13 -3
View File
@@ -14,7 +14,7 @@ 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) }
@@ -22,12 +22,17 @@ class DeliverablesController < InheritedResources::Base
def update
@deliverable = begin_of_association_chain.deliverables.find_by_id(params[:id])
params[:deliverable] = params[:fixed_deliverable] || params[:hourly_deliverable]
params[:deliverable] = params[:fixed_deliverable] || params[:hourly_deliverable] || params[:retainer_deliverable]
update! { contract_url(@project, @contract) }
end
def show
redirect_to contract_url(@project, @contract)
if show_partial?
@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
@@ -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
+11
View File
@@ -85,4 +85,15 @@ module ContractsHelper
def format_value_field_for_contracts(value)
number_with_precision(value, :precision => Contract::ViewPrecision, :delimiter => ',')
end
def retainer_period_options(deliverable)
options = []
options << content_tag(:option, l(:label_all).capitalize, :value => '')
deliverable.months.collect do |month|
options << content_tag(:option, month.strftime("%B %Y"), :value => month.strftime("%Y-%m"))
end
options
end
end
+66 -3
View File
@@ -22,6 +22,16 @@ 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
@@ -44,14 +54,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 +81,56 @@ class Deliverable < ActiveRecord::Base
issues.inject(0) {|total, issue| total += issue.spent_hours }
end
# Wrapper for the old Budget plugins' API
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
+4 -4
View File
@@ -11,7 +11,7 @@ class FixedDeliverable < Deliverable
'F'
end
def total
def total(date=nil)
read_attribute(:total) || 0.0
end
@@ -22,9 +22,9 @@ class FixedDeliverable < 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)
(total(date) || 0.0) - budgets
end
# The amount of money remaining after expenses have been taken out
+5 -5
View File
@@ -14,12 +14,12 @@ class HourlyDeliverable < Deliverable
'H'
end
def total
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)
return contract.billable_rate * labor_budget_hours(date)
end
# Total amount to be billed on the deliverable, using the total time logged
@@ -49,9 +49,9 @@ 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)
(total(date) || 0.0) - budgets
end
# The amount of money remaining after expenses have been taken out
+222
View File
@@ -0,0 +1,222 @@
# 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_period
Date.today.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
(beginning_date..ending_date)
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 labor_budget_total(date=nil)
if date
if within_date_range?(date)
labor_budgets.sum(:budget, :conditions => {:year => date.year, :month => date.month})
else
0 # outside of range
end
else
super
end
end
def overhead_budget_total(date=nil)
if date
if within_date_range?(date)
overhead_budgets.sum(:budget, :conditions => {:year => date.year, :month => date.month})
else
0 # outside of range
end
else
super
end
end
def labor_budget_hours(date=nil)
if date
if within_date_range?(date)
labor_budgets.sum(:hours, :conditions => {:year => date.year, :month => date.month})
else
0 # outside of range
end
else
super
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
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)
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
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})
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)
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})
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)
end
end
def create_budgets_for_new_period(new_period, labor_budgets_to_copy, overhead_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
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
end
+1 -98
View File
@@ -108,104 +108,7 @@
<% 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} %>
</tr>
<% end %>
</tbody>
@@ -0,0 +1,114 @@
<% period ||= '' %>
<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) %>
</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)) %></td>
<td class="labor_budget_total"><%= 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 class="overhead_budget_spent"><%= h(format_value_field_for_contracts(deliverable.overhead_spent)) %></td>
<td class="overhead_budget_total"><%= 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 class="total_spent"><strong><%= h(format_value_field_for_contracts(deliverable.total_spent)) %></strong></td>
<td class="total"><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>
@@ -0,0 +1,63 @@
<% form.inputs :name => label, :class => "deliverable-finances #{fieldset_class}" do %>
<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>
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'}, :hint => l(:text_dollar_sign) %>
<% end %>
+27 -61
View File
@@ -1,14 +1,28 @@
<%= javascript_tag("var i18nStartDateEmpty = '#{l(:text_start_date_empty)}'") %>
<%= javascript_tag("var i18nEndDateEmpty = '#{l(:text_end_date_empty)}'") %>
<%= javascript_tag("var i18nChangedPeriodMessage = '#{l(:text_changed_period_message)}'") %>
<div class="box tabular">
<% form.inputs :name => l(:text_deliverable_details_legend) do %>
<%# 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), :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, :label => l(:text_deliverable_finances), :fieldset_class => '' } %>
<% end %>
</div>
+57 -6
View File
@@ -1,4 +1,6 @@
jQuery(function($) {
$("#ajax-indicator").ajaxStart(function(){ $(this).show(); });
$("#ajax-indicator").ajaxStop(function(){ $(this).hide(); });
var right_align = $('#contract-terms .finance tr td:nth-child ~ td, .c_overview table.right tr td:nth-child ~ td, #deliverables table tr.click td:nth-child(5) ~ td, .deliverable_finance_table tr.aright td:nth-child ~ td');
@@ -22,16 +24,37 @@ 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();
} else if(deliverableType == "HourlyDeliverable") {
hideDeliverableTotal();
hideDeliverableFrequency();
} else if(deliverableType == "RetainerDeliverable") {
hideDeliverableTotal();
showDeliverableFrequency();
}
},
toggleSpecificDeliverableFields($('form.deliverable'));
@@ -39,6 +62,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 */
+6
View File
@@ -28,6 +28,7 @@ en:
field_feature_sign_off: Feature Sign Off
field_warranty_sign_off: Warranty Sign Off
text_deliverable_finances: Deliverable Finances
text_deliverable_finances_date: "Deliverable Finances - {{date}}"
text_short_hours: hrs
text_dollar_sign: '$'
field_client_point_of_contact: "Point of Contact"
@@ -60,3 +61,8 @@ 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"
@@ -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
@@ -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
@@ -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
@@ -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
+36
View File
@@ -209,4 +209,40 @@ class ContractsShowTest < ActionController::IntegrationTest
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
@@ -0,0 +1,50 @@
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'}
puts response.body
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]", "Feburary 2010"
end
end
end
end
end
+297
View File
@@ -90,4 +90,301 @@ 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.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
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
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.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
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.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
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 6 inputs:
# * labor hidden year
# * labor hidden month
# * labor hours
# * labor amount
# * overhead hidden year
# * overhead hidden month
# * overhead hours
# * overhead amount
# * total (hidden)
assert_select ".date-2010-01" do
assert_select "input", :count => 9
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
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)
end
end
+76 -3
View File
@@ -92,9 +92,9 @@ class DeliverablesNewTest < ActionController::IntegrationTest
fill_in "Notes", :with => 'Some notes on the deliverable'
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,6 +111,79 @@ class DeliverablesNewTest < ActionController::IntegrationTest
end
should "create a new Retainer deliverable" 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
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'
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
click_button "Save"
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!
-2
View File
@@ -26,8 +26,6 @@ class ContractTest < ActiveSupport::TestCase
end
end
should "QUESTION: name be unique"
should "default executed to false" do
@contract = Contract.new
+1
View File
@@ -33,4 +33,5 @@ class DeliverableTest < ActiveSupport::TestCase
assert_equal 20100.00, d.total.to_f
end
end
end
+241
View File
@@ -0,0 +1,241 @@
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
context "with a empty period" do
should "use all periods"
end
context "with a period out of the retainer range" do
should "use all periods"
end
context "with a period in the retainer range" do
should "filter the records"
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"
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
# context "#profit_left"
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"
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
end