14 Commits
Author SHA1 Message Date
Eric Davis 437909b621 [#4272] Added a performance test for showing a contract to profile the code. 2010-08-16 12:26:28 -07:00
Eric Davis 720da2c554 [#4389] Added the Contract filter to the Time Entry report. 2010-08-16 09:54:09 -07:00
Eric Davis 620eaa262f [#4389] Add the Deliverable filter to the Time Entry report. 2010-08-16 09:39:27 -07:00
Eric Davis a3e1aa277f [#4390] Added a Contract filter to the issues list. 2010-08-12 12:04:29 -07:00
Eric Davis 25436910fb [#4391] Add a Deliverable filter to the issues list. 2010-08-12 11:27:33 -07:00
Eric Davis 6924b32048 [#4390] Add the Contract name as an issue column. 2010-08-12 10:38:48 -07:00
Eric Davis b83696f49a [#4391] Add the Deliverable title as an issue column. 2010-08-12 10:36:01 -07:00
Eric Davis 8906639273 [#4412] Added HTML page titles. 2010-08-12 10:25:14 -07:00
Eric Davis dfcc31c83e [#4411] Format the Contracts list too. 2010-08-12 10:06:43 -07:00
Eric Davis fa12fcd129 [#4411] Round currency to 0 decimal places on the Contracts page. 2010-08-12 10:03:37 -07:00
Eric Davis 82d46bd12a [#4411] Round time and currencies on the Deliverable form to 2 decimal places. 2010-08-12 09:49:45 -07:00
Eric Davis a2b810a4d8 [#4410] Replace Payment Terms with an Enumeration
Instead of using a hard coded value for Payment Terms, they will use
Redmine's Enumeration table.  This provides an admin gui to managing
the value as well as ordering them.
2010-08-12 09:26:09 -07:00
Eric Davis c17e8eaf1b [#4409] Old HourlyDeliverables should be converted over to FixedDeliverables. 2010-08-12 08:11:11 -07:00
Eric Davis e734430deb [#4409] Convert contracts with the billable rate of 50. 2010-08-12 08:00:31 -07:00
37 changed files with 566 additions and 77 deletions
+11 -3
View File
@@ -10,8 +10,8 @@ module ContractsHelper
extra_css_class = options[:class] || ''
if total > 0 || spent > 0
content_tag(:td, h(number_to_currency(spent, :unit => '')), :class => 'spent-amount ' + extra_css_class) +
content_tag(:td, h(number_to_currency(total, :unit => '')), :class => 'total-amount white ' + extra_css_class)
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
@@ -75,6 +75,14 @@ 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
end
+10 -14
View File
@@ -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
+10
View File
@@ -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,18 @@ class Deliverable < ActiveRecord::Base
# Accessors
delegate :name, :to => :contract, :prefix => true, :allow_nil => true
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
+19
View 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
+1 -1
View File
@@ -18,7 +18,7 @@
<% end %>
<% form.inputs :name => l(:text_account_legend) do %>
<%= form.input :payment_terms, :as => :select, :collection => resource.payment_terms_for_select %>
<%= 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'} %>
+1
View File
@@ -10,3 +10,4 @@
<%= 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))}" %>
+3 -1
View File
@@ -31,7 +31,7 @@
<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="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 %>
@@ -48,3 +48,5 @@
<p>TODO: Release 5 (Contract Status)</p>
</div>
<% html_title "#{l(:text_contracts)}" %>
+2
View File
@@ -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)}" %>
+21 -19
View File
@@ -17,9 +17,9 @@
</table>
<table class="right">
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '49%'}) %>
<%= 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'}) %>
<%= 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>
@@ -33,7 +33,7 @@
<%= 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_terms, :format => :format_payment_terms, :html_options => {:class => 'contract-payment-terms'}) %>
<%= 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'}) %>
@@ -41,12 +41,12 @@
</div>
<table class="finance">
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '46%'}) %>
<%= 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, :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'}) %>
<tr>
<td colspan="3">
@@ -54,7 +54,7 @@
</td>
</tr>
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
<%= 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'}) %>
@@ -148,28 +148,28 @@
<tbody>
<tr>
<td class="l"><a href="#"><strong>Labor</strong></a></td>
<td><%= h(deliverable.labor_budget_spent) %></td>
<td><%= h(deliverable.labor_budget_total) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.labor_budget_spent)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.labor_budget_total)) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<tr>
<td class="l"><a href="#"><strong>Overhead</strong></a></td>
<td><%= h(deliverable.overhead_spent) %></td>
<td><%= h(deliverable.overhead_budget_total) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.overhead_spent)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.overhead_budget_total)) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<%# TODO: Release 2, Fixed %>
<%# TODO: Release 2, Markup %>
<tr>
<td class="l">Profit</td>
<td><%= h(deliverable.profit_left) %></td>
<td><%= h(deliverable.profit_budget) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_left)) %></td>
<td><%= h(format_value_field_for_contracts(deliverable.profit_budget)) %></td>
<td></td>
</tr>
<tr class="total">
<td class="l"><strong>Total:</strong></td>
<td><strong><%= h(deliverable.total_spent) %></strong></td>
<td><strong><%= h(deliverable.total) %></strong></td>
<td><strong><%= h(format_value_field_for_contracts(deliverable.total_spent)) %></strong></td>
<td><strong><%= h(format_value_field_for_contracts(deliverable.total)) %></strong></td>
<td><strong>TODO: Release 2</strong></td>
</tr>
</tbody>
@@ -292,3 +292,5 @@
-->
<p>TODO: Release 2+, history</p>
<% html_title "#{l(:text_contracts)} - #{h(resource.name)}" %>
+4 -4
View File
@@ -37,11 +37,11 @@
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:hours, l(:text_short_hours)) %></p>
<%= labor_budget.text_field(:hours, :size => 10) %>
<%= 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, :size => 10) %>
<%= 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>
@@ -64,11 +64,11 @@
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:hours, l(:text_short_hours)) %></p>
<%= overhead_budget.text_field(:hours, :size => 10) %>
<%= 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, :size => 10) %>
<%= 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>
+2
View File
@@ -5,3 +5,5 @@
<% 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))}" %>
+2
View File
@@ -5,3 +5,5 @@
<% 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)}" %>
+5 -6
View File
@@ -13,14 +13,10 @@ en:
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}}"
@@ -60,4 +56,7 @@ en:
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"
@@ -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
@@ -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
View 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
+15 -1
View File
@@ -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,25 @@ 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'
@@ -72,3 +85,4 @@ 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'
@@ -29,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
@@ -43,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!
@@ -106,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,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
@@ -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
@@ -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
+4 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -4
View File
@@ -8,7 +8,7 @@ class ContractsEditTest < ActionController::IntegrationTest
@account_executive = User.generate!
@role = Role.generate!
User.add_to_project(@account_executive, @project, @role)
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15', :account_executive => @account_executive)
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :account_executive => @account_executive)
end
should "allow any user to edit the contract" do
@@ -23,9 +23,7 @@ class ContractsEditTest < ActionController::IntegrationTest
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'
+3 -1
View File
@@ -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
@@ -40,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
+4 -4
View File
@@ -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,7 +205,7 @@ 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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
@@ -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)
+50
View 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
+1
View File
@@ -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
@@ -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
@@ -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
@@ -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
View 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