<%= 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? %>
<%= textilizable(deliverable, :notes) %>
diff --git a/app/views/deliverables/_form.html.erb b/app/views/deliverables/_form.html.erb
index 1da5da6..fdd4357 100644
--- a/app/views/deliverables/_form.html.erb
+++ b/app/views/deliverables/_form.html.erb
@@ -2,6 +2,17 @@
<%= javascript_tag("var i18nEndDateEmpty = '#{l(:text_end_date_empty)}'") %>
<%= javascript_tag("var i18nChangedPeriodMessage = '#{l(:text_changed_period_message)}'") %>
+<% if resource.locked? || resource.closed? || resource.contract_locked? || resource.contract_closed? %>
+
<% 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 %>
@@ -19,6 +30,7 @@
<%= form.input :type, :as => :hidden, :class => 'type' %>
<% end %>
+ <%= form.input :status, :required => true, :collection => [["Open","open"],["Locked","locked"],["Closed","closed"]] %>
<%= 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') %>
diff --git a/app/views/issues/_bulk_edit_deliverable.html.erb b/app/views/issues/_bulk_edit_deliverable.html.erb
index f4a0e97..26ed093 100644
--- a/app/views/issues/_bulk_edit_deliverable.html.erb
+++ b/app/views/issues/_bulk_edit_deliverable.html.erb
@@ -1,14 +1,10 @@
<% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %>
<%= label_tag(:deliverable_id, l(:field_deliverable)) %>
- <% options = project.contracts.inject([]) {|data, contract|
- data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
- } %>
-
<%= select_tag('deliverable_id',
content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') +
- grouped_options_for_select(options)) %>
+ grouped_deliverable_options_for_select(project)) %>
<% end %>
diff --git a/app/views/issues/_edit_deliverable.html.erb b/app/views/issues/_edit_deliverable.html.erb
index 47fd42b..c07a1a7 100644
--- a/app/views/issues/_edit_deliverable.html.erb
+++ b/app/views/issues/_edit_deliverable.html.erb
@@ -1,9 +1,6 @@
<% if project.module_enabled?(:contracts) && User.current.allowed_to?(:assign_deliverable_to_issue, project) %>
- <% options = project.contracts.inject([]) {|data, contract|
- data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
- } %>
- <%= form.select(:deliverable_id, grouped_options_for_select(options, issue.deliverable_id), {:include_blank => true}) %>
+ <%= form.select(:deliverable_id, grouped_deliverable_options_for_select(project, issue.deliverable_id), {:include_blank => true}) %>
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 661837d..acb3852 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,4 +1,19 @@
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_executed: Executed
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_update_orphaned_time: "Please update the orphaned issues."
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."
diff --git a/db/migrate/018_add_status_to_contracts.rb b/db/migrate/018_add_status_to_contracts.rb
new file mode 100644
index 0000000..58d1823
--- /dev/null
+++ b/db/migrate/018_add_status_to_contracts.rb
@@ -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
diff --git a/db/migrate/019_add_status_to_deliverables.rb b/db/migrate/019_add_status_to_deliverables.rb
new file mode 100644
index 0000000..b4dbb7d
--- /dev/null
+++ b/db/migrate/019_add_status_to_deliverables.rb
@@ -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
diff --git a/init.rb b/init.rb
index 19e673c..ea166d9 100644
--- a/init.rb
+++ b/init.rb
@@ -62,6 +62,9 @@ end
require 'dispatcher'
Dispatcher.to_prepare :redmine_contracts do
+
+ require_dependency 'time_entry'
+ TimeEntry.send(:include, RedmineContracts::Patches::TimeEntryPatch)
gem 'inherited_resources', :version => '1.0.6'
require_dependency 'inherited_resources'
require_dependency 'inherited_resources/base'
@@ -99,6 +102,9 @@ Dispatcher.to_prepare :redmine_contracts do
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'))
end
+
+ require_dependency 'application_controller'
+ ApplicationController.send(:helper, :contracts)
end
require 'redmine_contracts/hooks/view_layouts_base_html_head_hook'
diff --git a/lib/redmine_contracts/patches/issue_patch.rb b/lib/redmine_contracts/patches/issue_patch.rb
index 4df6003..f1157ad 100644
--- a/lib/redmine_contracts/patches/issue_patch.rb
+++ b/lib/redmine_contracts/patches/issue_patch.rb
@@ -15,6 +15,24 @@ module RedmineContracts
def contract_name
contract.try(:name)
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
diff --git a/lib/redmine_contracts/patches/time_entry_patch.rb b/lib/redmine_contracts/patches/time_entry_patch.rb
new file mode 100644
index 0000000..cca2541
--- /dev/null
+++ b/lib/redmine_contracts/patches/time_entry_patch.rb
@@ -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
diff --git a/test/integration/contracts_edit_test.rb b/test/integration/contracts_edit_test.rb
index 0e10ae7..d302cf4 100644
--- a/test/integration/contracts_edit_test.rb
+++ b/test/integration/contracts_edit_test.rb
@@ -54,6 +54,133 @@ class ContractsEditTest < ActionController::IntegrationTest
assert_template 'contracts/show'
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
diff --git a/test/integration/contracts_list_test.rb b/test/integration/contracts_list_test.rb
index a80d290..4888b91 100644
--- a/test/integration/contracts_list_test.rb
+++ b/test/integration/contracts_list_test.rb
@@ -5,8 +5,10 @@ class ContractsListTest < ActionController::IntegrationTest
def setup
@project = Project.generate!(:identifier => 'main')
- @contract = Contract.generate!(:project => @project)
- @contract2 = Contract.generate!(:project => @project)
+ @contract = Contract.generate!(:project => @project, :name => 'Contract1').reload
+ @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_contract = Contract.generate!(:project => @other_project)
@@ -44,10 +46,10 @@ class ContractsListTest < ActionController::IntegrationTest
visit_contracts_for_project(@project)
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)
- assert_select "table#contracts" do
+ assert_select "table#contracts.open" do
[@contract, @contract2].each do |contract|
assert_select "td.id", :text => /#{contract.id}/
assert_select "td.name", :text => /#{contract.name}/
@@ -56,7 +58,23 @@ class ContractsListTest < ActionController::IntegrationTest
assert_select "td.total-budget"
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
should "not list contracts from other projects" do
diff --git a/test/integration/contracts_new_test.rb b/test/integration/contracts_new_test.rb
index 399d970..6bf55c8 100644
--- a/test/integration/contracts_new_test.rb
+++ b/test/integration/contracts_new_test.rb
@@ -52,6 +52,7 @@ class ContractsNewTest < ActionController::IntegrationTest
fill_in "Start", :with => '2010-01-01'
fill_in "End Date", :with => '2010-12-31'
select "Net 30", :from => "Payment Terms"
+ select "Locked", :from => "Status"
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-12-31', @contract.end_date.to_s
assert_equal 'Net 30', @contract.payment_term.name
+ assert_equal "locked", @contract.status
end
end
diff --git a/test/integration/deliverables_edit_test.rb b/test/integration/deliverables_edit_test.rb
index 4082190..8d1b569 100644
--- a/test/integration/deliverables_edit_test.rb
+++ b/test/integration/deliverables_edit_test.rb
@@ -9,7 +9,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
@manager = User.generate!
@role = Role.generate!
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')
@user = User.generate_user_with_permission_to_manage_budget(:project => @project)
@@ -49,6 +49,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
+ select "Locked", :from => "Status"
check "Feature Sign Off"
check "Warranty Sign Off"
end
@@ -61,6 +62,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal "FixedDeliverable", @fixed_deliverable.reload.type
assert @fixed_deliverable.reload.warranty_sign_off?
assert @fixed_deliverable.reload.feature_sign_off?
+ assert_equal "locked", @fixed_deliverable.reload.status
end
@@ -78,6 +80,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
within("#deliverable-details") do
fill_in "Title", :with => 'An updated title'
+ select "Locked", :from => "Status"
check "Feature Sign Off"
check "Warranty Sign Off"
end
@@ -101,6 +104,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
assert_equal "HourlyDeliverable", @hourly_deliverable.reload.type
assert @hourly_deliverable.reload.warranty_sign_off?
assert @hourly_deliverable.reload.feature_sign_off?
+ assert_equal "locked", @hourly_deliverable.reload.status
assert_equal 1, @hourly_deliverable.labor_budgets.count
@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 [600, nil, nil], @retainer_deliverable.fixed_budgets.collect(&:budget)
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
diff --git a/test/integration/deliverables_new_test.rb b/test/integration/deliverables_new_test.rb
index a55d5f8..97edac4 100644
--- a/test/integration/deliverables_new_test.rb
+++ b/test/integration/deliverables_new_test.rb
@@ -74,6 +74,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Fixed", :from => "Type"
+ select "Locked", :from => "Status"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
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 @manager, @deliverable.manager
assert_equal 1000.0, @deliverable.total.to_f
+ assert_equal "locked", @deliverable.status
end
should "create a new Hourly deliverable" do
@@ -109,6 +111,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Hourly", :from => "Type"
+ select "Locked", :from => "Status"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
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-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager
-
+ assert_equal "locked", @deliverable.status
+
end
should "create a new Retainer deliverable" do
@@ -143,6 +147,7 @@ class DeliverablesNewTest < ActionController::IntegrationTest
within("#deliverable-details") do
fill_in "Title", :with => 'A New Deliverable'
select "Retainer", :from => "Type"
+ select "Locked", :from => "Status"
select @manager.name, :from => "Manager"
fill_in "Start", :with => '2010-01-01'
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-12-31', @deliverable.end_date.to_s
assert_equal @manager, @deliverable.manager
-
+ assert_equal "locked", @deliverable.status
+
# Budget items, one per month
labor_budgets = @deliverable.labor_budgets
assert_equal 12, labor_budgets.length
diff --git a/test/integration/redmine_contracts/hooks/controller_issues_edit_before_save.rb b/test/integration/redmine_contracts/hooks/controller_issues_edit_before_save.rb
index 4b475b2..e865245 100644
--- a/test/integration/redmine_contracts/hooks/controller_issues_edit_before_save.rb
+++ b/test/integration/redmine_contracts/hooks/controller_issues_edit_before_save.rb
@@ -24,10 +24,10 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
context "for a new issue" do
setup do
visit_project(@project)
- click_link "New issue"
end
should "set the issue's deliverable" do
+ click_link "New issue"
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
click_button "Create"
@@ -38,6 +38,70 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
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
should "not allow setting the Deliverable (force HTTP request)" do
@role.permissions.delete(:assign_deliverable_to_issue)
@@ -69,6 +133,75 @@ class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionContro
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
should "not allow setting the Deliverable (force HTTP request)" do
@role.permissions.delete(:assign_deliverable_to_issue)
diff --git a/test/integration/redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook_test.rb b/test/integration/redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook_test.rb
index 9debdf2..c96dad8 100644
--- a/test/integration/redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook_test.rb
+++ b/test/integration/redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook_test.rb
@@ -19,15 +19,15 @@ class RedmineContracts::Hooks::HelperIssuesShowDetailAfterSettingHookTest < Acti
# Set first
@issue.init_journal(@manager)
@issue.deliverable = @deliverable1
- @issue.save!
+ @issue.save! && @issue.reload
# Change
@issue.init_journal(@manager)
@issue.deliverable = @deliverable2
- @issue.save!
+ @issue.save! && @issue.reload
# Unset
@issue.init_journal(@manager)
@issue.deliverable = nil
- @issue.save!
+ @issue.save! && @issue.reload
login_as('manager', 'existing')
diff --git a/test/integration/redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook_test.rb b/test/integration/redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook_test.rb
index fd28f57..f33b8df 100644
--- a/test/integration/redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook_test.rb
+++ b/test/integration/redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook_test.rb
@@ -11,14 +11,25 @@ class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionC
@issue3 = Issue.generate_for_project!(@project)
@contract1 = 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')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
+ @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
+ # Set contract statuses now that all deliverables are created
+ assert @locked_contract.lock!
+ assert @closed_contract.close!
+
login_as('manager', 'existing')
end
@@ -42,6 +53,35 @@ class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionC
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
context "with no permission to Assign Deliverable" do
diff --git a/test/integration/redmine_contracts/hooks/view_issues_form_details_bottom_hook_test.rb b/test/integration/redmine_contracts/hooks/view_issues_form_details_bottom_hook_test.rb
index e91b926..b2995e8 100644
--- a/test/integration/redmine_contracts/hooks/view_issues_form_details_bottom_hook_test.rb
+++ b/test/integration/redmine_contracts/hooks/view_issues_form_details_bottom_hook_test.rb
@@ -9,13 +9,25 @@ class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionControlle
@issue = Issue.generate_for_project!(@project)
@contract1 = 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')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
- @deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
- @deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
+ @deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'Deliverable1')
+ @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
+ assert @issue.save
+
+ # Set contract statuses now that all deliverables are created
+ assert @locked_contract.lock!
+ assert @closed_contract.close!
login_as('manager', 'existing')
end
@@ -39,6 +51,78 @@ class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionControlle
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
context "with no permission to Assign Deliverable" do
diff --git a/test/unit/contract_test.rb b/test/unit/contract_test.rb
index 1eae9a4..5e0242b 100644
--- a/test/unit/contract_test.rb
+++ b/test/unit/contract_test.rb
@@ -17,6 +17,9 @@ class ContractTest < ActiveSupport::TestCase
should_allow_values_for :discount_type, "$", "%", nil, ''
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
should "be after start_date" do
@contract = Contract.new(:start_date => Date.today, :end_date => Date.yesterday)
@@ -32,6 +35,12 @@ class ContractTest < ActiveSupport::TestCase
assert_equal false, @contract.executed
end
+ should "default status to open" do
+ @contract = Contract.new
+
+ assert_equal "open", @contract.status
+ end
+
context "#labor_budget" do
should "sum all of the labor budgets of the Deliverables" do
contract = Contract.generate!
diff --git a/test/unit/deliverable_test.rb b/test/unit/deliverable_test.rb
index 694adc0..fa32573 100644
--- a/test/unit/deliverable_test.rb
+++ b/test/unit/deliverable_test.rb
@@ -12,6 +12,13 @@ class DeliverableTest < ActiveSupport::TestCase
should_validate_presence_of :type
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
should "strip dollar signs when writing" do
d = Deliverable.new
@@ -35,4 +42,45 @@ class DeliverableTest < ActiveSupport::TestCase
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
diff --git a/test/unit/lib/redmine_contracts/hooks/view_issues_show_details_bottom_hook_test.rb b/test/unit/lib/redmine_contracts/hooks/view_issues_show_details_bottom_hook_test.rb
index 0e42dfb..845cccb 100644
--- a/test/unit/lib/redmine_contracts/hooks/view_issues_show_details_bottom_hook_test.rb
+++ b/test/unit/lib/redmine_contracts/hooks/view_issues_show_details_bottom_hook_test.rb
@@ -19,6 +19,9 @@ class RedmineContracts::Hooks::ViewIssuesShowDetailsBottomTest < ActionControlle
@controller ||= ApplicationController.new
@controller.class.send(:include, ::Redmine::I18n)
@controller.response ||= ActionController::TestResponse.new
+ def @controller.api_request?
+ false
+ end
# Hack to support render_on
@controller.instance_variable_set('@template', template)
@controller.response = response
diff --git a/test/unit/lib/redmine_contracts/patches/time_entry_patch_test.rb b/test/unit/lib/redmine_contracts/patches/time_entry_patch_test.rb
new file mode 100644
index 0000000..52c3d57
--- /dev/null
+++ b/test/unit/lib/redmine_contracts/patches/time_entry_patch_test.rb
@@ -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