Merge branch '6441-contract-and-deliverable-status'

This commit is contained in:
Eric Davis committed 2011-08-10 16:12:11 -07:00
commit 066f46b9bf
29 files changed
+1258 -53

No files matched your search

+44 -4
View File
@@ -1,12 +1,52 @@
module ContractsHelper module ContractsHelper
def setup_nested_deliverable_records(deliverable) def setup_nested_deliverable_records(deliverable)
returning(deliverable) do |d| deliverable.labor_budgets.build if deliverable.labor_budgets.empty?
d.labor_budgets.build if d.labor_budgets.empty? deliverable.overhead_budgets.build if deliverable.overhead_budgets.empty?
d.overhead_budgets.build if d.overhead_budgets.empty? deliverable.fixed_budgets.build if deliverable.fixed_budgets.empty?
d.fixed_budgets.build if d.fixed_budgets.empty? deliverable
end
def group_contracts_by_status(contracts)
grouped_contracts = contracts.inject({}) do |grouped, contract|
grouped[contract.status] ||= []
grouped[contract.status] << contract
grouped
end
grouped_contracts["open"] ||= []
grouped_contracts["locked"] ||= []
grouped_contracts["closed"] ||= []
grouped_contracts
end
def grouped_deliverable_options_for_select(project, selected_key=nil)
project.contracts.all(:include => :deliverables).inject("") do |html, contract|
if contract.closed? && !contract.includes_deliverable_id?(selected_key)
html
else
html << content_tag(:optgroup,
deliverable_options_for_contract(contract, selected_key).join("\n"),
:label => h(contract.name))
end
end end
end end
def deliverable_options_for_contract(contract, selected_key)
contract.deliverables.collect do |deliverable|
deliverable_option(deliverable, selected_key)
end
end
def deliverable_option(deliverable, selected_key)
option_attributes = {}
option_attributes[:value] = h(deliverable.id)
option_attributes[:selected] = "selected" if selected_key.to_i == deliverable.id
option_attributes[:disabled] = "disabled" if (deliverable.locked? || deliverable.contract_locked?) && selected_key.to_i != deliverable.id
return "" if deliverable.closed? && option_attributes[:selected].blank? # Skip unselected, closed
content_tag(:option, h(deliverable.title), option_attributes)
end
# Simple helper to show the values of a field on an object in a standard format # Simple helper to show the values of a field on an object in a standard format
# #
# <p> # <p>
+72 -9
View File
@@ -17,7 +17,9 @@ class Contract < ActiveRecord::Base
validates_presence_of :start_date validates_presence_of :start_date
validates_presence_of :end_date validates_presence_of :end_date
validates_inclusion_of :discount_type, :in => %w($ %), :allow_blank => true, :allow_nil => true validates_inclusion_of :discount_type, :in => %w($ %), :allow_blank => true, :allow_nil => true
validates_inclusion_of :status, :in => ["open","locked","closed"], :allow_blank => true, :allow_nil => true
validate :start_and_end_date_are_valid validate :start_and_end_date_are_valid
validate_on_update :validate_status_changes
# Accessors # Accessors
attr_accessible :name attr_accessible :name
@@ -33,15 +35,49 @@ class Contract < ActiveRecord::Base
attr_accessible :po_number attr_accessible :po_number
attr_accessible :client_point_of_contact attr_accessible :client_point_of_contact
attr_accessible :details attr_accessible :details
attr_accessible :status
named_scope :by_name, {:order => "#{Contract.table_name}.name ASC"} named_scope :by_name, {:order => "#{Contract.table_name}.name ASC"}
named_scope :with_status, lambda {|statuses|
[:status, :contract_type, {
:conditions => ["#{Contract.table_name}.status IN (?)", statuses]
}
}
[:contract_type,
:discount_spent, :discount_budget :discount_spent, :discount_budget
].each do |mthd| ].each do |mthd|
define_method(mthd) { "TODO in later release" } define_method(mthd) { "TODO in later release" }
end end
def status
read_attribute(:status) || "open"
end
def lock!
update_attribute(:status, "locked")
end
def close!
update_attribute(:status, "closed")
end
def open?
self.status == "open"
end
def locked?
self.status == "locked"
end
def closed?
self.status == "closed"
end
def includes_deliverable_id?(deliverable_id)
deliverable_ids.include?(deliverable_id.to_i)
end
# ------------------------------------------------------------ # ------------------------------------------------------------
# Labor Methods # Labor Methods
# ------------------------------------------------------------ # ------------------------------------------------------------
@@ -229,6 +265,7 @@ class Contract < ActiveRecord::Base
def after_initialize def after_initialize
self.executed = false unless self.executed.present? self.executed = false unless self.executed.present?
self.status = "open" unless self.status.present?
end end
# Are the start_date and end_date valid? # Are the start_date and end_date valid?
@@ -238,6 +275,30 @@ class Contract < ActiveRecord::Base
end end
end end
def valid_status_change?
change_to_status_only? || changing_to_the_open_status? || changing_from_the_open_status?
end
def change_to_status_only?
["status"] == changes.keys
end
def changing_to_the_open_status?
changes["status"].present? && "open" == changes["status"].second
end
def changing_from_the_open_status?
changes["status"].present? && "open" == changes["status"].first
end
# TODO: duplicated on Deliverable, refactor after one more duplication
def validate_status_changes
return if valid_status_change?
errors.add_to_base(:cant_update_locked_contract) if locked?
errors.add_to_base(:cant_update_closed_contract) if closed?
end
# Currency amount of time that is logged to the project or to issues # Currency amount of time that is logged to the project or to issues
# that are not assigned to a Deliverable # that are not assigned to a Deliverable
def orphaned_time def orphaned_time
@@ -252,15 +313,17 @@ class Contract < ActiveRecord::Base
end end
if Rails.env.test? if Rails.env.test?
generator_for :name, :method => :next_name generator_for :name, :start => "Contract 0000"
generator_for :executed => true generator_for :executed => true
generator_for(:start_date) { Date.yesterday } generator_for(:start_date) { Date.yesterday }
generator_for(:end_date) { Date.tomorrow } generator_for(:end_date) { Date.tomorrow }
generator_for :discount, ''
def self.next_name generator_for :details, ''
@last_name ||= 'Contract 0000' generator_for :discount_note, ''
@last_name.succ! generator_for :client_point_of_contact, ''
end generator_for :client_ap_contact_information, ''
generator_for :po_number, ''
generator_for :status, 'open'
end end
@@ -270,5 +333,5 @@ class Contract < ActiveRecord::Base
def summarize_associated_values(records, value_method) def summarize_associated_values(records, value_method)
records.inject(0) {|total, record| total += record.send(value_method)} records.inject(0) {|total, record| total += record.send(value_method)}
end end
end end
+102 -2
View File
@@ -19,15 +19,27 @@ class Deliverable < ActiveRecord::Base
validates_presence_of :title validates_presence_of :title
validates_presence_of :type validates_presence_of :type
validates_presence_of :manager validates_presence_of :manager
validates_inclusion_of :status, :in => ["open","locked","closed"], :allow_blank => true, :allow_nil => true
validate_on_update :validate_status_changes
validate :validate_contract_status
# Accessors # Accessors
include DollarizedAttribute include DollarizedAttribute
dollarized_attribute :total dollarized_attribute :total
delegate :name, :to => :contract, :prefix => true, :allow_nil => true delegate :name, :to => :contract, :prefix => true, :allow_nil => true
delegate "open?", :to => :contract, :prefix => true, :allow_nil => true
delegate "closed?", :to => :contract, :prefix => true, :allow_nil => true
delegate "locked?", :to => :contract, :prefix => true, :allow_nil => true
# Callbacks # Callbacks
before_destroy :block_on_locked_contracts
before_destroy :block_on_closed_contracts
def after_initialize
self.status = "open" unless self.status.present?
end
# Register callbacks here, on new records the class isn't set so class-specific # Register callbacks here, on new records the class isn't set so class-specific
# callbacks don't fire. # callbacks don't fire.
def after_save def after_save
@@ -37,6 +49,11 @@ class Deliverable < ActiveRecord::Base
end end
named_scope :by_title, {:order => "#{Deliverable.table_name}.title ASC"} named_scope :by_title, {:order => "#{Deliverable.table_name}.title ASC"}
named_scope :with_status, lambda {|statuses|
{
:conditions => ["#{Deliverable.table_name}.status IN (?)", statuses]
}
}
def short_type def short_type
'' ''
@@ -51,6 +68,88 @@ class Deliverable < ActiveRecord::Base
nil nil
end end
def lock!
update_attribute(:status, "locked")
end
def close!
update_attribute(:status, "closed")
end
def open?
self.status == "open"
end
def locked?
self.status == "locked"
end
def closed?
self.status == "closed"
end
def editable?
(new_record? || open?)
end
def valid_status_change?
change_to_status_only? || changing_to_the_open_status? || changing_from_the_open_status?
end
def change_to_status_only?
["status"] == changes.keys
end
def changing_to_the_open_status?
changes["status"].present? && "open" == changes["status"].second
end
def changing_from_the_open_status?
changes["status"].present? && "open" == changes["status"].first
end
# TODO: duplicated on Contract, refactor after one more duplication
def validate_status_changes
return if valid_status_change?
errors.add_to_base(:cant_update_locked_deliverable) if locked?
errors.add_to_base(:cant_update_closed_deliverable) if closed?
end
def validate_contract_status
return if contract_open?
return if change_to_status_only?
if contract_locked?
if new_record?
errors.add_to_base(:cant_create_deliverable_on_locked_contract)
else
errors.add_to_base(:cant_update_locked_contract)
end
end
if contract_closed?
if new_record?
errors.add_to_base(:cant_create_deliverable_on_closed_contract)
else
errors.add_to_base(:cant_update_closed_contract)
end
end
end
# No operation method, useful to clean up logic with an optional message
# for documentation
def noop(message="")
end
def block_on_locked_contracts
!contract_locked?
end
def block_on_closed_contracts
!contract_closed?
end
def to_s def to_s
title title
end end
@@ -214,7 +313,8 @@ class Deliverable < ActiveRecord::Base
if Rails.env.test? if Rails.env.test?
generator_for :title, :method => :next_title generator_for :title, :method => :next_title
generator_for :status, 'open'
def self.next_title def self.next_title
@last_title ||= 'Deliverable 0000' @last_title ||= 'Deliverable 0000'
@last_title.succ! @last_title.succ!
+7
View File
@@ -1,6 +1,13 @@
<% if resource.locked? || resource.closed? %>
<div class="error_msg">
<p><%= resource.locked? ? l(:text_contract_locked_warning) : l(:text_contract_closed_warning) %></p>
</div>
<% end %>
<div class="box tabular"> <div class="box tabular">
<% form.inputs :name => l(:text_general_legend) do %> <% form.inputs :name => l(:text_general_legend) do %>
<%= form.input :name, :required => true %> <%= form.input :name, :required => true %>
<%= form.input :status, :required => true, :collection => [["Open","open"],["Locked","locked"],["Closed","closed"]] %>
<%= form.input :account_executive, :required => true, :collection => @project.users.sort %> <%= form.input :account_executive, :required => true, :collection => @project.users.sort %>
<li class="boolean optional"> <li class="boolean optional">
<%= label('contract', 'executed') %> <%= label('contract', 'executed') %>
+67 -7
View File
@@ -12,10 +12,10 @@
</div> </div>
<% if collection.empty? %> <% if group_contracts_by_status(collection)["open"].empty? %>
<p class="nodata"><%= l(:label_no_data) %></p> <p class="nodata"><%= l(:label_no_data) %></p>
<% else %> <% else %>
<table class="list" cellspacing="0" border="0" cellpadding="0" id="contracts"> <table class="list open" cellspacing="0" border="0" cellpadding="0" id="contracts">
<thead> <thead>
<th><%= l(:field_id) %></th> <th><%= l(:field_id) %></th>
<th><%= l(:field_name) %></th> <th><%= l(:field_name) %></th>
@@ -26,11 +26,11 @@
<th><%= l(:field_end_date) %></th> <th><%= l(:field_end_date) %></th>
</thead> </thead>
<tbody> <tbody>
<% collection.each do |contract| %> <% group_contracts_by_status(collection)["open"].each do |contract| %>
<% content_tag_for(:tr, contract, :class => cycle('','odd')) do %> <% content_tag_for(:tr, contract, :class => cycle('','odd')) do %>
<td class="id"><%= link_to(h(contract.id), contract_path(@project, contract)) %></td> <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="name"><%= link_to(h(contract.name), contract_path(@project, contract)) %></td>
<td><%= release(5, "Contract Status") %></td> <td class="status"><%= h(contract.status) %></td>
<td><%= release(5, "Contract Type") %></td> <td><%= release(5, "Contract Type") %></td>
<td class="account-executive"><%= h contract.account_executive.name %></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="total-budget"><%= h(format_value_field_for_contracts(contract.total_budget)) %></td>
@@ -42,11 +42,71 @@
<% end %> <% end %>
<div class="title-bar"> <div class="title-bar">
<h2>Inactive Contracts</h2> <h2>Locked Contracts</h2>
</div> </div>
<p class="nodata"><%= l(:label_no_data) %></p> <% if group_contracts_by_status(collection)["locked"].empty? %>
<p><%= release(5, "Contract Status. Split contracts by active and inactive") %></p> <p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table class="list locked" cellspacing="0" border="0" cellpadding="0" id="contracts">
<thead>
<th><%= l(:field_id) %></th>
<th><%= l(:field_name) %></th>
<th><%= l(:field_status) %></th>
<th><%= l(:field_type) %></th>
<th><%= l(:field_account_executive_short) %></th>
<th><%= l(:field_total_budget) %></th>
<th><%= l(:field_end_date) %></th>
</thead>
<tbody>
<% group_contracts_by_status(collection)["locked"].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="status"><%= h(contract.status) %></td>
<td><%= release(5, "Contract Type") %></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>Closed Contracts</h2>
</div>
<% if group_contracts_by_status(collection)["closed"].empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table class="list closed" cellspacing="0" border="0" cellpadding="0" id="contracts">
<thead>
<th><%= l(:field_id) %></th>
<th><%= l(:field_name) %></th>
<th><%= l(:field_status) %></th>
<th><%= l(:field_type) %></th>
<th><%= l(:field_account_executive_short) %></th>
<th><%= l(:field_total_budget) %></th>
<th><%= l(:field_end_date) %></th>
</thead>
<tbody>
<% group_contracts_by_status(collection)["closed"].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="status"><%= h(contract.status) %></td>
<td><%= release(5, "Contract Type") %></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> </div>
+3 -6
View File
@@ -15,10 +15,7 @@
<div class="c_overview"> <div class="c_overview">
<table class="left"> <table class="left">
<tr class="contract-status"> <%= show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
<%# show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
<td colspan="2"><%= release(5, "Contract status") %></td>
</tr>
<%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %> <%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %>
<tr class="contract-type"> <tr class="contract-type">
<%# show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %> <%# show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %>
@@ -105,7 +102,7 @@
<div class="actions"> <div class="actions">
<a href="#TODO-release-2"><%= release(2, "CSV") %></a> <a href="#TODO-release-2"><%= release(2, "CSV") %></a>
<a href="#TODO-release?"><%= release(5, "View All/Pagination") %></a> <a href="#TODO-release?"><%= release(5, "View All/Pagination") %></a>
<%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') %> <%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') if resource.open? %>
</div> </div>
<div class="clear"></div> <div class="clear"></div>
@@ -133,7 +130,7 @@
<td width="10%" class="arrow end-date"><span><%= h format_date(deliverable.end_date) %></span></td> <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="2%" class="type"><%= h deliverable.short_type %></td>
<td width="25%" class="title"><%= h deliverable.title %></td> <td width="25%" class="title"><%= h deliverable.title %></td>
<td width="15%"><%= release(5, "Deliverable status") %></td> <td width="15%" class="status"><%= h deliverable.status %></td>
<td width="15%" class="manager"><%= h deliverable.manager.try(:name) %></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.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, deliverable.overhead_spent, deliverable.overhead_budget_total, :class => 'overhead') %>
+1 -1
View File
@@ -7,7 +7,7 @@
<div class="info"> <div class="info">
<div class="title"> <div class="title">
<%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, contract, deliverable), :class => 'icon icon-edit') %> <%= 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') %> <%= link_to(l(:button_delete), contract_deliverable_path(@project, contract, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') if contract.open? %>
</div> </div>
<%= textilizable(deliverable, :notes) %> <%= textilizable(deliverable, :notes) %>
+12
View File
@@ -2,6 +2,17 @@
<%= javascript_tag("var i18nEndDateEmpty = '#{l(:text_end_date_empty)}'") %> <%= javascript_tag("var i18nEndDateEmpty = '#{l(:text_end_date_empty)}'") %>
<%= javascript_tag("var i18nChangedPeriodMessage = '#{l(:text_changed_period_message)}'") %> <%= javascript_tag("var i18nChangedPeriodMessage = '#{l(:text_changed_period_message)}'") %>
<% if resource.locked? || resource.closed? || resource.contract_locked? || resource.contract_closed? %>
<div class="error_msg">
<% if resource.contract_locked? || resource.contract_closed? %>
<p><%= resource.contract_locked? ? l(:text_contract_locked_warning) : l(:text_contract_closed_warning) %></p>
<% end %>
<% if resource.locked? || resource.closed? %>
<p><%= resource.locked? ? l(:text_deliverable_locked_warning) : l(:text_deliverable_closed_warning) %></p>
<% end %>
</div>
<% end %>
<div class="box tabular"> <div class="box tabular">
<% form.inputs :name => l(:text_deliverable_details_legend), :id => 'deliverable-details' do %> <% form.inputs :name => l(:text_deliverable_details_legend), :id => 'deliverable-details' do %>
<%# Used by jquery to check if this is a new or existing record %> <%# Used by jquery to check if this is a new or existing record %>
@@ -19,6 +30,7 @@
</li> </li>
<%= form.input :type, :as => :hidden, :class => 'type' %> <%= form.input :type, :as => :hidden, :class => 'type' %>
<% end %> <% end %>
<%= form.input :status, :required => true, :collection => [["Open","open"],["Locked","locked"],["Closed","closed"]] %>
<%= form.input :manager, :required => true, :collection => @project.users.sort %> <%= form.input :manager, :required => true, :collection => @project.users.sort %>
<%= form.input :start_date, :as => :string, :input_html => {:size => 10, :class => 'start-date', :id => 'deliverable_start_date'}, :hint => calendar_for('deliverable_start_date') %> <%= form.input :start_date, :as => :string, :input_html => {:size => 10, :class => 'start-date', :id => 'deliverable_start_date'}, :hint => calendar_for('deliverable_start_date') %>
@@ -1,14 +1,10 @@
<% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %> <% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %>
<p> <p>
<%= label_tag(:deliverable_id, l(:field_deliverable)) %> <%= 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', <%= select_tag('deliverable_id',
content_tag('option', l(:label_no_change_option), :value => '') + content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') + content_tag('option', l(:label_none), :value => 'none') +
grouped_options_for_select(options)) %> grouped_deliverable_options_for_select(project)) %>
</p> </p>
<% end %> <% end %>
+1 -4
View File
@@ -1,9 +1,6 @@
<% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %> <% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %>
<p> <p>
<% options = project.contracts.inject([]) {|data, contract| <%= form.select(:deliverable_id, grouped_deliverable_options_for_select(project, issue.deliverable_id), {:include_blank => true}) %>
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> </p>
<% end %> <% end %>
+19 -1
View File
@@ -1,4 +1,19 @@
en: en:
activerecord:
errors:
messages:
cant_create_time_on_object: "Can't create a time entry on a %{reason} %{thing}"
cant_assign_to_closed_deliverable: "Can't assign issue to a closed deliverable"
cant_assign_to_locked_deliverable: "Can't assign issue to a locked deliverable"
cant_assign_to_closed_contract: "Can't assign issue to a closed contract"
cant_assign_to_locked_contract: "Can't assign issue to a locked contract"
cant_update_locked_deliverable: "Can't update a locked deliverable"
cant_update_closed_deliverable: "Can't update a closed deliverable"
cant_update_locked_contract: "Can't update a locked contract"
cant_update_closed_contract: "Can't update a closed contract"
cant_create_deliverable_on_locked_contract: "Can't create a deliverable on a locked contract"
cant_create_deliverable_on_closed_contract: "Can't create a deliverable on a closed contract"
field_end_date: End Date field_end_date: End Date
field_executed: Executed field_executed: Executed
text_contracts: Contracts text_contracts: Contracts
@@ -81,4 +96,7 @@ en:
text_error_message_orphaned_time: "There is {{amount}} worth of time clocked to issues that are not assigned to any deliverables." text_error_message_orphaned_time: "There is {{amount}} worth of time clocked to issues that are not assigned to any deliverables."
text_error_message_update_orphaned_time: "Please update the orphaned issues." text_error_message_update_orphaned_time: "Please update the orphaned issues."
field_estimated: Estimated field_estimated: Estimated
text_deliverable_locked_warning: "This deliverable is locked and cannot be saved without changing it's status to Open."
text_deliverable_closed_warning: "This deliverable is closed and cannot be saved without changing it's status to Open."
text_contract_locked_warning: "This contract is locked and cannot be saved without changing it's status to Open."
text_contract_closed_warning: "This contract is closed and cannot be saved without changing it's status to Open."
+10
View File
@@ -0,0 +1,10 @@
class AddStatusToContracts < ActiveRecord::Migration
def self.up
add_column :contracts, :status, :string
add_index :contracts, :status
end
def self.down
remove_column :contracts, :status
end
end
@@ -0,0 +1,10 @@
class AddStatusToDeliverables < ActiveRecord::Migration
def self.up
add_column :deliverables, :status, :string
add_index :deliverables, :status
end
def self.down
remove_column :deliverables, :status
end
end
+6
View File
@@ -62,6 +62,9 @@ end
require 'dispatcher' require 'dispatcher'
Dispatcher.to_prepare :redmine_contracts do Dispatcher.to_prepare :redmine_contracts do
require_dependency 'time_entry'
TimeEntry.send(:include, RedmineContracts::Patches::TimeEntryPatch)
gem 'inherited_resources', :version => '1.0.6' gem 'inherited_resources', :version => '1.0.6'
require_dependency 'inherited_resources' require_dependency 'inherited_resources'
require_dependency 'inherited_resources/base' require_dependency 'inherited_resources/base'
@@ -99,6 +102,9 @@ Dispatcher.to_prepare :redmine_contracts do
unless Query.available_columns.collect(&:name).include?(:contract_name) unless Query.available_columns.collect(&:name).include?(:contract_name)
Query.add_available_column(QueryColumn.new(:contract_name, :sortable => "#{Contract.table_name}.name", :groupable => 'contracts.name')) Query.add_available_column(QueryColumn.new(:contract_name, :sortable => "#{Contract.table_name}.name", :groupable => 'contracts.name'))
end end
require_dependency 'application_controller'
ApplicationController.send(:helper, :contracts)
end end
require 'redmine_contracts/hooks/view_layouts_base_html_head_hook' require 'redmine_contracts/hooks/view_layouts_base_html_head_hook'
@@ -15,6 +15,24 @@ module RedmineContracts
def contract_name def contract_name
contract.try(:name) contract.try(:name)
end end
validate :validate_deliverable_status
validate :validate_contract_status
def validate_deliverable_status
if deliverable.present? && changes["deliverable_id"].present?
errors.add_to_base(:cant_assign_to_closed_deliverable) if deliverable.closed?
errors.add_to_base(:cant_assign_to_locked_deliverable) if deliverable.locked?
end
end
def validate_contract_status
if deliverable.present? && changes["deliverable_id"].present? && contract.present?
errors.add_to_base(:cant_assign_to_closed_contract) if contract.closed?
errors.add_to_base(:cant_assign_to_locked_contract) if contract.locked?
end
end
end end
end end
@@ -0,0 +1,38 @@
module RedmineContracts
module Patches
module TimeEntryPatch
def self.included(base)
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
validate :validate_deliverable_status
validate :validate_contract_status
def validate_deliverable_status
if issue.present? && issue.deliverable.present?
errors.add_to_base("#{l(:"activerecord.errors.messages.cant_create_time_on_object", :reason => 'locked', :thing => 'deliverable')}") if issue.deliverable.locked?
errors.add_to_base("#{l(:"activerecord.errors.messages.cant_create_time_on_object", :reason => 'closed', :thing => 'deliverable')}") if issue.deliverable.closed?
end
end
def validate_contract_status
if issue.present? && issue.deliverable.present? && issue.deliverable.contract.present?
errors.add_to_base("#{l(:"activerecord.errors.messages.cant_create_time_on_object", :reason => 'locked', :thing => 'contract')}") if issue.deliverable.contract.locked?
errors.add_to_base("#{l(:"activerecord.errors.messages.cant_create_time_on_object", :reason => 'closed', :thing => 'contract')}") if issue.deliverable.contract.closed?
end
end
end
end
module ClassMethods
end
module InstanceMethods
end
end
end
end
+127
View File
@@ -54,6 +54,133 @@ class ContractsEditTest < ActionController::IntegrationTest
assert_template 'contracts/show' assert_template 'contracts/show'
assert_equal "An updated name", @contract.reload.name assert_equal "An updated name", @contract.reload.name
end
context "locked contract" do
setup do
assert @contract.lock!
end
should "block edits" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
fill_in "Name", :with => 'An updated name'
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/edit'
assert_not_equal "An updated name", @contract.reload.name
end
should "block edits even when the status is changed to closed" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
fill_in "Name", :with => 'An updated name'
select "Closed", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/edit'
assert_not_equal "An updated name", @contract.reload.name
assert @contract.reload.locked?
end
should "be allowed to change the status from locked to open" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
select "Open", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/show'
assert @contract.reload.open?
end
should "be allowed to change the status from locked to closed" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
select "Closed", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/show'
assert @contract.reload.closed?
end
end
context "closed contract" do
setup do
assert @contract.close!
end
should "block edits" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
fill_in "Name", :with => 'An updated name'
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/edit'
assert_not_equal "An updated name", @contract.reload.name
end
should "block edits weven when the status is changed to locked" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
fill_in "Name", :with => 'An updated name'
select "Locked", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/edit'
assert_not_equal "An updated name", @contract.reload.name
assert @contract.reload.closed?
end
should "be allowed to change the status from closed to open" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
select "Open", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/show'
assert @contract.reload.open?
end
should "be allowed to change the status from closed to locked" do
visit_contract_page(@contract)
click_link 'Update'
assert_response :success
select "Locked", :from => "Status"
click_button 'Save Contract'
assert_response :success
assert_template 'contracts/show'
assert @contract.reload.locked?
end
end end
end end
+23 -5
View File
@@ -5,8 +5,10 @@ class ContractsListTest < ActionController::IntegrationTest
def setup def setup
@project = Project.generate!(:identifier => 'main') @project = Project.generate!(:identifier => 'main')
@contract = Contract.generate!(:project => @project) @contract = Contract.generate!(:project => @project, :name => 'Contract1').reload
@contract2 = Contract.generate!(:project => @project) @contract2 = Contract.generate!(:project => @project, :name => 'Contract2').reload
@contract_locked = Contract.generate!(:project => @project, :status => 'locked', :name => 'LockedContract').reload
@contract_closed = Contract.generate!(:project => @project, :status => 'closed', :name => 'ClosedContract').reload
@other_project = Project.generate!(:identifier => 'other') @other_project = Project.generate!(:identifier => 'other')
@other_contract = Contract.generate!(:project => @other_project) @other_contract = Contract.generate!(:project => @other_project)
@@ -44,10 +46,10 @@ class ContractsListTest < ActionController::IntegrationTest
visit_contracts_for_project(@project) visit_contracts_for_project(@project)
end end
should "list all contracts for the project" do should "list all contracts for the project grouped by status" do
visit_contracts_for_project(@project) visit_contracts_for_project(@project)
assert_select "table#contracts" do assert_select "table#contracts.open" do
[@contract, @contract2].each do |contract| [@contract, @contract2].each do |contract|
assert_select "td.id", :text => /#{contract.id}/ assert_select "td.id", :text => /#{contract.id}/
assert_select "td.name", :text => /#{contract.name}/ assert_select "td.name", :text => /#{contract.name}/
@@ -56,7 +58,23 @@ class ContractsListTest < ActionController::IntegrationTest
assert_select "td.total-budget" assert_select "td.total-budget"
end end
end end
assert_select "table#contracts.locked" do
assert_select "td.id", :text => /#{@contract_locked.id}/
assert_select "td.name", :text => /#{@contract_locked.name}/
assert_select "td.account-executive", :text => /#{@contract_locked.account_executive.name}/
assert_select "td.end-date", :text => /#{format_date(@contract_locked.end_date)}/
assert_select "td.total-budget"
end
assert_select "table#contracts.closed" do
assert_select "td.id", :text => /#{@contract_closed.id}/
assert_select "td.name", :text => /#{@contract_closed.name}/
assert_select "td.account-executive", :text => /#{@contract_closed.account_executive.name}/
assert_select "td.end-date", :text => /#{format_date(@contract_closed.end_date)}/
assert_select "td.total-budget"
end
end end
should "not list contracts from other projects" do should "not list contracts from other projects" do
+2
View File
@@ -52,6 +52,7 @@ class ContractsNewTest < ActionController::IntegrationTest
fill_in "Start", :with => '2010-01-01' fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31' fill_in "End Date", :with => '2010-12-31'
select "Net 30", :from => "Payment Terms" select "Net 30", :from => "Payment Terms"
select "Locked", :from => "Status"
click_button "Save Contract" click_button "Save Contract"
@@ -64,6 +65,7 @@ class ContractsNewTest < ActionController::IntegrationTest
assert_equal '2010-01-01', @contract.start_date.to_s assert_equal '2010-01-01', @contract.start_date.to_s
assert_equal '2010-12-31', @contract.end_date.to_s assert_equal '2010-12-31', @contract.end_date.to_s
assert_equal 'Net 30', @contract.payment_term.name assert_equal 'Net 30', @contract.payment_term.name
assert_equal "locked", @contract.status
end end
end end
+241 -1
View File
@@ -9,7 +9,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
@manager = User.generate! @manager = User.generate!
@role = Role.generate! @role = Role.generate!
User.add_to_project(@manager, @project, @role) User.add_to_project(@manager, @project, @role)
@fixed_deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'The Title') @fixed_deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'The Title', :notes => "", :feature_sign_off => false, :warranty_sign_off => false)
@hourly_deliverable = HourlyDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'An Hourly') @hourly_deliverable = HourlyDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'An Hourly')
@user = User.generate_user_with_permission_to_manage_budget(:project => @project) @user = User.generate_user_with_permission_to_manage_budget(:project => @project)
@@ -49,6 +49,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
within("#deliverable-details") do within("#deliverable-details") do
fill_in "Title", :with => 'An updated title' fill_in "Title", :with => 'An updated title'
select "Locked", :from => "Status"
check "Feature Sign Off" check "Feature Sign Off"
check "Warranty Sign Off" check "Warranty Sign Off"
end end
@@ -61,6 +62,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal "FixedDeliverable", @fixed_deliverable.reload.type assert_equal "FixedDeliverable", @fixed_deliverable.reload.type
assert @fixed_deliverable.reload.warranty_sign_off? assert @fixed_deliverable.reload.warranty_sign_off?
assert @fixed_deliverable.reload.feature_sign_off? assert @fixed_deliverable.reload.feature_sign_off?
assert_equal "locked", @fixed_deliverable.reload.status
end end
@@ -78,6 +80,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
within("#deliverable-details") do within("#deliverable-details") do
fill_in "Title", :with => 'An updated title' fill_in "Title", :with => 'An updated title'
select "Locked", :from => "Status"
check "Feature Sign Off" check "Feature Sign Off"
check "Warranty Sign Off" check "Warranty Sign Off"
end end
@@ -101,6 +104,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal "HourlyDeliverable", @hourly_deliverable.reload.type assert_equal "HourlyDeliverable", @hourly_deliverable.reload.type
assert @hourly_deliverable.reload.warranty_sign_off? assert @hourly_deliverable.reload.warranty_sign_off?
assert @hourly_deliverable.reload.feature_sign_off? assert @hourly_deliverable.reload.feature_sign_off?
assert_equal "locked", @hourly_deliverable.reload.status
assert_equal 1, @hourly_deliverable.labor_budgets.count assert_equal 1, @hourly_deliverable.labor_budgets.count
@labor_budget = @hourly_deliverable.labor_budgets.first @labor_budget = @hourly_deliverable.labor_budgets.first
@@ -471,4 +475,240 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal 3, @retainer_deliverable.fixed_budgets.count assert_equal 3, @retainer_deliverable.fixed_budgets.count
assert_equal [600, nil, nil], @retainer_deliverable.fixed_budgets.collect(&:budget) assert_equal [600, nil, nil], @retainer_deliverable.fixed_budgets.collect(&:budget)
end end
context "locked deliverable" do
setup do
assert @fixed_deliverable.lock!
end
should "block edits to locked deliverables" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
end
should "block edits to locked deliverables even when status changes to closed" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
select "Closed", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
assert @fixed_deliverable.reload.locked?
end
should "be allowed to change the status on a locked deliverables to open" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Open", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.open?
end
should "be allowed to change the status on a locked deliverables to closed" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Closed", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.closed?
end
end
context "closed deliverable" do
setup do
assert @fixed_deliverable.close!
end
should "block edits to closed deliverables" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
end
should "block edits to closed deliverables even when the status is changed to locked" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
select "Locked", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
assert @fixed_deliverable.reload.closed?
end
should "be allowed to change the status on a closed deliverables to open" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Open", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.open?
end
should "be allowed to change the status on a closed deliverables to Locked" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Locked", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.locked?
end
end
context "a Deliverable on a locked Contract" do
setup do
assert @contract.lock!
end
should "be blocked from editing" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
end
should "allow status only changes" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Locked", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.locked?
end
end
context "a Deliverable on a closed Contract" do
setup do
assert @contract.close!
end
should "be blocked from editing" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
end
click_button "Save"
assert_response :success
assert_template 'deliverables/edit'
assert_not_equal "An updated title", @fixed_deliverable.reload.title
end
should "allow status only changes" do
visit_contract_page(@contract)
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
within("#deliverable-details") do
select "Locked", :from => "Status"
end
click_button "Save"
assert_response :success
assert_template 'contracts/show'
assert @fixed_deliverable.reload.locked?
end
end
end end
+8 -2
View File
@@ -74,6 +74,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable' fill_in "Title", :with => 'A New Deliverable'
select "Fixed", :from => "Type" select "Fixed", :from => "Type"
select "Locked", :from => "Status"
select @manager.name, :from => "Manager" select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01' fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31' fill_in "End Date", :with => '2010-12-31'
@@ -95,6 +96,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
assert_equal '2010-12-31', @deliverable.end_date.to_s assert_equal '2010-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager assert_equal @manager, @deliverable.manager
assert_equal 1000.0, @deliverable.total.to_f assert_equal 1000.0, @deliverable.total.to_f
assert_equal "locked", @deliverable.status
end end
should "create a new Hourly deliverable" do should "create a new Hourly deliverable" do
@@ -109,6 +111,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable' fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type" select "Hourly", :from => "Type"
select "Locked", :from => "Status"
select @manager.name, :from => "Manager" select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01' fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31' fill_in "End Date", :with => '2010-12-31'
@@ -128,7 +131,8 @@ class DeliverablesNewTest < ActionController::IntegrationTest
assert_equal '2010-01-01', @deliverable.start_date.to_s assert_equal '2010-01-01', @deliverable.start_date.to_s
assert_equal '2010-12-31', @deliverable.end_date.to_s assert_equal '2010-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager assert_equal @manager, @deliverable.manager
assert_equal "locked", @deliverable.status
end end
should "create a new Retainer deliverable" do should "create a new Retainer deliverable" do
@@ -143,6 +147,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable' fill_in "Title", :with => 'A New Deliverable'
select "Retainer", :from => "Type" select "Retainer", :from => "Type"
select "Locked", :from => "Status"
select @manager.name, :from => "Manager" select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01' fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31' fill_in "End Date", :with => '2010-12-31'
@@ -171,7 +176,8 @@ class DeliverablesNewTest < ActionController::IntegrationTest
assert_equal '2010-01-01', @deliverable.start_date.to_s assert_equal '2010-01-01', @deliverable.start_date.to_s
assert_equal '2010-12-31', @deliverable.end_date.to_s assert_equal '2010-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager assert_equal @manager, @deliverable.manager
assert_equal "locked", @deliverable.status
# Budget items, one per month # Budget items, one per month
labor_budgets = @deliverable.labor_budgets labor_budgets = @deliverable.labor_budgets
assert_equal 12, labor_budgets.length assert_equal 12, labor_budgets.length
@@ -24,10 +24,10 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
context "for a new issue" do context "for a new issue" do
setup do setup do
visit_project(@project) visit_project(@project)
click_link "New issue"
end end
should "set the issue's deliverable" do should "set the issue's deliverable" do
click_link "New issue"
fill_in "Subject", :with => 'Hook test' fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable" select @deliverable2.title, :from => "Deliverable"
click_button "Create" click_button "Create"
@@ -38,6 +38,70 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
end end
should "not allow setting a locked Deliverable" do
assert @deliverable2.lock!
click_link "New issue"
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
assert_no_difference("Issue.count") do
click_button "Create"
assert_response :success
end
assert_equal nil, Issue.last.deliverable
end
should "not allow setting a closed Deliverable" do
assert @deliverable2.close!
click_link "New issue"
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
assert_no_difference("Issue.count") do
click_button "Create"
assert_response :success
end
assert_equal nil, Issue.last.deliverable
end
should "not allow setting a Deliverable on a locked Contract" do
assert @contract2.lock!
click_link "New issue"
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
assert_no_difference("Issue.count") do
click_button "Create"
assert_response :success
end
assert_equal nil, Issue.last.deliverable
end
should "not allow setting a Deliverable on a closed Contract" do
assert @contract2.close!
click_link "New issue"
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
assert_no_difference("Issue.count") do
click_button "Create"
assert_response :success
end
assert_equal nil, Issue.last.deliverable
end
context "with no permission to Assign Deliverable" do context "with no permission to Assign Deliverable" do
should "not allow setting the Deliverable (force HTTP request)" do should "not allow setting the Deliverable (force HTTP request)" do
@role.permissions.delete(:assign_deliverable_to_issue) @role.permissions.delete(:assign_deliverable_to_issue)
@@ -69,6 +133,75 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
end end
should "not allow updating to a locked deliverable" do
assert @deliverable2.lock!
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal nil, @issue.deliverable
end
should "not allow updating to a closed deliverable" do
assert @deliverable2.close!
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal nil, @issue.deliverable
end
should "not allow updating to a deliverable on a locked contract" do
assert @contract2.lock!
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal nil, @issue.deliverable
end
should "not allow updating to a deliverable on a closed contract" do
assert @contract2.close!
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal nil, @issue.deliverable
end
should "allow updating an issue, even if the deliverable is locked as long as the deliverable isn't changed" do
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal @deliverable2, @issue.deliverable
# Now normal update after locking
assert @deliverable2.lock!
fill_in "Subject", :with => 'Change subject'
click_button "Submit"
assert_response :success
@issue.reload
assert_equal "Change subject", @issue.subject
assert_equal @deliverable2, @issue.deliverable
end
context "with no permission to Assign Deliverable" do context "with no permission to Assign Deliverable" do
should "not allow setting the Deliverable (force HTTP request)" do should "not allow setting the Deliverable (force HTTP request)" do
@role.permissions.delete(:assign_deliverable_to_issue) @role.permissions.delete(:assign_deliverable_to_issue)
@@ -19,15 +19,15 @@ class RedmineContracts::Hooks::HelperIssuesShowDetailAfterSettingHookTest < Acti
# Set first # Set first
@issue.init_journal(@manager) @issue.init_journal(@manager)
@issue.deliverable = @deliverable1 @issue.deliverable = @deliverable1
@issue.save! @issue.save! && @issue.reload
# Change # Change
@issue.init_journal(@manager) @issue.init_journal(@manager)
@issue.deliverable = @deliverable2 @issue.deliverable = @deliverable2
@issue.save! @issue.save! && @issue.reload
# Unset # Unset
@issue.init_journal(@manager) @issue.init_journal(@manager)
@issue.deliverable = nil @issue.deliverable = nil
@issue.save! @issue.save! && @issue.reload
login_as('manager', 'existing') login_as('manager', 'existing')
@@ -11,14 +11,25 @@ class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionC
@issue3 = Issue.generate_for_project!(@project) @issue3 = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project) @contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project) @contract2 = Contract.generate!(:project => @project)
@locked_contract = Contract.generate!(:project => @project)
@closed_contract = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing') @manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues]) @role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role) User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title') @deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title') @deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
@locked_deliverable = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Locked Deliverable', :status => 'locked')
@closed_deliverable = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Closed Deliverable', :status => 'closed')
@deliverable1_on_locked_contract = FixedDeliverable.generate!(:contract => @locked_contract, :manager => @manager, :title => 'Deliverable 1 on locked contract')
@deliverable2_on_locked_contract = FixedDeliverable.generate!(:contract => @locked_contract, :manager => @manager, :title => 'Deliverable 2 on locked contract')
@deliverable_on_closed_contract = FixedDeliverable.generate!(:contract => @closed_contract, :manager => @manager, :title => 'Deliverable on closed contract')
@issue.deliverable = @deliverable1 @issue.deliverable = @deliverable1
# Set contract statuses now that all deliverables are created
assert @locked_contract.lock!
assert @closed_contract.close!
login_as('manager', 'existing') login_as('manager', 'existing')
end end
@@ -42,6 +53,35 @@ class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionC
end end
end end
end end
should "disable all locked deliverables" do
assert_select "select#deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@locked_deliverable.title}/
end
end
should "disable all deliverables on locked contracts" do
assert_select "select#deliverable_id" do
assert_select "optgroup[label=?]", @locked_contract.name do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1_on_locked_contract.title}/
assert_select "option[disabled=disabled]", :text => /#{@deliverable2_on_locked_contract.title}/
end
end
end
should "not show closed deliverables" do
assert_select "select#deliverable_id" do
assert_select "option", :text => /#{@closed_deliverable.title}/, :count => 0
end
end
should "not show deliverables on closed contracts" do
assert_select "select#deliverable_id" do
assert_select "optgroup[label=?]", @closed_contract.name, :count => 0
assert_select "option", :text => /#{@deliverable_on_closed_contract.title}/, :count => 0
end
end
end end
context "with no permission to Assign Deliverable" do context "with no permission to Assign Deliverable" do
@@ -9,13 +9,25 @@ class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionControlle
@issue = Issue.generate_for_project!(@project) @issue = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project) @contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project) @contract2 = Contract.generate!(:project => @project)
@locked_contract = Contract.generate!(:project => @project)
@closed_contract = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing') @manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues]) @role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role) User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title') @deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Deliverable1')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title') @deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'Deliverable2')
@locked_deliverable = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Locked Deliverable', :status => 'locked')
@closed_deliverable = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Closed Deliverable', :status => 'closed')
@deliverable1_on_locked_contract = FixedDeliverable.generate!(:contract => @locked_contract, :manager => @manager, :title => 'Deliverable 1 on locked contract')
@deliverable2_on_locked_contract = FixedDeliverable.generate!(:contract => @locked_contract, :manager => @manager, :title => 'Deliverable 2 on locked contract')
@deliverable_on_closed_contract = FixedDeliverable.generate!(:contract => @closed_contract, :manager => @manager, :title => 'Deliverable on closed contract')
@issue.deliverable = @deliverable1 @issue.deliverable = @deliverable1
assert @issue.save
# Set contract statuses now that all deliverables are created
assert @locked_contract.lock!
assert @closed_contract.close!
login_as('manager', 'existing') login_as('manager', 'existing')
end end
@@ -39,6 +51,78 @@ class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionControlle
end end
end end
end end
should "disable all locked deliverables" do
assert_select "select#issue_deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@locked_deliverable.title}/
end
end
should "disable all deliverables on locked contracts" do
assert_select "select#issue_deliverable_id" do
assert_select "optgroup[label=?]", @locked_contract.name do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1_on_locked_contract.title}/
assert_select "option[disabled=disabled]", :text => /#{@deliverable2_on_locked_contract.title}/
end
end
end
should "not show closed deliverables" do
assert_select "select#issue_deliverable_id" do
assert_select "option", :text => /#{@closed_deliverable.title}/, :count => 0
end
end
should "not show deliverables on closed contracts" do
assert_select "select#issue_deliverable_id" do
assert_select "optgroup[label=?]", @closed_contract.name, :count => 0
assert_select "option", :text => /#{@deliverable_on_closed_contract.title}/, :count => 0
end
end
should "show the assigned deliverable as an option, even if it's locked" do
@deliverable1.lock!
visit_issue_page(@issue)
assert_select "select#issue_deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1.title}/, :count => 0 # Not disabled
assert_select "option", :text => /#{@deliverable1.title}/, :count => 1 # Present
end
end
should "show the assigned deliverable as an option, even if it's closed" do
@deliverable1.close!
visit_issue_page(@issue)
assert_select "select#issue_deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1.title}/, :count => 0 # Not disabled
assert_select "option", :text => /#{@deliverable1.title}/, :count => 1 # Present
end
end
should "show the assigned deliverable as an option, even if it's contract is locked" do
@contract1.lock!
visit_issue_page(@issue)
assert_select "select#issue_deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1.title}/, :count => 0 # Not disabled
assert_select "option", :text => /#{@deliverable1.title}/, :count => 1 # Present
end
end
should "show the assigned deliverable as an option, even if it's contract is closed" do
@contract1.close!
visit_issue_page(@issue)
assert_select "select#issue_deliverable_id" do
assert_select "option[disabled=disabled]", :text => /#{@deliverable1.title}/, :count => 0 # Not disabled
assert_select "option", :text => /#{@deliverable1.title}/, :count => 1 # Present
end
end
end end
context "with no permission to Assign Deliverable" do context "with no permission to Assign Deliverable" do
+9
View File
@@ -17,6 +17,9 @@ class ContractTest < ActiveSupport::TestCase
should_allow_values_for :discount_type, "$", "%", nil, '' should_allow_values_for :discount_type, "$", "%", nil, ''
should_not_allow_values_for :discount_type, ["amount", "percent", "bar"] should_not_allow_values_for :discount_type, ["amount", "percent", "bar"]
should_allow_values_for :status, "", nil, 'open', 'locked', 'closed'
should_not_allow_values_for :status, "other", "things", "1"
context "end_date" do context "end_date" do
should "be after start_date" do should "be after start_date" do
@contract = Contract.new(:start_date => Date.today, :end_date => Date.yesterday) @contract = Contract.new(:start_date => Date.today, :end_date => Date.yesterday)
@@ -32,6 +35,12 @@ class ContractTest < ActiveSupport::TestCase
assert_equal false, @contract.executed assert_equal false, @contract.executed
end end
should "default status to open" do
@contract = Contract.new
assert_equal "open", @contract.status
end
context "#labor_budget" do context "#labor_budget" do
should "sum all of the labor budgets of the Deliverables" do should "sum all of the labor budgets of the Deliverables" do
contract = Contract.generate! contract = Contract.generate!
+48
View File
@@ -12,6 +12,13 @@ class DeliverableTest < ActiveSupport::TestCase
should_validate_presence_of :type should_validate_presence_of :type
should_validate_presence_of :manager should_validate_presence_of :manager
should_allow_values_for :status, "", nil, 'open', 'locked', 'closed'
should_not_allow_values_for :status, "other", "things", "1"
should "default status to open" do
assert_equal "open", Deliverable.new.status
end
context "#total=" do context "#total=" do
should "strip dollar signs when writing" do should "strip dollar signs when writing" do
d = Deliverable.new d = Deliverable.new
@@ -35,4 +42,45 @@ class DeliverableTest < ActiveSupport::TestCase
end end
end end
context "with a locked contract" do
should "block creating a new deliverable" do
contract = Contract.generate!(:status => "locked")
deliverable = FixedDeliverable.spawn(:contract => contract)
assert !deliverable.valid?
assert deliverable.errors.on_base.include?("Can't create a deliverable on a locked contract")
end
should "block deleting a deliverable" do
contract = Contract.generate!
deliverable = FixedDeliverable.generate!(:contract => contract).reload
assert contract.lock!
assert_no_difference("Deliverable.count") do
deliverable.destroy
end
end
end
context "with a closed contract" do
should "block creating a new deliverable" do
contract = Contract.generate!(:status => "closed")
deliverable = FixedDeliverable.spawn(:contract => contract)
assert !deliverable.valid?
assert deliverable.errors.on_base.include?("Can't create a deliverable on a closed contract")
end
should "block deleting a deliverable" do
contract = Contract.generate!
deliverable = FixedDeliverable.generate!(:contract => contract).reload
assert contract.close!
assert_no_difference("Deliverable.count") do
deliverable.destroy
end
end
end
end end
@@ -19,6 +19,9 @@ class RedmineContracts::Hooks::ViewIssuesShowDetailsBottomTest < ActionControlle
@controller ||= ApplicationController.new @controller ||= ApplicationController.new
@controller.class.send(:include, ::Redmine::I18n) @controller.class.send(:include, ::Redmine::I18n)
@controller.response ||= ActionController::TestResponse.new @controller.response ||= ActionController::TestResponse.new
def @controller.api_request?
false
end
# Hack to support render_on # Hack to support render_on
@controller.instance_variable_set('@template', template) @controller.instance_variable_set('@template', template)
@controller.response = response @controller.response = response
@@ -0,0 +1,123 @@
require File.dirname(__FILE__) + '/../../../../test_helper'
class RedmineContracts::Patches::TimeEntryTest < ActionController::TestCase
def setup
@project = Project.generate!
@contract = Contract.generate!(:project => @project, :status => 'open')
@deliverable = FixedDeliverable.generate!(:contract => @contract, :status => 'open').reload
@issue = Issue.generate_for_project!(@project, :deliverable => @deliverable).reload
assert_equal @deliverable, @issue.deliverable
@user = User.generate!
@role = Role.generate!
User.add_to_project(@user, @project, @role)
@activity = TimeEntryActivity.generate!
end
def create_time_entry
@issue.reload
@time_entry = TimeEntry.create(:issue => @issue,
:project => @project,
:spent_on => Date.today,
:activity => @activity,
:hours => 10,
:user => @user)
end
def assert_error_about_locked_deliverable(time_entry)
assert_equal "Can't create a time entry on a locked deliverable", time_entry.errors.on_base
end
def assert_error_about_locked_contract(time_entry)
assert_equal "Can't create a time entry on a locked contract", time_entry.errors.on_base
end
def assert_error_about_closed_deliverable(time_entry)
assert_equal "Can't create a time entry on a closed deliverable", time_entry.errors.on_base
end
def assert_error_about_closed_contract(time_entry)
assert_equal "Can't create a time entry on a closed contract", time_entry.errors.on_base
end
should "allow logging time to an issue on an open deliverable, open contract" do
assert_difference("TimeEntry.count") { create_time_entry }
end
should "block logging time to an issue on a locked deliverable, open contract" do
assert @deliverable.lock!
assert @deliverable.locked?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert_error_about_locked_deliverable(@time_entry)
end
should "block logging time to an issue on an open deliverable, locked contract" do
assert @contract.lock!
assert @contract.locked?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert_error_about_locked_contract(@time_entry)
end
should "block logging time to an issue on a locked deliverable, locked contract" do
assert @deliverable.lock!
assert @deliverable.locked?
assert @contract.lock!
assert @contract.locked?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert @time_entry.errors.on_base.include?("Can't create a time entry on a locked deliverable")
assert @time_entry.errors.on_base.include?("Can't create a time entry on a locked contract")
end
should "block logging time to an issue on a closed deliverable, open contract" do
assert @deliverable.close!
assert @deliverable.closed?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert_error_about_closed_deliverable(@time_entry)
end
should "block logging time to an issue on a closed deliverable, locked contract" do
assert @deliverable.close!
assert @deliverable.closed?
assert @contract.lock!
assert @contract.locked?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert @time_entry.errors.on_base.include?("Can't create a time entry on a closed deliverable")
assert @time_entry.errors.on_base.include?("Can't create a time entry on a locked contract")
end
should "block logging time to an issue on an open deliverable, closed contract" do
assert @contract.close!
assert @contract.closed?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert_error_about_closed_contract(@time_entry)
end
should "block logging time to an issue on a locked deliverable, closed contract" do
assert @deliverable.lock!
assert @deliverable.locked?
assert @contract.close!
assert @contract.closed?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert @time_entry.errors.on_base.include?("Can't create a time entry on a locked deliverable")
assert @time_entry.errors.on_base.include?("Can't create a time entry on a closed contract")
end
should "block logging time to an issue on a closed deliverable, closed contract" do
assert @deliverable.close!
assert @deliverable.closed?
assert @contract.close!
assert @contract.closed?
assert_no_difference("TimeEntry.count") { create_time_entry }
assert @time_entry.errors.on_base.include?("Can't create a time entry on a closed deliverable")
assert @time_entry.errors.on_base.include?("Can't create a time entry on a closed contract")
end
end