Compare commits
93 Commits
4184-budge
...
4420-wip-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
437909b621 | ||
|
|
720da2c554 | ||
|
|
620eaa262f | ||
|
|
a3e1aa277f | ||
|
|
25436910fb | ||
|
|
6924b32048 | ||
|
|
b83696f49a | ||
|
|
8906639273 | ||
|
|
dfcc31c83e | ||
|
|
fa12fcd129 | ||
|
|
82d46bd12a | ||
|
|
a2b810a4d8 | ||
|
|
c17e8eaf1b | ||
|
|
e734430deb | ||
|
|
61270c60d5 | ||
|
|
cb8fd95f29 | ||
|
|
074d6a47e7 | ||
|
|
de0752ed02 | ||
|
|
ed1d04d18d | ||
|
|
0cb4b1941a | ||
|
|
66f3a07e75 | ||
|
|
4af0860542 | ||
|
|
118b0d7622 | ||
|
|
74795a2788 | ||
|
|
a2dfc1c92b | ||
|
|
67069dc60a | ||
|
|
90d2aeccea | ||
|
|
78c43a8821 | ||
|
|
73e92d2970 | ||
|
|
c652360a6d | ||
|
|
5958285410 | ||
|
|
fcf012c828 | ||
|
|
fe2c058def | ||
|
|
ea00b89af5 | ||
|
|
84c679dc67 | ||
|
|
848616853d | ||
|
|
72aeaac59a | ||
|
|
6ad50c57b2 | ||
|
|
1d644178bd | ||
|
|
389fbf3d13 | ||
|
|
729acf200d | ||
|
|
ad8dec8638 | ||
|
|
39edad3e07 | ||
|
|
76a0573341 | ||
|
|
231066098b | ||
|
|
7209871e87 | ||
|
|
1670fb5a73 | ||
|
|
4100972d46 | ||
|
|
e4ce899cd5 | ||
|
|
c8ede6a05d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
webrat*
|
||||
tmp/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ module ContractsHelper
|
||||
end
|
||||
end
|
||||
|
||||
def format_budget_for_deliverable(deliverable, spent, total)
|
||||
def format_budget_for_deliverable(deliverable, spent, total, options={})
|
||||
extra_css_class = options[:class] || ''
|
||||
|
||||
if total > 0 || spent > 0
|
||||
content_tag(:div, h(number_to_currency(spent, :unit => '')), :class => 'spent-amount') +
|
||||
content_tag(:div, h(number_to_currency(total, :unit => '')), :class => 'total-amount')
|
||||
content_tag(:td, h(format_value_field_for_contracts(spent)), :class => 'spent-amount ' + extra_css_class) +
|
||||
content_tag(:td, h(format_value_field_for_contracts(total)), :class => 'total-amount white ' + extra_css_class)
|
||||
else
|
||||
'---'
|
||||
content_tag(:td, '----', :colspan => '2', :class => 'no-value ' + extra_css_class)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,10 +25,12 @@ module ContractsHelper
|
||||
# </p>
|
||||
def show_field(object, field, options={}, &block)
|
||||
html_options = options[:html_options] || {}
|
||||
label = content_tag(:span, l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) + ": ", :class => 'contract-details-label')
|
||||
label_html_options = options[:label_html_options] || {}
|
||||
label = content_tag(:strong, l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) + ": ", :class => 'contract-details-label')
|
||||
|
||||
formatter = options[:format]
|
||||
raw_content = options[:raw] || false
|
||||
wrap_in_td = options[:wrap_in_td] || true
|
||||
|
||||
content = ''
|
||||
|
||||
@@ -39,10 +43,16 @@ module ContractsHelper
|
||||
object.send(field)
|
||||
end
|
||||
end
|
||||
|
||||
if raw_content
|
||||
field_content = content
|
||||
else
|
||||
field_content = h(content)
|
||||
end
|
||||
|
||||
content_tag(:p,
|
||||
label +
|
||||
(raw_content ? content : h(content)),
|
||||
content_tag(:tr,
|
||||
content_tag(:td, label, label_html_options) +
|
||||
(wrap_in_td ? content_tag(:td, field_content) : field_content),
|
||||
html_options)
|
||||
end
|
||||
|
||||
@@ -52,10 +62,10 @@ module ContractsHelper
|
||||
spent_content = send(formatter, object.send(spent_field))
|
||||
total_content = send(formatter, object.send(total_field))
|
||||
|
||||
show_field(object, spent_field, options.merge(:raw => true)) do
|
||||
show_field(object, spent_field, options.merge(:raw => true, :wrap_in_td => false)) do
|
||||
|
||||
content_tag(:span, h(spent_content), :class => 'spent') +
|
||||
content_tag(:span, h(total_content), :class => 'budget')
|
||||
content_tag(:td, h(spent_content), :class => 'spent') +
|
||||
content_tag(:td, h(total_content), :class => 'budget')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,6 +75,25 @@ module ContractsHelper
|
||||
|
||||
def format_payment_terms(value)
|
||||
return '' if value.blank?
|
||||
return l(Contract::PaymentTerms[value.to_sym])
|
||||
return h(value.name)
|
||||
end
|
||||
|
||||
def format_deliverable_value_fields(value)
|
||||
number_with_precision(value, :precision => Deliverable::ViewPrecision, :delimiter => '')
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
class Contract < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
ViewPrecision = 0
|
||||
|
||||
# Associations
|
||||
belongs_to :project
|
||||
belongs_to :account_executive, :class_name => 'User', :foreign_key => 'account_executive_id'
|
||||
belongs_to :payment_term, :class_name => "PaymentTerm", :foreign_key => "payment_term_id"
|
||||
has_many :deliverables, :dependent => :destroy
|
||||
|
||||
# Validations
|
||||
@@ -24,12 +27,14 @@ class Contract < ActiveRecord::Base
|
||||
attr_accessible :billable_rate
|
||||
attr_accessible :discount
|
||||
attr_accessible :discount_note
|
||||
attr_accessible :payment_terms
|
||||
attr_accessible :payment_term_id
|
||||
attr_accessible :client_ap_contact_information
|
||||
attr_accessible :po_number
|
||||
attr_accessible :client_point_of_contact
|
||||
attr_accessible :details
|
||||
|
||||
named_scope :by_name, {:order => "#{Contract.table_name}.name ASC"}
|
||||
|
||||
[:status, :contract_type,
|
||||
:fixed_spent, :fixed_budget,
|
||||
:markup_spent, :markup_budget,
|
||||
@@ -91,19 +96,6 @@ class Contract < ActiveRecord::Base
|
||||
end
|
||||
alias_method :profit_spent, :profit_left
|
||||
|
||||
PaymentTerms = {
|
||||
:net_0 => :text_payment_terms_net_0,
|
||||
:net_15 => :text_payment_terms_net_15,
|
||||
:net_30 => :text_payment_terms_net_30,
|
||||
:net_45 => :text_payment_terms_net_45
|
||||
}
|
||||
|
||||
def payment_terms_for_select
|
||||
PaymentTerms.collect {|value, label|
|
||||
[l(label), value.to_s]
|
||||
}
|
||||
end
|
||||
|
||||
def after_initialize
|
||||
self.executed = false unless self.executed.present?
|
||||
end
|
||||
@@ -114,6 +106,10 @@ class Contract < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
if Rails.env.test?
|
||||
generator_for :name, :method => :next_name
|
||||
generator_for :executed => true
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Deliverable < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
ViewPrecision = 2
|
||||
|
||||
# Associations
|
||||
belongs_to :contract
|
||||
belongs_to :manager, :class_name => 'User', :foreign_key => 'manager_id'
|
||||
@@ -18,10 +20,28 @@ class Deliverable < ActiveRecord::Base
|
||||
|
||||
# Accessors
|
||||
|
||||
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
|
||||
|
||||
def to_s
|
||||
title
|
||||
end
|
||||
|
||||
def to_underscore
|
||||
self.class.to_s.underscore
|
||||
end
|
||||
@@ -34,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) +
|
||||
@@ -53,6 +81,56 @@ class Deliverable < ActiveRecord::Base
|
||||
issues.inject(0) {|total, issue| total += issue.spent_hours }
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
19
app/models/payment_term.rb
Normal file
19
app/models/payment_term.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class PaymentTerm < Enumeration
|
||||
unloadable
|
||||
|
||||
has_many :contracts, :foreign_key => 'payment_term_id'
|
||||
|
||||
OptionName = :enumeration_payment_term
|
||||
|
||||
def option_name
|
||||
OptionName
|
||||
end
|
||||
|
||||
def objects_count
|
||||
contracts.count
|
||||
end
|
||||
|
||||
def transfer_relations(to)
|
||||
contracts.update_all("payment_term_id = #{to.id}")
|
||||
end
|
||||
end
|
||||
222
app/models/retainer_deliverable.rb
Normal file
222
app/models/retainer_deliverable.rb
Normal 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,24 +1,37 @@
|
||||
<% form.inputs do %>
|
||||
<div class="box tabular">
|
||||
<% form.inputs :name => l(:text_general_legend) do %>
|
||||
<%= form.input :name, :required => true %>
|
||||
<%= form.input :account_executive, :required => true %>
|
||||
<%= form.input :account_executive, :required => true, :collection => @project.users.sort %>
|
||||
<li class="boolean optional">
|
||||
<%= label('contract', 'executed') %>
|
||||
<%= check_box 'contract', 'executed' %>
|
||||
</li>
|
||||
<%= form.input :start_date, :required => true, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('contract_start_date') %>
|
||||
<%= form.input :end_date, :required => true, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('contract_end_date') %>
|
||||
<%= form.input :billable_rate, :input_html => {:size => 20}, :hint => l(:field_billable_rate_hint) %>
|
||||
<%= form.input :discount, :input_html => {:size => 20}, :hint => l(:field_discount_hint) %>
|
||||
<%= form.input :discount_note, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
|
||||
<%= form.input :payment_terms, :as => :select, :collection => resource.payment_terms_for_select %>
|
||||
<%= form.input :client_ap_contact_information, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
|
||||
<%= form.input :po_number %>
|
||||
<%= form.input :client_point_of_contact, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
|
||||
<%= form.input :details, :input_html => {:class => 'wiki-edit'} %>
|
||||
<% end %>
|
||||
|
||||
<% form.inputs :name => l(:text_budget_legend) do %>
|
||||
<%= form.input :billable_rate, :input_html => {:size => 20}, :hint => l(:field_billable_rate_hint) %>
|
||||
<%= form.input :discount, :input_html => {:size => 20}, :hint => l(:field_discount_hint) %>
|
||||
<%= form.input :discount_note, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
|
||||
<% end %>
|
||||
|
||||
<% form.inputs :name => l(:text_account_legend) do %>
|
||||
<%= form.input :payment_term %>
|
||||
<%= form.input :po_number %>
|
||||
<%= form.input :client_ap_contact_information, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
|
||||
<%= form.input :client_point_of_contact, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% form.buttons do %>
|
||||
<%= form.commit_button %>
|
||||
<%= link_to(l(:button_cancel), cancel_path) %>
|
||||
<ol>
|
||||
<li class="commit">
|
||||
<%= submit_tag(l(:text_save_contract)) %>
|
||||
<%= link_to(l(:button_cancel), cancel_path) %>
|
||||
</li>
|
||||
</ol>
|
||||
<% end %>
|
||||
|
||||
<%= wikitoolbar_for 'contract_discount_note' %>
|
||||
|
||||
25
app/views/contracts/_title.html.erb
Normal file
25
app/views/contracts/_title.html.erb
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="title-bar" id="upper-title-bar">
|
||||
<%= content_tag(:h2, h(contract.name)) %>
|
||||
|
||||
<div class="title-bar-actions">
|
||||
<span><a href="#TODO-release-4" class="">Log time (Release 4)</a></span>
|
||||
|
||||
<span class="meta-sep"> | </span>
|
||||
|
||||
<span id="watcher"><a class="icon icon-fav-off" href="#TODO-release-3" onclick="">Watch (Release 3)</a></span>
|
||||
|
||||
<span class="meta-sep"> | </span>
|
||||
|
||||
<span><a href="#TODO">Copy (Release ?)</a></span>
|
||||
<span class="meta-sep"> | </span>
|
||||
|
||||
<div class="update button-large">
|
||||
<%= link_to(l(:button_update), edit_contract_path(@project, contract)) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# TODO: release 3/4, contract messages
|
||||
<div class="error_msg">
|
||||
<p>There is $243 worth of time clocked to issues that are not assigned to any deliverables. <span>Please update the orphaned issues.</span> </p>
|
||||
</div>
|
||||
%>
|
||||
@@ -4,8 +4,10 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= content_tag(:h2, h(resource.name)) %>
|
||||
<%= content_tag(:h2, h(l(:text_edit_contract_name, :name => resource.name))) %>
|
||||
|
||||
<% semantic_form_for resource, :url => contract_path(@project, resource), :html => {:class => 'tabular'} do |form| %>
|
||||
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contract_path(@project, resource)} %>
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:text_contracts)} - #{h(l(:text_edit_contract_name, :name => resource.name))}" %>
|
||||
|
||||
@@ -1,26 +1,52 @@
|
||||
<% if collection.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<table class="list" cellspacing="0" border="0" cellpadding="0" id="contracts">
|
||||
<thead>
|
||||
<th><%= l(:field_id) %></th>
|
||||
<th><%= l(:field_name) %></th>
|
||||
<%# TODO: Status %>
|
||||
<%# TODO: Type %>
|
||||
<th><%= l(:field_account_executive_short) %></th>
|
||||
<th><%= l(:field_total_budget) %></th>
|
||||
<th><%= l(:field_end_date) %></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% collection.each do |contract| %>
|
||||
<% content_tag_for(:tr, contract) do %>
|
||||
<td class="id"><%= link_to(h(contract.id), contract_path(@project, contract)) %></td>
|
||||
<td class="name"><%= link_to(h(contract.name), contract_path(@project, contract)) %></td>
|
||||
<td class="account-executive"><%= h contract.account_executive.name %></td>
|
||||
<td class="total-budget"><%= h(number_to_currency(contract.total_budget)) %></td>
|
||||
<td class="end-date"><%= h format_date(contract.end_date) %></td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
<div id="contract_list">
|
||||
|
||||
<div class="title-bar">
|
||||
|
||||
<div class="contextual">
|
||||
<div class="new-issue button-large">
|
||||
<%= link_to(l(:text_new_contract), new_contract_path) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Active Contacts</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<% if collection.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<table class="list" cellspacing="0" border="0" cellpadding="0" id="contracts">
|
||||
<thead>
|
||||
<th><%= l(:field_id) %></th>
|
||||
<th><%= l(:field_name) %></th>
|
||||
<%# TODO: Release 5, Status %>
|
||||
<%# TODO: Release 5, Type %>
|
||||
<th><%= l(:field_account_executive_short) %></th>
|
||||
<th><%= l(:field_total_budget) %></th>
|
||||
<th><%= l(:field_end_date) %></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% collection.each do |contract| %>
|
||||
<% content_tag_for(:tr, contract, :class => cycle('','odd')) do %>
|
||||
<td class="id"><%= link_to(h(contract.id), contract_path(@project, contract)) %></td>
|
||||
<td class="name"><%= link_to(h(contract.name), contract_path(@project, contract)) %></td>
|
||||
<td class="account-executive"><%= h contract.account_executive.name %></td>
|
||||
<td class="total-budget"><%= h(format_value_field_for_contracts(contract.total_budget)) %></td>
|
||||
<td class="end-date"><%= h format_date(contract.end_date) %></td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<div class="title-bar">
|
||||
<h2>Inactive Contracts</h2>
|
||||
</div>
|
||||
|
||||
<%# TODO: split contracts by active and inactive %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<p>TODO: Release 5 (Contract Status)</p>
|
||||
|
||||
</div>
|
||||
|
||||
<% html_title "#{l(:text_contracts)}" %>
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
<% semantic_form_for resource, :html => {:class => 'tabular'} do |form| %>
|
||||
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contracts_path} %>
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:text_new_contract)}" %>
|
||||
|
||||
@@ -1,82 +1,199 @@
|
||||
<%# TODO: Need skinning %>
|
||||
<%= render :partial => 'title', :locals => {:contract => resource} %>
|
||||
|
||||
<% div_for(resource) do %>
|
||||
<%= content_tag(:h2, h(resource.name)) %>
|
||||
|
||||
<%= show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
|
||||
<%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %>
|
||||
<%= show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %>
|
||||
<%= show_field(resource, :start_date, :format => :format_date, :html_options => {:class => 'contract-start-date'}) %>
|
||||
<%= show_field(resource, :end_date, :format => :format_date, :html_options => {:class => 'contract-end-date'}) %>
|
||||
<%= avatar(resource.account_executive, :size => 40) %>
|
||||
|
||||
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}) %>
|
||||
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :html_options => {:class => 'contract-overhead'}) %>
|
||||
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
|
||||
<div class="c_overview">
|
||||
<table class="left">
|
||||
<%= show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
|
||||
<%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %>
|
||||
<%= show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %>
|
||||
</table>
|
||||
|
||||
<table class="middle">
|
||||
<%= show_field(resource, :start_date, :format => :format_date, :html_options => {:class => 'contract-start-date'}) %>
|
||||
<%= show_field(resource, :end_date, :format => :format_date, :html_options => {:class => 'contract-end-date'}) %>
|
||||
</table>
|
||||
|
||||
<table class="right">
|
||||
<%= show_budget_field(resource, :labor_spent, :labor_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '49%'}) %>
|
||||
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-overhead'}) %>
|
||||
<%= show_budget_field(resource, :total_spent, :total_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-total'}) %>
|
||||
</table>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<a href="#" id="expand_terms" class="expand"><%= l(:text_terms_and_financial_overview) %></a>
|
||||
|
||||
<div id="contract-terms">
|
||||
|
||||
<div class="left">
|
||||
<table class="info">
|
||||
<%= show_field(resource, :client_point_of_contact, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-point-of-contact padd'}, :label_html_options => {:width => '25%'}) %>
|
||||
<%= show_field(resource, :executed, :html_options => {:class => 'contract-executed'}) %>
|
||||
<%= show_field(resource, :discount_note, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-discount-note'}) %>
|
||||
<%= show_field(resource, :payment_term, :format => :format_payment_terms, :html_options => {:class => 'contract-payment-terms'}) %>
|
||||
<%= show_field(resource, :client_ap_contact_information, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-ap-contact-information'}) %>
|
||||
<%= show_field(resource, :po_number, :html_options => {:class => 'contract-po-number'}) %>
|
||||
<%= show_field(resource, :details, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-details'}) %>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="contract-terms" style="border: 1px solid #000">
|
||||
<%= show_field(resource, :client_point_of_contact, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-point-of-contact'}) %>
|
||||
<%= show_field(resource, :executed, :html_options => {:class => 'contract-executed'}) %>
|
||||
<%= show_field(resource, :discount_note, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-discount-note'}) %>
|
||||
<%= show_field(resource, :payment_terms, :format => :format_payment_terms, :html_options => {:class => 'contract-payment-terms'}) %>
|
||||
<%= show_field(resource, :client_ap_contact_information, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-ap-contact-information'}) %>
|
||||
<%= show_field(resource, :po_number, :html_options => {:class => 'contract-po-number'}) %>
|
||||
<%= show_field(resource, :details, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-details'}) %>
|
||||
<table class="finance">
|
||||
<%= 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, :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'}) %>
|
||||
|
||||
<div id="contract-finances" style="border: 1px solid #000">
|
||||
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}) %>
|
||||
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :html_options => {:class => 'contract-overhead'}) %>
|
||||
<%= show_budget_field(resource, :fixed_spent, :fixed_budget, :html_options => {:class => 'contract-fixed'}) %>
|
||||
<%= show_budget_field(resource, :markup_spent, :markup_budget, :html_options => {:class => 'contract-markup'}) %>
|
||||
<%= show_budget_field(resource, :profit_spent, :profit_budget, :html_options => {:class => 'contract-profit'}) %>
|
||||
<%= show_budget_field(resource, :discount_spent, :discount_budget, :html_options => {:class => 'contract-discount'}) %>
|
||||
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<div class="hr"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<%= show_field(resource, :billable_rate, :format => :format_hourly_rate, :html_options => {:class => 'contract-billable-rate'}) %>
|
||||
<%= show_budget_field(resource, :estimated_hour_spent, :estimated_hour_budget, :format => :l_hours, :html_options => {:class => 'contract-estimated-hour'}) %>
|
||||
<%= show_budget_field(resource, :total_spent, :total_budget, :format => :format_value_field_for_contracts, :html_options => {:class => 'contract-total'}) %>
|
||||
<%= show_field(resource, :billable_rate, :format => :format_hourly_rate, :html_options => {:class => 'contract-billable-rate total'}) %>
|
||||
|
||||
<%= show_budget_field(resource, :estimated_hour_spent, :estimated_hour_budget, :format => :l_hours, :html_options => {:class => 'contract-estimated-hour total'}) %>
|
||||
|
||||
</table>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
<div id="deliverables">
|
||||
|
||||
<%= content_tag(:h3, l(:field_deliverable_plural)) %>
|
||||
|
||||
<div class="actions">
|
||||
<a href="#TODO-release-2">CSV (Release 2)</a>
|
||||
<a href="#TODO-release?">View All (9) (Release ?)</a>
|
||||
<%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="clear"></div>
|
||||
|
||||
<%= link_to(l(:button_update), edit_contract_path(@project, resource)) %>
|
||||
<% if resource.deliverables.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<table width="100%" class="texpand" id="deliverables">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= l(:field_end_date) %></th>
|
||||
<th> </th>
|
||||
<th><%= l(:field_title) %></th>
|
||||
<th><%= l(:field_status) %></th>
|
||||
<th><%= l(:field_manager) %></th>
|
||||
<th colspan="2"><%= l(:field_labor) %></th>
|
||||
<th colspan="2"><%= l(:field_overhead) %></th>
|
||||
<th colspan="2"><%= l(:field_fixed) %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') %>
|
||||
<tbody>
|
||||
<% resource.deliverables.each do |deliverable| %>
|
||||
<% content_tag_for(:tr, deliverable, :class => 'click ' + cycle('even','')) do %>
|
||||
<td width="10%" class="arrow end-date"><span><%= h format_date(deliverable.end_date) %></span></td>
|
||||
<td width="2%" class="type"><%= h deliverable.short_type %></td>
|
||||
<td width="25%" class="title"><%= h deliverable.title %></td>
|
||||
<td width="15%">TODO: Release 5</td>
|
||||
<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 %>
|
||||
<% end %>
|
||||
|
||||
<% if resource.deliverables.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<table class="list" cellspacing="0" border="0" cellpadding="0" id="deliverables">
|
||||
<thead>
|
||||
<th><%= l(:field_end_date) %></th>
|
||||
<th> </th>
|
||||
<th><%= l(:field_title) %></th>
|
||||
<%# TODO: Status %>
|
||||
<th><%= l(:field_manager) %></th>
|
||||
<th><%= l(:field_labor) %></th>
|
||||
<th><%= l(:field_overhead) %></th>
|
||||
<th><%= l(:field_fixed) %></th>
|
||||
<th><%= l(:button_edit) %></th>
|
||||
<th><%= l(:button_delete) %></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% resource.deliverables.each do |deliverable| %>
|
||||
<% content_tag_for(:tr, deliverable) do %>
|
||||
<td class="end-date"><%= h format_date(deliverable.end_date) %></td>
|
||||
<td class="type"><%= h deliverable.short_type %></td>
|
||||
<td class="title"><%= h deliverable.title %></td>
|
||||
<%# TODO: Status %>
|
||||
<td class="manager"><%= h deliverable.manager.try(:name) %></td>
|
||||
<td class="labor">
|
||||
<%= format_budget_for_deliverable(deliverable, deliverable.labor_budget_spent, deliverable.labor_budget_total) %>
|
||||
</td>
|
||||
<td class="overhead">
|
||||
<%= format_budget_for_deliverable(deliverable, deliverable.overhead_spent, deliverable.overhead_budget_total) %>
|
||||
</td>
|
||||
<td class="fixed">---</td>
|
||||
<td><%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, resource, deliverable), :class => 'icon icon-edit') %></td>
|
||||
<td><%= link_to(l(:button_delete), contract_deliverable_path(@project, resource, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %></td>
|
||||
|
||||
<% end %>
|
||||
<tr id="deliverable_details_<%= h(deliverable.id) %>" class="ign">
|
||||
<%= render :partial => 'deliverables/details_row', :locals => {:deliverable => deliverable, :contract => resource} %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<% end %> <%# div_for(resource) %>
|
||||
<p>TODO: Release 4, Milestones</p>
|
||||
<!--
|
||||
<div id="milestones">
|
||||
|
||||
<h3>Milestones</h3>
|
||||
<div class="options">
|
||||
<input type="checkbox" checked="checked" /> Event
|
||||
<input type="checkbox" checked="checked" /> Financial
|
||||
<input type="checkbox" checked="checked" /> Dependency
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a href="#">iCal</a>
|
||||
<a href="#">View All (37)</a>
|
||||
<a href="#" id="new-deliverable">Add New</a>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="c">Done</th>
|
||||
<th>Date</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Type</th>
|
||||
<th>Alert</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td class="c"><input type="checkbox" value="1" /></td>
|
||||
<td>11/19/2009</td>
|
||||
<td>ACME delivers branding guide</td>
|
||||
<td><span class="red"><strong>Overdue</strong></span></td>
|
||||
<td>Dependency</td>
|
||||
<td>1 day</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td class="c"><input type="checkbox" value="1" /></td>
|
||||
<td>12/01/2009</td>
|
||||
<td>Send Invoice #2</td>
|
||||
<td><span class="red"><strong>Overdue</strong></span></td>
|
||||
<td>Financial</td>
|
||||
<td>EoD</td>
|
||||
</tr>
|
||||
<tr class="odd highlight">
|
||||
<td class="c"><input type="checkbox" value="1" /></td>
|
||||
<td>12/11/2009</td>
|
||||
<td>QA Signoff on 1.2</td>
|
||||
<td>Pending</td>
|
||||
<td>Event</td>
|
||||
<td>3 days</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td class="c"><input type="checkbox" value="1" /></td>
|
||||
<td>12/15/2009</td>
|
||||
<td>Release 1.2</td>
|
||||
<td>Pending</td>
|
||||
<td>Event</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td class="c"><input type="checkbox" value="1" /></td>
|
||||
<td>12/15/2009</td>
|
||||
<td>Deliverable: Load and Release Software</td>
|
||||
<td>Pending</td>
|
||||
<td>Event</td>
|
||||
<td>BoD</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
-->
|
||||
|
||||
<p>TODO: Release 2+, history</p>
|
||||
|
||||
<% html_title "#{l(:text_contracts)} - #{h(resource.name)}" %>
|
||||
|
||||
114
app/views/deliverables/_details_row.html.erb
Normal file
114
app/views/deliverables/_details_row.html.erb
Normal file
@@ -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>
|
||||
|
||||
63
app/views/deliverables/_finance_form.html.erb
Normal file
63
app/views/deliverables/_finance_form.html.erb
Normal file
@@ -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 %>
|
||||
@@ -1,13 +1,28 @@
|
||||
<% form.inputs do %>
|
||||
<%= 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 %>
|
||||
<%= 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 :manager, :required => true, :collection => @project.users.sort %>
|
||||
|
||||
<%= 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? %>
|
||||
@@ -20,30 +35,29 @@
|
||||
<%= check_box resource.class.to_s.underscore, 'warranty_sign_off' %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'} %>
|
||||
|
||||
<% form.inputs :name => l(:text_deliverable_finances), :id => 'deliverable-finances' do %>
|
||||
<% form.semantic_fields_for :labor_budgets do |labor_budget| %>
|
||||
<% labor_budget.inputs :name => l(:field_labor), :id => 'deliverable-labor' do %>
|
||||
<%= labor_budget.input :hours, :label => l(:text_short_hours), :input_html => {:size => 10} %>
|
||||
<%= labor_budget.input :budget, :label => l(:text_dollar_sign), :input_html => {:size => 10} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% form.semantic_fields_for :overhead_budgets do |overhead_budget| %>
|
||||
<% overhead_budget.inputs :name => l(:field_overhead), :id => 'deliverable-overhead' do %>
|
||||
<%= overhead_budget.input :hours, :label => l(:text_short_hours), :input_html => {:size => 10} %>
|
||||
<%= overhead_budget.input :budget, :label => l(:text_dollar_sign), :input_html => {:size => 10} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% 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>
|
||||
|
||||
<% form.buttons do %>
|
||||
<%= form.commit_button :label => l(:button_save) %>
|
||||
<%= link_to(l(:button_cancel), cancel_path) %>
|
||||
<ol>
|
||||
<li class="commit">
|
||||
<%= submit_tag(l(:button_save)) %>
|
||||
<%= link_to(l(:button_cancel), cancel_path) %>
|
||||
</li>
|
||||
</ol>
|
||||
<% end %>
|
||||
|
||||
<%= wikitoolbar_for resource.to_underscore + '_notes' %>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<%= content_tag(:h2, h(resource.title)) %>
|
||||
<%= render :partial => 'contracts/title', :locals => {:contract => @contract} %>
|
||||
|
||||
<%= content_tag(:h2, h(l(:text_edit_deliverable_title, :title => resource.title))) %>
|
||||
|
||||
<% semantic_form_for [@project, @contract, setup_nested_deliverable_records(resource)], :url => contract_deliverable_path(@project, @contract, resource), :html => {:class => 'deliverable tabular'} do |form| %>
|
||||
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contract_path(@project, @contract)} %>
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:field_deliverable_plural)} - #{h(l(:text_edit_deliverable_title, :title => resource.title))}" %>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<%= render :partial => 'contracts/title', :locals => {:contract => @contract} %>
|
||||
|
||||
<%= content_tag(:h2, l(:text_new_deliverable)) %>
|
||||
|
||||
<% semantic_form_for [@project, @contract, setup_nested_deliverable_records(resource)], :url => contract_deliverables_path(@project, @contract), :html => {:class => 'deliverable tabular'} do |form| %>
|
||||
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contract_path(@project, @contract)} %>
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:field_deliverable_plural)} - #{l(:text_new_deliverable)}" %>
|
||||
|
||||
14
app/views/issues/_bulk_edit_deliverable.html.erb
Normal file
14
app/views/issues/_bulk_edit_deliverable.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<% if project.module_enabled?(:contracts) %>
|
||||
<p>
|
||||
<%= label_tag(:deliverable_id, l(:field_deliverable)) %>
|
||||
<% options = project.contracts.inject([]) {|data, contract|
|
||||
data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
|
||||
} %>
|
||||
|
||||
<%= select_tag('deliverable_id',
|
||||
content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_none), :value => 'none') +
|
||||
grouped_options_for_select(options)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
9
app/views/issues/_edit_deliverable.html.erb
Normal file
9
app/views/issues/_edit_deliverable.html.erb
Normal file
@@ -0,0 +1,9 @@
|
||||
<% if project.module_enabled?(:contracts) %>
|
||||
<p>
|
||||
<% options = project.contracts.inject([]) {|data, contract|
|
||||
data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
|
||||
} %>
|
||||
<%= form.select(:deliverable_id, grouped_options_for_select(options, issue.deliverable_id), {:include_blank => true}) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
7
app/views/issues/_show_deliverable.html.erb
Normal file
7
app/views/issues/_show_deliverable.html.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<% if project.module_enabled?(:contracts) %>
|
||||
<tr>
|
||||
<th class="deliverable"><%= l(:field_deliverable) %>:</th>
|
||||
<td class="deliverable"><%= h(issue.deliverable.title) if issue.deliverable.present? %></td>"
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
@@ -1,14 +1,60 @@
|
||||
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');
|
||||
|
||||
if (right_align.length > 0) {
|
||||
right_align.after().css("text-align", "right");
|
||||
}
|
||||
|
||||
$("#deliverables table tbody tr td:contains('---')").css("text-align", "center");
|
||||
|
||||
|
||||
$(".texpand").jExpand();
|
||||
|
||||
$(".texpand").find("tr.even").next('tr:first').addClass("even");
|
||||
|
||||
$(window).resize(function() {
|
||||
|
||||
});
|
||||
|
||||
$('#expand_terms').click( function(){
|
||||
$(this).next().slideToggle();
|
||||
$(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'));
|
||||
@@ -16,4 +62,55 @@ 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 */
|
||||
(function($){
|
||||
$.fn.jExpand = function(){
|
||||
var element = this;
|
||||
$(element).find("tr.ign").hide();
|
||||
|
||||
$(element).find("tr.click").click(function() {
|
||||
$(this).toggleClass("noborder");
|
||||
$(this).next("tr").toggle();
|
||||
$(this).find('.arrow').toggleClass("alt");
|
||||
|
||||
var box_height = $(this).next().find('.expanded').height();
|
||||
var table_height = $(this).next().find('.finance table').height();
|
||||
|
||||
if(box_height-table_height > 0){
|
||||
$(this).next().find('.finance table .fill td').css("padding-top", box_height-table_height+2);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
})(jQuery);
|
||||
|
||||
@@ -12,6 +12,14 @@ html>body .tabular li {overflow:hidden;}
|
||||
|
||||
.tabular .inline-hints { color:#666; margin:0.5em 0 0 0; padding: 0px; display: inline; }
|
||||
|
||||
.deliverable_finance_table .inline-hints {
|
||||
padding-top:4px;
|
||||
}
|
||||
|
||||
.no-value {
|
||||
text-align:center;
|
||||
color:#999;
|
||||
}
|
||||
.tabular li.required label { color: #484848; }
|
||||
.tabular li.required label span.required {color: #bb0000;}
|
||||
|
||||
@@ -21,11 +29,477 @@ html>body .tabular li {overflow:hidden;}
|
||||
a.contract-delete {color: red; }
|
||||
|
||||
/* Positioning */
|
||||
#deliverable-finances ol li { display: inline; }
|
||||
#deliverable-finances ol li label { float: none; margin-left: 0; }
|
||||
|
||||
#deliverables td.labor, #deliverables td.overhead, #deliverables td.fixed { text-align: center; }
|
||||
#deliverables td.labor div, #deliverables td.overhead div, #deliverables td.fixed div { text-align: right; display: inline; padding: 0 5px; width: 50%; }
|
||||
.spent, .budget { text-align: right; padding: 0 5px; }
|
||||
|
||||
span.contract-details-label { font-weight: bold; }
|
||||
|
||||
/* Design */
|
||||
.tabular li{
|
||||
padding-left: 120px; /*width of left column containing the label elements*/
|
||||
}
|
||||
|
||||
.tabular li.commit {
|
||||
padding-left: 0px; /* Don't pad submit buttons */
|
||||
}
|
||||
|
||||
#content .error_msg{
|
||||
background: #ffd5d5;
|
||||
color: #a40000;
|
||||
border: 1px solid #a40000;
|
||||
margin: 5px 0 10px 0;
|
||||
}
|
||||
|
||||
#content .error_msg p{
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#content .error_msg p span{
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.contract .gravatar{
|
||||
float: left;
|
||||
border: 1px solid #b4d1d9;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
margin-left: 6px;
|
||||
margin-right: 25px;
|
||||
|
||||
}
|
||||
|
||||
#content .contract .fixed-item,
|
||||
#content .contract .fixed-item-form {
|
||||
border-top: 1px dotted #999;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
#content .contract .fixed-items {
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
.add_fixed a{
|
||||
display: block;
|
||||
margin: 15px 0 15px 0;
|
||||
color: #2A5685;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.select_dropdown{
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
#content .contract table td,
|
||||
#content .contract table th{
|
||||
padding: 2px 5px 2px 5px;
|
||||
}
|
||||
|
||||
#deliverable-overhead ol li,
|
||||
#deliverable-labor ol li{
|
||||
padding-left: 155px
|
||||
}
|
||||
|
||||
#content table.deliverable_finance_table{
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
#content table.deliverable_finance_table select{
|
||||
width: 145px;
|
||||
}
|
||||
|
||||
#content table.deliverable_finance_table input{
|
||||
text-align: right;
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
#content table.deliverable_finance_table,
|
||||
#content table.deliverable_finance_table tr,
|
||||
#content table.deliverable_finance_table tr td{
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.deliverable_finance_table p.inline-hints label {font-weight: normal; float: none; display: auto; margin-left: 0px; } /* An inline label so it needs to be reset from .tabular */
|
||||
|
||||
#expand_terms{
|
||||
margin: 10px 0 10px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#content #contract_list table{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#content #contract_list table tbody tr td.name a{
|
||||
color: #2f8fa5;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#content #contract_list table tbody tr td.name a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#content #contract_list table tbody tr td.priority_1{
|
||||
background: #ffaaaa;
|
||||
}
|
||||
|
||||
#content .c_overview{
|
||||
background: #edf6fa;
|
||||
border: 1px solid #dcebf0;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 6px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#content .c_overview table{
|
||||
width: 230px;
|
||||
float: left;
|
||||
border: 0;
|
||||
}
|
||||
#content .c_overview table.left{
|
||||
float:left;
|
||||
width:30%;
|
||||
}
|
||||
|
||||
#content .c_overview table.middle{
|
||||
margin-right:3%;
|
||||
margin-left:3%;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
#content .c_overview table.right{
|
||||
width:30%;
|
||||
max-width:300px;
|
||||
float:right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#content .c_overview table td{
|
||||
border: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#content .c_overview table tr:hover td,
|
||||
#content .c_overview table tr:hover,
|
||||
#content .c_overview table td:hover{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#content .c_overview a.expand{
|
||||
background: url(../../../images/arrow_collapsed.png) no-repeat;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#content .c_overview a.expand.alt{
|
||||
background: url(../../../images/arrow_expanded.png) no-repeat;
|
||||
}
|
||||
|
||||
#deliverables .arrow{
|
||||
background: url(../../../images/arrow_collapsed.png) no-repeat;
|
||||
background-position: 0 6px;
|
||||
|
||||
}
|
||||
#deliverables .arrow.alt{
|
||||
background: url(../../../images/arrow_expanded.png) no-repeat;
|
||||
background-position: 0 6px;
|
||||
}
|
||||
|
||||
#deliverables .arrow span{
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#contract-terms,
|
||||
.deliverable_overview{
|
||||
background: #f6fbfd;
|
||||
border: 1px solid #d5dde1;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#contract-terms table .hr{
|
||||
border-top: 1px solid #dee0e1;
|
||||
}
|
||||
|
||||
#content #contract-terms table tr.padd td{
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#contract-terms table.info{
|
||||
width: 60%;
|
||||
background: #f6fbfd;
|
||||
}
|
||||
|
||||
#contract-terms table.finance{
|
||||
width: 30%;
|
||||
max-width:300px;
|
||||
border-left: 1px solid #bed3dd;
|
||||
float: right;
|
||||
margin: 0;
|
||||
background: #fafdfe;
|
||||
}
|
||||
|
||||
#contract-terms table.info td,
|
||||
#contract-terms table.finance td{
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#contract-terms table tr.grey td,
|
||||
#content .contract .grey{
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
#contract-terms span.red,
|
||||
#deliverables span.red,
|
||||
#milestones span.red{
|
||||
color: #990000;
|
||||
}
|
||||
|
||||
#deliverables h3,
|
||||
#milestones h3,
|
||||
#content #history h3{
|
||||
float: left;
|
||||
font-size: 22px;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#content #deliverables .actions,
|
||||
#content #milestones .actions{
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#content #deliverables .actions a,
|
||||
#content #milestones .actions a{
|
||||
font-size: 13px;
|
||||
color: #3e3e3e;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#deliverables table{
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#content #deliverables table thead tr th{
|
||||
border-right: 1px solid #e6e6e6;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#content #deliverables table thead tr{
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
#content #deliverables table th,
|
||||
#content #deliverables table td{
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
|
||||
#content #deliverables table td, #content #deliverables table th,
|
||||
#content #milestones table td, #content #milestones table th,
|
||||
#content #contract_list table td, #content #contract_list table th {
|
||||
border:1px solid #e6e6e6;
|
||||
border-style: solid;
|
||||
text-align:left;
|
||||
position: static;
|
||||
vertical-align:top;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#content #deliverables table tr.noborder td{
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
#content #deliverables table tr.ign td{
|
||||
border-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#content #deliverables table tr.ign td table td{
|
||||
padding: 2px 5px 2px 5px;
|
||||
}
|
||||
|
||||
#content #deliverables table tr{
|
||||
background-color: #edf6fa;
|
||||
}
|
||||
|
||||
#content #deliverables table tr td.white{
|
||||
background-color: #f6fafd;
|
||||
}
|
||||
|
||||
#content #deliverables table tr.even,
|
||||
#content #deliverables table tr.even:hover {
|
||||
background-color:#dff3fc;
|
||||
}
|
||||
|
||||
#content #deliverables table tr.ign tr{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
#content #deliverables div.title{
|
||||
background: #FFF;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid #e4eff4;
|
||||
}
|
||||
#content #deliverables .title a {
|
||||
margin-right: 6px;
|
||||
}
|
||||
#content #deliverables .title img{
|
||||
vertical-align: middle;
|
||||
|
||||
}
|
||||
|
||||
#content #deliverables .click td{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#content #deliverables .expanded{
|
||||
border: 1px solid #c8d0d4;
|
||||
margin: 5px;
|
||||
margin-top: 0;
|
||||
background: #f6fbfd;
|
||||
}
|
||||
|
||||
#content #deliverables .expanded fieldset{
|
||||
border: none;
|
||||
}
|
||||
|
||||
#content #deliverables .info{
|
||||
width: 45%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#content #deliverables .info p{
|
||||
margin: 10px 0 10px 10px;
|
||||
}
|
||||
#content #deliverables .info form fieldset{
|
||||
margin-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#content #deliverables .info table{
|
||||
width: auto;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
#content #deliverables .info table td{
|
||||
border: 0;
|
||||
padding: 2px 5px 2px 5px;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table{
|
||||
border-left: 1px solid #e4eff4;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table td,
|
||||
#content #deliverables .issue_status table td{
|
||||
border: 0px;
|
||||
border-right: 1px solid #e4eff4;
|
||||
text-align: right;
|
||||
padding: 2px 5px 2px 5px;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table td.l,
|
||||
#content #deliverables .issue_status table td.l{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#content #deliverables .finance{
|
||||
float: left;
|
||||
width: 39%;
|
||||
}
|
||||
#content #deliverables .finance table{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table thead th,
|
||||
#content #deliverables .issue_status table thead th{
|
||||
background: #FFF;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #e4eff4;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table thead th{
|
||||
border-right: 1px solid #e4eff4;
|
||||
}
|
||||
|
||||
#content #deliverables .finance table tbody tr.total td,
|
||||
#contract-terms table tbody tr.total td{
|
||||
border-top: 1px solid #bed3dd;
|
||||
}
|
||||
|
||||
#content #deliverables .issue_status{
|
||||
float: left;
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
#content #deliverables .issue_status table tbody tr td{
|
||||
border-right: 0;
|
||||
color: #277d94;
|
||||
text-align: left;
|
||||
}
|
||||
#content #deliverables .issue_status table tbody tr td.number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
#content #deliverables a,
|
||||
#content .c_overview a{
|
||||
color: #277d94;
|
||||
}
|
||||
|
||||
#content #milestones .options{
|
||||
float: left;
|
||||
width: 300px;
|
||||
margin-top: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#content #milestones table{
|
||||
margin: 0;padding: 0;
|
||||
}
|
||||
|
||||
#content #milestones table thead th{
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#content #milestones table tr.odd{
|
||||
background: #dff3fc;
|
||||
}
|
||||
|
||||
#content #milestones table tr.even{
|
||||
background: #edf6fa;
|
||||
}
|
||||
|
||||
#content #milestones table tr.highlight td{
|
||||
border-top: 2px solid #5fccf9;
|
||||
}
|
||||
|
||||
#content #milestones .c{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#edit_contract_1 input#contract_start_date,
|
||||
#edit_contract_1 input#contract_end_date{
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
#edit_contract_1 li#contract_billable_rate_input div{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -7,21 +7,19 @@ en:
|
||||
field_account_executive_short: "Acct. Mgr."
|
||||
field_end_date: "End Date"
|
||||
text_new_contract: "New Contract"
|
||||
text_edit_contract_name: "Edit {{name}}"
|
||||
field_billable_rate: "Billable Rate"
|
||||
field_billable_rate_hint: "$"
|
||||
field_discount: "Discount"
|
||||
field_discount_hint: "$, %"
|
||||
field_discount_note: "Discounts Notes"
|
||||
field_payment_terms: "Payment Terms"
|
||||
field_payment_term: "Payment Terms"
|
||||
field_client_ap_contact_information: "AP Contact Info"
|
||||
field_po_number: "PO Number"
|
||||
field_details: "Details"
|
||||
text_payment_terms_net_0: "Net 0"
|
||||
text_payment_terms_net_15: "Net 15"
|
||||
text_payment_terms_net_30: "Net 30"
|
||||
text_payment_terms_net_45: "Net 45"
|
||||
button_add_new: Add New
|
||||
text_new_deliverable: New Deliverable
|
||||
text_edit_deliverable_title: "Edit {{title}}"
|
||||
field_manager: Manager
|
||||
field_labor: Labor
|
||||
field_overhead: Overhead
|
||||
@@ -30,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"
|
||||
@@ -50,3 +49,20 @@ en:
|
||||
field_total_budget: "Total Budget"
|
||||
field_total_spent: "Contract Total"
|
||||
field_contract_type: "Type"
|
||||
field_deliverable: "Deliverable"
|
||||
field_deliverable_plural: "Deliverables"
|
||||
text_terms_and_financial_overview: "Contract Terms and Financial Overview"
|
||||
text_general_legend: "General"
|
||||
text_budget_legend: "Budget"
|
||||
text_account_legend: "Account Management"
|
||||
text_deliverable_details_legend: "Deliverable Details"
|
||||
text_save_contract: "Save Contract"
|
||||
enumeration_payment_term: "Payment Terms"
|
||||
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"
|
||||
|
||||
9
db/migrate/008_add_payment_term_id_to_contracts.rb
Normal file
9
db/migrate/008_add_payment_term_id_to_contracts.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class AddPaymentTermIdToContracts < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :contracts, :payment_term_id, :integer
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :contracts, :payment_term_id
|
||||
end
|
||||
end
|
||||
9
db/migrate/009_remove_payment_terms_from_contracts.rb
Normal file
9
db/migrate/009_remove_payment_terms_from_contracts.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class RemovePaymentTermsFromContracts < ActiveRecord::Migration
|
||||
def self.up
|
||||
remove_column :contracts, :payment_terms
|
||||
end
|
||||
|
||||
def self.down
|
||||
add_column :contracts, :payment_terms, :string
|
||||
end
|
||||
end
|
||||
14
db/migrate/010_populate_payment_terms.rb
Normal file
14
db/migrate/010_populate_payment_terms.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class PopulatePaymentTerms < ActiveRecord::Migration
|
||||
def self.up
|
||||
[0, 15, 30, 45, 60, 75, 90].each_with_index do |days, index|
|
||||
name = "Net #{days}"
|
||||
unless PaymentTerm.find_by_name(name)
|
||||
PaymentTerm.create!(:name => name, :position => index + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
# No-op
|
||||
end
|
||||
end
|
||||
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
|
||||
22
init.rb
22
init.rb
@@ -44,7 +44,6 @@ end
|
||||
|
||||
require 'dispatcher'
|
||||
Dispatcher.to_prepare :redmine_contracts do
|
||||
|
||||
gem 'inherited_resources', :version => '1.0.6'
|
||||
require_dependency 'inherited_resources'
|
||||
require_dependency 'inherited_resources/base'
|
||||
@@ -58,11 +57,32 @@ Dispatcher.to_prepare :redmine_contracts do
|
||||
|
||||
Formtastic::SemanticFormBuilder.all_fields_required_by_default = false
|
||||
Formtastic::SemanticFormBuilder.required_string = "<span class='required'> *</span>"
|
||||
|
||||
require_dependency 'payment_term' # Load so Enumeration will pick up the subclass in dev
|
||||
|
||||
require_dependency 'project'
|
||||
Project.send(:include, RedmineContracts::Patches::ProjectPatch)
|
||||
require_dependency 'issue'
|
||||
Issue.send(:include, RedmineContracts::Patches::IssuePatch)
|
||||
require_dependency 'query'
|
||||
unless Query.included_modules.include? RedmineContracts::Patches::QueryPatch
|
||||
Query.send(:include, RedmineContracts::Patches::QueryPatch)
|
||||
end
|
||||
|
||||
unless Query.available_columns.collect(&:name).include?(:deliverable_title)
|
||||
Query.add_available_column(QueryColumn.new(:deliverable_title, :sortable => "#{Deliverable.table_name}.title"))
|
||||
end
|
||||
|
||||
unless Query.available_columns.collect(&:name).include?(:contract_name)
|
||||
Query.add_available_column(QueryColumn.new(:contract_name, :sortable => "#{Contract.table_name}.name"))
|
||||
end
|
||||
end
|
||||
|
||||
require 'redmine_contracts/hooks/view_layouts_base_html_head_hook'
|
||||
require 'redmine_contracts/hooks/view_issues_show_details_bottom_hook'
|
||||
require 'redmine_contracts/hooks/view_issues_form_details_bottom_hook'
|
||||
require 'redmine_contracts/hooks/controller_issues_edit_before_save_hook'
|
||||
require 'redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook'
|
||||
require 'redmine_contracts/hooks/controller_issues_bulk_edit_before_save_hook'
|
||||
require 'redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook'
|
||||
require 'redmine_contracts/hooks/controller_timelog_available_criterias_hook'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'pp'
|
||||
|
||||
module RedmineContracts
|
||||
class BudgetPluginInstalledError < StandardError; end
|
||||
|
||||
@@ -7,10 +5,6 @@ module RedmineContracts
|
||||
set_table_name 'deliverables'
|
||||
end
|
||||
|
||||
# TODO: decide how this is activated.
|
||||
# Have the user dump yaml themselves? Or rename the tables and
|
||||
# SQL select the data out into yaml?
|
||||
# Deliverable.all.collect {|d| d.attributes}.to_yaml
|
||||
class BudgetPluginMigration
|
||||
@@data = nil
|
||||
|
||||
@@ -35,7 +29,9 @@ module RedmineContracts
|
||||
end
|
||||
|
||||
# * old_data - YAML string of deliverables to migrate
|
||||
def self.migrate(old_data)
|
||||
def self.migrate(old_data, options={})
|
||||
@contract_rate = options[:contract_rate] ? options[:contract_rate].to_f : 150.0
|
||||
|
||||
@@data = YAML.load(old_data)
|
||||
|
||||
# Map old deliverable ids to the new ones
|
||||
@@ -49,31 +45,33 @@ module RedmineContracts
|
||||
:end_date => old_deliverable['due'],
|
||||
:notes => old_deliverable['description']
|
||||
)
|
||||
deliverable.type = old_deliverable['type']
|
||||
# All deliverables are converted over to FixedDeliverable
|
||||
deliverable.type = 'FixedDeliverable'
|
||||
project = Project.find(old_deliverable['project_id'])
|
||||
contract = Contract.find_by_project_id(project.id)
|
||||
contract ||= create_new_contract(old_deliverable)
|
||||
|
||||
deliverable.contract = contract
|
||||
deliverable.manager = project.users.first
|
||||
|
||||
deliverable.total = old_deliverable['budget']
|
||||
|
||||
case old_deliverable['type']
|
||||
when 'FixedDeliverable'
|
||||
@total = deliverable.total = old_deliverable['fixed_cost']
|
||||
@total_cost = old_deliverable['fixed_cost']
|
||||
when 'HourlyDeliverable'
|
||||
@total = old_deliverable['total_hours'].to_f * old_deliverable['cost_per_hour'].to_f
|
||||
@total_cost = old_deliverable['total_hours'].to_f * old_deliverable['cost_per_hour'].to_f
|
||||
|
||||
if old_deliverable['total_hours'].present? || old_deliverable['cost_per_hour'].present?
|
||||
deliverable.labor_budgets << LaborBudget.new(:deliverable => deliverable,
|
||||
:budget => @total,
|
||||
:budget => @total_cost,
|
||||
:hours => old_deliverable['total_hours'])
|
||||
end
|
||||
else
|
||||
@total = 0
|
||||
@total_cost = 0
|
||||
end
|
||||
|
||||
convert_overhead(deliverable, old_deliverable, @total)
|
||||
convert_materials(deliverable, old_deliverable, @total)
|
||||
convert_overhead(deliverable, old_deliverable, @total_cost)
|
||||
convert_materials(deliverable, old_deliverable, @total_cost)
|
||||
append_old_deliverable_to_notes(old_deliverable, deliverable)
|
||||
|
||||
deliverable.save!
|
||||
@@ -82,8 +80,17 @@ module RedmineContracts
|
||||
end
|
||||
end
|
||||
|
||||
@deliverable_mapper.each do |old, new|
|
||||
Issue.update_all(["deliverable_id = ?", new], ["deliverable_id = ?", old])
|
||||
# Slower than update_all but update_all could potentially hit an issue
|
||||
# multiple times depending on the migration order. Example:
|
||||
#
|
||||
# - Issue 1 has Deliverable 1
|
||||
# - Deliverable 1 updates Issue 1 to have the new deliverable id of 3
|
||||
# - Deliverable 3 runs and updates Issue 1 again to have the new deliverable id of 5
|
||||
#
|
||||
Issue.all.each do |issue|
|
||||
next if issue.deliverable_id.blank?
|
||||
|
||||
issue.update_attribute(:deliverable_id, @deliverable_mapper[issue.deliverable_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,6 +110,7 @@ module RedmineContracts
|
||||
c.account_executive = project.users.first
|
||||
c.start_date ||= Date.today
|
||||
c.end_date ||= Date.today
|
||||
c.billable_rate = @contract_rate
|
||||
end
|
||||
|
||||
contract.save!
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ControllerIssuesBulkEditBeforeSaveHook < Redmine::Hook::ViewListener
|
||||
# Context:
|
||||
# * :issue => Issue being saved
|
||||
# * :params => HTML parameters
|
||||
#
|
||||
def controller_issues_bulk_edit_before_save(context={})
|
||||
case
|
||||
when context[:params][:deliverable_id].blank?
|
||||
# Do nothing
|
||||
when context[:params][:deliverable_id] == 'none'
|
||||
# Unassign deliverable
|
||||
context[:issue].deliverable = nil
|
||||
else
|
||||
context[:issue].deliverable = Deliverable.find(context[:params][:deliverable_id])
|
||||
end
|
||||
|
||||
return ''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ControllerIssuesEditBeforeSaveHook < Redmine::Hook::ViewListener
|
||||
def controller_issues_edit_before_save(context={})
|
||||
|
||||
if context[:params] && context[:params][:issue]
|
||||
if context[:params][:issue][:deliverable_id].present?
|
||||
deliverable = Deliverable.find_by_id(context[:params][:issue][:deliverable_id])
|
||||
if deliverable.contract.project == context[:issue].project
|
||||
context[:issue].deliverable = deliverable
|
||||
end
|
||||
|
||||
else
|
||||
context[:issue].deliverable = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return ''
|
||||
end
|
||||
|
||||
alias_method :controller_issues_new_before_save, :controller_issues_edit_before_save
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ControllerTimelogAvailableCriteriasHook < Redmine::Hook::ViewListener
|
||||
def controller_timelog_available_criterias(context={})
|
||||
context[:available_criterias]["deliverable_id"] = {
|
||||
:sql => "#{Issue.table_name}.deliverable_id",
|
||||
:klass => Deliverable,
|
||||
:label => :field_deliverable
|
||||
}
|
||||
context[:available_criterias]["contract_id"] = {
|
||||
:sql => "(SELECT deliverable.contract_id FROM #{Deliverable.table_name} deliverable WHERE deliverable.id = issues.deliverable_id)",
|
||||
:klass => Contract,
|
||||
:label => :field_contract
|
||||
}
|
||||
return ''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class HelperIssuesShowDetailAfterSettingHook < Redmine::Hook::ViewListener
|
||||
# Deliverable changes for the journal use the Deliverable subject
|
||||
# instead of the id
|
||||
#
|
||||
# Context:
|
||||
# * :detail => Detail about the journal change
|
||||
#
|
||||
def helper_issues_show_detail_after_setting(context = { })
|
||||
# TODO Later: Overwritting the caller is bad juju
|
||||
if context[:detail].prop_key == 'deliverable_id'
|
||||
d = Deliverable.find_by_id(context[:detail].value)
|
||||
context[:detail].value = d.title if d.present? && d.title.present?
|
||||
|
||||
d = Deliverable.find_by_id(context[:detail].old_value)
|
||||
context[:detail].old_value = d.title if d.present? && d.title.present?
|
||||
end
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ViewIssuesBulkEditDetailsBottomHook < Redmine::Hook::ViewListener
|
||||
|
||||
render_on(:view_issues_bulk_edit_details_bottom, :partial => 'issues/bulk_edit_deliverable', :layout => false)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ViewIssuesFormDetailsBottomHook < Redmine::Hook::ViewListener
|
||||
render_on(:view_issues_form_details_bottom, :partial => 'issues/edit_deliverable', :layout => false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
module RedmineContracts
|
||||
module Hooks
|
||||
class ViewIssuesShowDetailsBottomHook < Redmine::Hook::ViewListener
|
||||
include Redmine::I18n
|
||||
|
||||
render_on(:view_issues_show_details_bottom, :partial => 'issues/show_deliverable', :layout => false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,9 @@ module RedmineContracts
|
||||
base.class_eval do
|
||||
unloadable
|
||||
belongs_to :deliverable
|
||||
|
||||
delegate :title, :to => :deliverable, :prefix => true, :allow_nil => true
|
||||
delegate :contract_name, :to => :deliverable, :allow_nil => true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ module RedmineContracts
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
has_many :contracts
|
||||
has_many :deliverables, :through => :contracts
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
89
lib/redmine_contracts/patches/query_patch.rb
Normal file
89
lib/redmine_contracts/patches/query_patch.rb
Normal file
@@ -0,0 +1,89 @@
|
||||
module RedmineContracts
|
||||
module Patches
|
||||
module QueryPatch
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :available_filters, :deliverable
|
||||
alias_method_chain :available_filters, :contract
|
||||
|
||||
alias_method_chain :sql_for_field, :contract
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# TODO: Should have an API on the Redmine core for this
|
||||
def available_filters_with_deliverable
|
||||
@available_filters = available_filters_without_deliverable
|
||||
|
||||
if project
|
||||
deliverable_filters = {
|
||||
"deliverable_id" => {
|
||||
:type => :list_optional,
|
||||
:order => 15,
|
||||
:values => project.deliverables.by_title.collect { |d| [d.title, d.id.to_s] }
|
||||
}
|
||||
}
|
||||
return @available_filters.merge(deliverable_filters)
|
||||
else
|
||||
return @available_filters
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# TODO: Should have an API on the Redmine core for this
|
||||
def available_filters_with_contract
|
||||
@available_filters = available_filters_without_contract
|
||||
|
||||
if project
|
||||
contract_filters = {
|
||||
"contract_id" => {
|
||||
:type => :list_optional,
|
||||
:order => 16,
|
||||
:values => project.contracts.by_name.collect { |d| [d.name, d.id.to_s] }
|
||||
}
|
||||
}
|
||||
return @available_filters.merge(contract_filters)
|
||||
else
|
||||
return @available_filters
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def sql_for_field_with_contract(field, operator, value, db_table, db_field, is_custom_filter=false)
|
||||
if field != "contract_id"
|
||||
return sql_for_field_without_contract(field, operator, value, db_table, db_field, is_custom_filter)
|
||||
else
|
||||
# Contracts > Deliverables > Issue
|
||||
case operator
|
||||
when "="
|
||||
contracts = value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",")
|
||||
inner_select = "(SELECT id from deliverables where deliverables.contract_id IN (#{contracts}))"
|
||||
sql = "#{Issue.table_name}.deliverable_id IN (#{inner_select})"
|
||||
when "!"
|
||||
contracts = value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",")
|
||||
inner_select = "(SELECT id from deliverables where deliverables.contract_id IN (#{contracts}))"
|
||||
sql = "(#{Issue.table_name}.deliverable_id IS NULL OR #{Issue.table_name}.deliverable_id NOT IN (#{inner_select}))"
|
||||
when "!*"
|
||||
# If it doesn't have a deliverable, it can't have a contract
|
||||
sql = "#{Issue.table_name}.deliverable_id IS NULL"
|
||||
when "*"
|
||||
# If it has a deliverable, it must have a contract
|
||||
sql = "#{Issue.table_name}.deliverable_id IS NOT NULL"
|
||||
end
|
||||
|
||||
return sql
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,13 @@
|
||||
namespace :redmine_contracts do
|
||||
desc "Migrate data from the budget_plugin to redmine_contracts"
|
||||
task :budget_migration => :environment do
|
||||
options = {}
|
||||
options[:contract_rate] = ENV['contract_rate']
|
||||
|
||||
RedmineContracts::BudgetPluginMigration.check_for_installed_budget_plugin
|
||||
data = RedmineContracts::BudgetPluginMigration.export_data
|
||||
RedmineContracts::BudgetPluginMigration.rename_old_tables
|
||||
RedmineContracts::BudgetPluginMigration.migrate_contracts
|
||||
RedmineContracts::BudgetPluginMigration.migrate(data)
|
||||
RedmineContracts::BudgetPluginMigration.migrate(data, options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,8 +35,8 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
|
||||
should "create a new Deliverable for each old Deliverable" do
|
||||
|
||||
assert_difference("Deliverable.count", 3) do
|
||||
assert_difference("HourlyDeliverable.count", 2) do
|
||||
assert_difference("FixedDeliverable.count", 1) do
|
||||
assert_difference("HourlyDeliverable.count", 0) do
|
||||
assert_difference("FixedDeliverable.count", 3) do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
end
|
||||
end
|
||||
@@ -53,6 +53,22 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
|
||||
assert_equal 2, @project_two.reload.contracts.first.deliverables.count
|
||||
end
|
||||
|
||||
context "on new Contracts" do
|
||||
should "default the contract billable rate to $150" do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
|
||||
assert_equal 150, @project_one.reload.contracts.first.billable_rate
|
||||
assert_equal 150, @project_two.reload.contracts.first.billable_rate
|
||||
end
|
||||
|
||||
should "allow overriding the contract billable rate" do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data, :contract_rate => '100.50')
|
||||
|
||||
assert_equal 100.5, @project_one.reload.contracts.first.billable_rate
|
||||
assert_equal 100.5, @project_two.reload.contracts.first.billable_rate
|
||||
end
|
||||
end
|
||||
|
||||
should "enable the contracts plugin for each project with a contract" do
|
||||
@no_deliverables = Project.generate!(:enabled_modules => [])
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
@@ -136,15 +152,28 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
|
||||
end
|
||||
|
||||
context "converting Fixed Deliverables" do
|
||||
should "convert fixed_cost to total" do
|
||||
should "convert the budget field to total" do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
|
||||
d = FixedDeliverable.find_by_title("Version 1.0")
|
||||
assert_equal 30_000, d.total
|
||||
assert_equal 93_000, d.total
|
||||
end
|
||||
end
|
||||
|
||||
context "converting Hourly Deliverables" do
|
||||
should "convert over into Fixed Deliverables" do
|
||||
assert_difference("FixedDeliverable.count",3) do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
end
|
||||
end
|
||||
|
||||
should "convert the old 'budget' field into the total" do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
|
||||
assert_equal 5600, FixedDeliverable.find_by_title("Deliverable One").total
|
||||
assert_equal 900, FixedDeliverable.find_by_title("Deliverable 2").total
|
||||
end
|
||||
|
||||
should "create a new Labor Budget" do
|
||||
assert_difference("LaborBudget.count", 2) do
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
@@ -175,6 +204,7 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
|
||||
setup do
|
||||
@issue1 = Issue.generate_for_project!(@project_two, :deliverable_id => 2)
|
||||
@issue2 = Issue.generate_for_project!(@project_two, :deliverable_id => 4)
|
||||
@issue3 = Issue.generate_for_project!(@project_one, :deliverable_id => 1)
|
||||
|
||||
end
|
||||
|
||||
@@ -189,7 +219,9 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
|
||||
RedmineContracts::BudgetPluginMigration.migrate(@data)
|
||||
|
||||
# The "third" deliverable has an id of 4
|
||||
assert_equal "Deliverable 2", @issue1.reload.deliverable.title
|
||||
assert_equal "Version 1.0", @issue2.reload.deliverable.title
|
||||
assert_equal "Deliverable One", @issue3.reload.deliverable.title
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class ContractsDeleteTest < ActionController::IntegrationTest
|
||||
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract')
|
||||
end
|
||||
|
||||
should "allow admins to delete the contract" do
|
||||
|
||||
@@ -5,7 +5,10 @@ class ContractsEditTest < ActionController::IntegrationTest
|
||||
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
|
||||
@account_executive = User.generate!
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@account_executive, @project, @role)
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :account_executive => @account_executive)
|
||||
end
|
||||
|
||||
should "allow any user to edit the contract" do
|
||||
@@ -17,16 +20,14 @@ class ContractsEditTest < ActionController::IntegrationTest
|
||||
assert_response :success
|
||||
assert_template 'contracts/edit'
|
||||
|
||||
assert_select "h2", :text => @contract.name
|
||||
assert_select "h2", :text => /#{@contract.name}/
|
||||
assert_select "form#edit_contract_#{@contract.id}.contract" do
|
||||
assert_select "input[value=?]", /#{@contract.name}/
|
||||
assert_select "select#contract_payment_terms" do
|
||||
assert_select "option[selected=selected][value=net_15]"
|
||||
end
|
||||
assert_select "select#contract_payment_term_id"
|
||||
end
|
||||
|
||||
fill_in "Name", :with => 'An updated name'
|
||||
click_button "Update Contract"
|
||||
click_button "Save Contract"
|
||||
|
||||
assert_response :success
|
||||
assert_template 'contracts/show'
|
||||
|
||||
@@ -5,6 +5,8 @@ class ContractsNewTest < ActionController::IntegrationTest
|
||||
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
PaymentTerm.generate!(:type => 'PaymentTerm', :name => 'Net 15')
|
||||
PaymentTerm.generate!(:type => 'PaymentTerm', :name => 'Net 30')
|
||||
end
|
||||
|
||||
should "allow any user to open the new contracts form" do
|
||||
@@ -17,6 +19,8 @@ class ContractsNewTest < ActionController::IntegrationTest
|
||||
|
||||
should "create a new contract" do
|
||||
@account_executive = User.generate!
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@account_executive, @project, @role)
|
||||
|
||||
visit_contracts_for_project(@project)
|
||||
click_link 'New Contract'
|
||||
@@ -28,7 +32,7 @@ class ContractsNewTest < ActionController::IntegrationTest
|
||||
fill_in "End Date", :with => '2010-12-31'
|
||||
select "Net 30", :from => "Payment Terms"
|
||||
|
||||
click_button "Create Contract"
|
||||
click_button "Save Contract"
|
||||
|
||||
assert_response :success
|
||||
assert_template 'contracts/show'
|
||||
@@ -38,7 +42,7 @@ class ContractsNewTest < ActionController::IntegrationTest
|
||||
assert_equal @account_executive, @contract.account_executive
|
||||
assert_equal '2010-01-01', @contract.start_date.to_s
|
||||
assert_equal '2010-12-31', @contract.end_date.to_s
|
||||
assert_equal 'net_30', @contract.payment_terms
|
||||
assert_equal 'Net 30', @contract.payment_term.name
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ class ContractsShowTest < ActionController::IntegrationTest
|
||||
assert_template 'contracts/show'
|
||||
assert_equal "/projects/main/contracts/#{@contract.id}", current_url
|
||||
|
||||
assert_select "div#contract_#{@contract.id}.contract" do
|
||||
assert_select "div.title-bar" do
|
||||
assert_select 'h2', :text => @contract.name
|
||||
end
|
||||
end
|
||||
@@ -119,7 +119,7 @@ class ContractsShowTest < ActionController::IntegrationTest
|
||||
|
||||
visit_contract_page(@contract)
|
||||
assert_select "table#deliverables" do
|
||||
assert_select "td.labor", :text => /4,200.50/
|
||||
assert_select "td.labor", :text => /4,201/
|
||||
end
|
||||
|
||||
end
|
||||
@@ -137,7 +137,7 @@ class ContractsShowTest < ActionController::IntegrationTest
|
||||
|
||||
visit_contract_page(@contract)
|
||||
assert_select "table#deliverables" do
|
||||
assert_select "td.overhead", :text => /4,200.50/
|
||||
assert_select "td.overhead", :text => /4,201/
|
||||
end
|
||||
|
||||
end
|
||||
@@ -171,7 +171,7 @@ class ContractsShowTest < ActionController::IntegrationTest
|
||||
|
||||
visit_contract_page(@contract)
|
||||
assert_select "table#deliverables" do
|
||||
assert_select "td.labor", :text => /1,000.00/
|
||||
assert_select "td.labor", :text => /1,000/
|
||||
end
|
||||
|
||||
end
|
||||
@@ -205,8 +205,44 @@ class ContractsShowTest < ActionController::IntegrationTest
|
||||
|
||||
visit_contract_page(@contract)
|
||||
assert_select "table#deliverables" do
|
||||
assert_select "td.overhead", :text => /2,000.00/
|
||||
assert_select "td.overhead", :text => /2,000/
|
||||
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
|
||||
|
||||
50
test/integration/deliverable_details_test.rb
Normal file
50
test/integration/deliverable_details_test.rb
Normal file
@@ -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
|
||||
@@ -5,7 +5,7 @@ class DeliverablesDeleteTest < ActionController::IntegrationTest
|
||||
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract')
|
||||
@manager = User.generate!
|
||||
@deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager)
|
||||
end
|
||||
@@ -13,7 +13,7 @@ class DeliverablesDeleteTest < ActionController::IntegrationTest
|
||||
should "allow anyone to delete the deliverable" do
|
||||
visit_contract_page(@contract)
|
||||
|
||||
click_link_within "#fixed_deliverable_#{@deliverable.id}", 'Delete'
|
||||
click_link_within "#deliverable_details_#{@deliverable.id}", 'Delete'
|
||||
assert_response :success
|
||||
assert_template 'contracts/show'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
|
||||
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract')
|
||||
@manager = User.generate!
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@@ -16,7 +16,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
|
||||
|
||||
should "allow any user to edit the Fixed deliverable" do
|
||||
visit_contract_page(@contract)
|
||||
click_link_within "#fixed_deliverable_#{@fixed_deliverable.id}", 'Edit'
|
||||
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
|
||||
assert_response :success
|
||||
assert_template 'deliverables/edit'
|
||||
|
||||
@@ -45,7 +45,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
|
||||
|
||||
should "allow any user to edit the Hourly deliverable" do
|
||||
visit_contract_page(@contract)
|
||||
click_link_within "#hourly_deliverable_#{@hourly_deliverable.id}", 'Edit'
|
||||
click_link_within "#deliverable_details_#{@hourly_deliverable.id}", 'Edit'
|
||||
assert_response :success
|
||||
assert_template 'deliverables/edit'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -3,7 +3,7 @@ require 'test_helper'
|
||||
class OverheadPluginIntegrationTest < ActionController::IntegrationTest
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract')
|
||||
@manager = User.generate!
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ControllerIssuesBulkEditBeforeSaveHookTest < ActionController::IntegrationTest
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
context "#view_issues_bulk_edit_details_bottom" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
@issue2 = Issue.generate_for_project!(@project)
|
||||
@issue3 = Issue.generate_for_project!(@project)
|
||||
@issues = [@issue, @issue2, @issue3]
|
||||
@contract1 = Contract.generate!(:project => @project)
|
||||
@contract2 = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
|
||||
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title 1')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title 2')
|
||||
@issue.deliverable = @deliverable1
|
||||
|
||||
login_as('manager', 'existing')
|
||||
end
|
||||
|
||||
context "when saving multiple issues" do
|
||||
setup do
|
||||
visit_issue_bulk_edit_page(@issues)
|
||||
end
|
||||
|
||||
should "allow clearing all of the deliverables" do
|
||||
select "none", :from => "Deliverable"
|
||||
click_button "Submit"
|
||||
|
||||
assert_response :success
|
||||
|
||||
@issues.each do |issue|
|
||||
assert_equal nil, issue.reload.deliverable
|
||||
end
|
||||
end
|
||||
|
||||
should "allow assigning a deliverable" do
|
||||
select @deliverable2.title, :from => "Deliverable"
|
||||
click_button "Submit"
|
||||
|
||||
assert_response :success
|
||||
|
||||
@issues.each do |issue|
|
||||
assert_equal @deliverable2, issue.reload.deliverable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionController::IntegrationTest
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
context "#controller_issues_edit_before_save" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
IssueStatus.generate!(:is_default => true)
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
@contract1 = Contract.generate!(:project => @project)
|
||||
@contract2 = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing', :admin => true)
|
||||
@role = Role.generate!(:permissions => [:view_issues, :add_issues, :edit_issues])
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title for 1')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title for 2')
|
||||
|
||||
login_as('manager', 'existing')
|
||||
@project.reload
|
||||
end
|
||||
|
||||
context "for a new issue" do
|
||||
setup do
|
||||
visit_project(@project)
|
||||
click_link "New issue"
|
||||
end
|
||||
|
||||
should "set the issue's deliverable" do
|
||||
fill_in "Subject", :with => 'Hook test'
|
||||
select @deliverable2.title, :from => "Deliverable"
|
||||
click_button "Create"
|
||||
|
||||
assert_response :success
|
||||
|
||||
assert_equal @deliverable2, Issue.last.deliverable
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context "for an existing issue" do
|
||||
setup do
|
||||
visit_issue_page(@issue)
|
||||
end
|
||||
|
||||
should "update the issue's deliverable" do
|
||||
select @deliverable2.title, :from => "Deliverable"
|
||||
click_button "Submit"
|
||||
|
||||
assert_response :success
|
||||
|
||||
@issue.reload
|
||||
assert_equal @deliverable2, @issue.deliverable
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::HelperIssuesShowDetailAfterSettingHookTest < ActionController::IntegrationTest
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
context "#helper_issues_show_detail_after_setting" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
|
||||
@contract1 = Contract.generate!(:project => @project)
|
||||
@contract2 = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
|
||||
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title 1')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title 2')
|
||||
# Set first
|
||||
@issue.init_journal(@manager)
|
||||
@issue.deliverable = @deliverable1
|
||||
@issue.save!
|
||||
# Change
|
||||
@issue.init_journal(@manager)
|
||||
@issue.deliverable = @deliverable2
|
||||
@issue.save!
|
||||
# Unset
|
||||
@issue.init_journal(@manager)
|
||||
@issue.deliverable = nil
|
||||
@issue.save!
|
||||
|
||||
login_as('manager', 'existing')
|
||||
|
||||
visit_issue_page(@issue)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
should "show when a deliverable is set" do
|
||||
assert_select ".details" do
|
||||
assert_select "li", :text => /Deliverable set to #{@deliverable1.title}/
|
||||
end
|
||||
end
|
||||
|
||||
should "show when a deliverable is changed" do
|
||||
assert_select ".details" do
|
||||
assert_select "li", :text => /Deliverable changed from #{@deliverable1.title} to #{@deliverable2.title}/
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
should "show when a deliverable is removed" do
|
||||
assert_select ".details" do
|
||||
assert_select "li", :text => /Deliverable deleted .*#{@deliverable2.title}/
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionController::IntegrationTest
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
context "#view_issues_bulk_edit_details_bottom" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
@issue2 = Issue.generate_for_project!(@project)
|
||||
@issue3 = Issue.generate_for_project!(@project)
|
||||
@contract1 = Contract.generate!(:project => @project)
|
||||
@contract2 = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
|
||||
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
|
||||
@issue.deliverable = @deliverable1
|
||||
|
||||
login_as('manager', 'existing')
|
||||
end
|
||||
|
||||
context "with Contracts Enabled" do
|
||||
setup do
|
||||
visit_issue_bulk_edit_page([@issue, @issue2, @issue3])
|
||||
end
|
||||
|
||||
should "render the a select field for the deliverables with all of the deliverables grouped by contract" do
|
||||
|
||||
assert_select "select#deliverable_id" do
|
||||
assert_select "optgroup[label=?]", @contract1.name do
|
||||
assert_select "option", :text => /#{@deliverable1.title}/
|
||||
end
|
||||
|
||||
assert_select "optgroup[label=?]", @contract2.name do
|
||||
assert_select "option", :text => /#{@deliverable2.title}/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with Contracts Disabled" do
|
||||
setup do
|
||||
@project.enabled_modules.collect {|m| m.destroy if m.name == 'contracts' }
|
||||
visit_issue_bulk_edit_page([@issue, @issue2, @issue3])
|
||||
end
|
||||
|
||||
should "not render the deliverable select field" do
|
||||
assert_select 'select#deliverable_id', :count => 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
require File.dirname(__FILE__) + '/../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionController::IntegrationTest
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
context "#view_issues_form_details_bottom" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
@contract1 = Contract.generate!(:project => @project)
|
||||
@contract2 = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
|
||||
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
|
||||
@issue.deliverable = @deliverable1
|
||||
|
||||
login_as('manager', 'existing')
|
||||
end
|
||||
|
||||
context "with Contracts Enabled" do
|
||||
setup do
|
||||
visit_issue_page(@issue)
|
||||
end
|
||||
|
||||
should "render the a select field for the deliverables with all of the deliverables grouped by contract" do
|
||||
assert_select "select#issue_deliverable_id" do
|
||||
assert_select "optgroup[label=?]", @contract1.name do
|
||||
assert_select "option", :text => /#{@deliverable1.title}/
|
||||
end
|
||||
|
||||
assert_select "optgroup[label=?]", @contract2.name do
|
||||
assert_select "option", :text => /#{@deliverable2.title}/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with Contracts Disabled" do
|
||||
setup do
|
||||
@project.enabled_modules.collect {|m| m.destroy if m.name == 'contracts' }
|
||||
visit_issue_page(@issue)
|
||||
end
|
||||
|
||||
should "not render the deliverable select field" do
|
||||
assert_select 'select#issue_deliverable_id', :count => 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
test/performance/contract_show_test.rb
Normal file
50
test/performance/contract_show_test.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
require 'test_helper'
|
||||
require 'performance_test_help'
|
||||
|
||||
# Performance logs
|
||||
#
|
||||
class ContractShowTest < ActionController::PerformanceTest
|
||||
def setup
|
||||
@project = Project.generate!(:identifier => 'main').reload
|
||||
@contract = Contract.generate!(:project => @project)
|
||||
@manager = User.generate!(:login => 'user', :password => 'password', :password_confirmation => 'password')
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@fixed_deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'The Title')
|
||||
@hourly_deliverable = HourlyDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'An Hourly')
|
||||
|
||||
configure_overhead_plugin
|
||||
100.times do
|
||||
generate_issues_and_time_entries_for_deliverable(@hourly_deliverable, @project)
|
||||
generate_issues_and_time_entries_for_deliverable(@fixed_deliverable, @project)
|
||||
end
|
||||
|
||||
# Load the app
|
||||
login_as 'user', 'password'
|
||||
visit_contracts_for_project(@project)
|
||||
end
|
||||
|
||||
def test_contract_show
|
||||
click_link @contract.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_issues_and_time_entries_for_deliverable(deliverable, project)
|
||||
@issue1 = Issue.generate_for_project!(project)
|
||||
@time_entry1 = TimeEntry.generate!(:issue => @issue1,
|
||||
:project => project,
|
||||
:activity => @billable_activity,
|
||||
:spent_on => Date.today,
|
||||
:hours => 10,
|
||||
:user => @manager)
|
||||
@time_entry2 = TimeEntry.generate!(:issue => @issue1,
|
||||
:project => project,
|
||||
:activity => @non_billable_activity,
|
||||
:spent_on => Date.today,
|
||||
:hours => 20,
|
||||
:user => @manager)
|
||||
deliverable.issues << @issue1
|
||||
|
||||
end
|
||||
end
|
||||
@@ -71,6 +71,14 @@ module IntegrationTestHelper
|
||||
assert_template 'contracts/show'
|
||||
end
|
||||
|
||||
def visit_issue_page(issue)
|
||||
visit '/issues/' + issue.id.to_s
|
||||
end
|
||||
|
||||
def visit_issue_bulk_edit_page(issues)
|
||||
visit url_for(:controller => 'issues', :action => 'bulk_edit', :ids => issues.collect(&:id))
|
||||
end
|
||||
|
||||
def assert_forbidden
|
||||
assert_response :forbidden
|
||||
assert_template 'common/403'
|
||||
|
||||
@@ -3,6 +3,7 @@ require File.dirname(__FILE__) + '/../test_helper'
|
||||
class ContractTest < ActiveSupport::TestCase
|
||||
should_belong_to :account_executive
|
||||
should_belong_to :project
|
||||
should_belong_to :payment_term
|
||||
should_have_many :deliverables
|
||||
|
||||
should_validate_presence_of :name
|
||||
@@ -25,8 +26,6 @@ class ContractTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
should "QUESTION: name be unique"
|
||||
|
||||
should "default executed to false" do
|
||||
@contract = Contract.new
|
||||
|
||||
|
||||
@@ -33,4 +33,5 @@ class DeliverableTest < ActiveSupport::TestCase
|
||||
assert_equal 20100.00, d.total.to_f
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ControllerTimelogAvailableCriteriasTest < ActionController::TestCase
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
def controller
|
||||
@controller ||= ApplicationController.new
|
||||
@controller.response ||= ActionController::TestResponse.new
|
||||
@controller
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
def hook(args={})
|
||||
call_hook :controller_timelog_available_criterias, args
|
||||
end
|
||||
|
||||
def context
|
||||
@context ||= {
|
||||
:available_criterias => {"existing" => {:label => 'existing'}}
|
||||
}
|
||||
end
|
||||
|
||||
context "#controller_timelog_available_criterias" do
|
||||
should "return an empty string" do
|
||||
@response.body = hook(context)
|
||||
assert @response.body.blank?
|
||||
end
|
||||
|
||||
context "Deliverables" do
|
||||
should "add a deliverable_id to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert context[:available_criterias]['deliverable_id']
|
||||
end
|
||||
|
||||
should "add the deliverable sql to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert "issues.deliverable_id", context[:available_criterias]['deliverable_id'][:sql]
|
||||
end
|
||||
|
||||
should "add the deliverable Class to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert Deliverable, context[:available_criterias]['deliverable_id'][:klass]
|
||||
end
|
||||
|
||||
should "add the deliverable label to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert :field_deliverable, context[:available_criterias]['deliverable_id'][:label]
|
||||
end
|
||||
end
|
||||
|
||||
context "Contracts" do
|
||||
should "add a contract_id to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert context[:available_criterias]['contract_id']
|
||||
end
|
||||
|
||||
should "add the contact sql to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert "issues.deliverable_id", context[:available_criterias]['contract_id'][:sql]
|
||||
end
|
||||
|
||||
should "add the deliverable Class to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert Contract, context[:available_criterias]['contract_id'][:klass]
|
||||
end
|
||||
|
||||
should "add the deliverable label to the available criterias" do
|
||||
@response.body = hook(context)
|
||||
assert :field_contract, context[:available_criterias]['contract_id'][:label]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||
|
||||
class RedmineContracts::Hooks::ViewIssuesShowDetailsBottomTest < ActionController::TestCase
|
||||
include Redmine::Hook::Helper
|
||||
|
||||
# Bloody bloody hack to work around Rails coupling of _VC.
|
||||
def template
|
||||
t = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
|
||||
def t.template_format
|
||||
"html"
|
||||
end
|
||||
|
||||
# Rendered views aren't getting access to the controller's I18n module
|
||||
t.send(:extend, Redmine::I18n)
|
||||
t
|
||||
end
|
||||
|
||||
def controller
|
||||
@controller ||= ApplicationController.new
|
||||
@controller.class.send(:include, ::Redmine::I18n)
|
||||
@controller.response ||= ActionController::TestResponse.new
|
||||
# Hack to support render_on
|
||||
@controller.instance_variable_set('@template', template)
|
||||
@controller.response = response
|
||||
@controller
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
# Hack to support render_on
|
||||
def response
|
||||
@response.template ||= template
|
||||
@response
|
||||
end
|
||||
|
||||
def hook(args={})
|
||||
call_hook :view_issues_show_details_bottom, args
|
||||
end
|
||||
|
||||
context "#view_issues_show_details_bottom" do
|
||||
setup do
|
||||
@project = Project.generate!
|
||||
@issue = Issue.generate_for_project!(@project)
|
||||
@contract = Contract.generate!(:project => @project)
|
||||
|
||||
@manager = User.generate!
|
||||
@role = Role.generate!
|
||||
User.add_to_project(@manager, @project, @role)
|
||||
@deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'The Title')
|
||||
@issue.deliverable = @deliverable
|
||||
end
|
||||
|
||||
context "with Contracts Enabled" do
|
||||
should "render the deliverable's name" do
|
||||
@response.body = hook(:project => @project, :issue => @issue, :controller => controller)
|
||||
|
||||
assert_select "tr" do
|
||||
assert_select "td", :text => /#{@deliverable.title}/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with Contracts Disabled" do
|
||||
setup do
|
||||
@project.enabled_modules.destroy_all
|
||||
end
|
||||
|
||||
should "not render the deliverable's name" do
|
||||
@response.body = hook(:project => @project, :issue => @issue, :controller => controller)
|
||||
|
||||
assert_no_match /#{@deliverable.title}/, @response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,5 +5,6 @@ class RedmineContracts::Patches::ProjectTest < ActionController::TestCase
|
||||
context "Project" do
|
||||
subject { Project.new }
|
||||
should_have_many :contracts
|
||||
should_have_many :deliverables
|
||||
end
|
||||
end
|
||||
|
||||
83
test/unit/lib/redmine_contracts/patches/query_patch_test.rb
Normal file
83
test/unit/lib/redmine_contracts/patches/query_patch_test.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
require File.dirname(__FILE__) + '/../../../../test_helper'
|
||||
|
||||
class RedmineContracts::Patches::QueryTest < ActionController::TestCase
|
||||
|
||||
context "Query" do
|
||||
subject {Query.new}
|
||||
|
||||
context "#available_filters with project" do
|
||||
setup do
|
||||
@query = Query.new
|
||||
@query.project = @project = Project.generate!
|
||||
@contract = Contract.generate!(:project => @project, :name => 'A Contract')
|
||||
@manager = User.generate!
|
||||
@deliverable1 = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'One')
|
||||
@deliverable2 = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'Two')
|
||||
end
|
||||
|
||||
should "add a deliverable_id filter" do
|
||||
filters = @query.available_filters
|
||||
|
||||
assert filters.keys.include?("deliverable_id")
|
||||
|
||||
deliverable_filter = filters["deliverable_id"]
|
||||
assert_equal :list_optional, deliverable_filter[:type]
|
||||
assert_equal [
|
||||
["One", @deliverable1.id.to_s],
|
||||
["Two", @deliverable2.id.to_s]
|
||||
], deliverable_filter[:values]
|
||||
end
|
||||
|
||||
should "add a contract_id filter" do
|
||||
filters = @query.available_filters
|
||||
|
||||
assert filters.keys.include?("contract_id")
|
||||
|
||||
contract_filter = filters["contract_id"]
|
||||
assert_equal :list_optional, contract_filter[:type]
|
||||
assert_equal [["A Contract", @contract.id.to_s]], contract_filter[:values]
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Dragons in this test
|
||||
context "#sql_for_field_with_contract" do
|
||||
context "for contract_id fields" do
|
||||
setup do
|
||||
@query = Query.new
|
||||
end
|
||||
|
||||
context "with the equal operator" do
|
||||
should "return the SQL snippet for checking for deliverables on the specific contracts" do
|
||||
sql = @query.send(:sql_for_field, 'contract_id', '=', ['1','2'], '', '')
|
||||
assert_equal "issues.deliverable_id IN ((SELECT id from deliverables where deliverables.contract_id IN ('1','2')))", sql
|
||||
end
|
||||
end
|
||||
|
||||
context "with is not operator" do
|
||||
should "return the SQL snippet for checking for null deliverables or deliverables no on the specific contracts" do
|
||||
sql = @query.send(:sql_for_field, 'contract_id', '!', ['1','2'], '', '')
|
||||
assert_equal "(issues.deliverable_id IS NULL OR issues.deliverable_id NOT IN ((SELECT id from deliverables where deliverables.contract_id IN ('1','2'))))", sql
|
||||
end
|
||||
end
|
||||
|
||||
context "with none operator" do
|
||||
should "return the SQL snippet for checking for null deliverables" do
|
||||
sql = @query.send(:sql_for_field, 'contract_id', '!*', '', '', '')
|
||||
assert_equal "issues.deliverable_id IS NULL", sql
|
||||
end
|
||||
end
|
||||
|
||||
context "with all operator" do
|
||||
should "return the SQL snippet for checking for not null deliverables" do
|
||||
sql = @query.send(:sql_for_field, 'contract_id', '*', '', '', '')
|
||||
assert_equal "issues.deliverable_id IS NOT NULL", sql
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
40
test/unit/payment_term_test.rb
Normal file
40
test/unit/payment_term_test.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class PaymentTermTest < ActiveSupport::TestCase
|
||||
include Redmine::I18n
|
||||
|
||||
should_have_many(:contracts)
|
||||
|
||||
should "be a subclass of Enumeration" do
|
||||
assert_equal Enumeration, PaymentTerm.superclass
|
||||
end
|
||||
|
||||
context "#option_name" do
|
||||
should "be Payment Terms" do
|
||||
assert_equal "Payment Terms", l(PaymentTerm.new.option_name)
|
||||
end
|
||||
end
|
||||
|
||||
context "#objects_count" do
|
||||
should "count the number of contracts with this payment term" do
|
||||
@payment_term = PaymentTerm.generate!(:type => 'PaymentTerm')
|
||||
Contract.generate!(:payment_term => @payment_term)
|
||||
Contract.generate!(:payment_term => @payment_term)
|
||||
|
||||
assert_equal 2, @payment_term.objects_count
|
||||
end
|
||||
end
|
||||
|
||||
context "#transfer_relations" do
|
||||
should "update all contracts to use a new PaymentTerm" do
|
||||
@old_payment_term = PaymentTerm.generate!(:type => 'PaymentTerm')
|
||||
@new_payment_term = PaymentTerm.generate!(:type => 'PaymentTerm')
|
||||
@contract1 = Contract.generate!(:payment_term => @old_payment_term)
|
||||
@contract2 = Contract.generate!(:payment_term => @old_payment_term)
|
||||
|
||||
@old_payment_term.transfer_relations(@new_payment_term)
|
||||
assert_equal @new_payment_term, @contract1.reload.payment_term
|
||||
assert_equal @new_payment_term, @contract2.reload.payment_term
|
||||
end
|
||||
end
|
||||
end
|
||||
241
test/unit/retainer_deliverable_test.rb
Normal file
241
test/unit/retainer_deliverable_test.rb
Normal 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
|
||||
Reference in New Issue
Block a user