Author SHA1 Message Date
Eric Davis 61270c60d5 [#4178] Fixed some more tests from the UI changes. 2010-08-09 12:47:45 -07:00
Eric Davis cb8fd95f29 [#4178] Fixed a JS syntax error from the skinning. 2010-08-09 12:34:42 -07:00
Eric Davis 074d6a47e7 [#4178] Fixing integration tests based on the UI changes. 2010-08-09 12:29:04 -07:00
Eric Davis de0752ed02 [#4178] CSS stripe the contracts list. 2010-08-09 12:00:38 -07:00
Eric Davis ed1d04d18d [#4178] Fix a css rule that leaked into the table cell title. 2010-08-09 12:00:20 -07:00
Eric Davis 0cb4b1941a Removed extra TODO, migration process was decided. 2010-08-09 11:50:14 -07:00
Eric Davis 66f3a07e75 [#4178] TODO flagging sections of the views with their release version. 2010-08-09 11:49:42 -07:00
Eric Davis 4af0860542 [#4178] Zebra stripe deliverable table. 2010-08-09 11:34:36 -07:00
Eric Davis 118b0d7622 [#4178] Fix a width issue in the contract terms in Chrome webkit. 2010-08-09 11:28:59 -07:00
Eric Davis 74795a2788 Untabify 2010-08-09 11:16:15 -07:00
Eric Davis a2dfc1c92b [#4178] Removed emtpy table cells that are for future features. 2010-08-09 11:16:00 -07:00
Eric Davis 67069dc60a [#4178] Tweak the Deliverable Finances so it's one table per child record. 2010-08-09 11:12:51 -07:00
Eric Davis 90d2aeccea [#4178] Added styles for the Deliverable form. 2010-08-09 11:07:55 -07:00
Eric Davis 78c43a8821 [#4178] Added Contract title bar to the deliverable form. 2010-08-09 10:37:00 -07:00
Eric Davis 73e92d2970 [#4178] Extract contract title bar to partial. 2010-08-09 10:33:29 -07:00
Eric Davis c652360a6d [#4178] Fixed the Contract form's submit button layout. 2010-08-09 10:24:46 -07:00
Eric Davis 5958285410 [#4178] Updated the Contract form from the design. 2010-08-03 15:17:50 -07:00
Eric Davis fcf012c828 [#4178] First port of the Contracts#show skin. 2010-08-03 14:58:08 -07:00
Eric Davis fe2c058def [#4178] Skinned Contracts#list. 2010-08-03 13:23:51 -07:00
Eric Davis ea00b89af5 [#4178] Adding JS and CSS assets. 2010-08-03 13:12:20 -07:00
Eric Davis 84c679dc67 [#4327] Show the deliverable's title in the Journal notes. 2010-08-03 12:02:37 -07:00
Eric Davis 848616853d [#4327] Save deliverable bulk edits. 2010-08-03 11:38:17 -07:00
Eric Davis 72aeaac59a [#4327] Added the Deliverable field to the bulk edit. 2010-08-03 11:24:23 -07:00
Eric Davis 6ad50c57b2 [#4184] Add a compatibility wrapper for Deliverable#due 2010-08-03 10:48:17 -07:00
Eric Davis 1d644178bd [#4184] Fix the issue mapper so each issue is migrated only once. 2010-08-03 10:17:57 -07:00
Eric Davis 389fbf3d13 [#4327] Save the deliverable changes to the issue using the hooks. 2010-08-02 16:36:14 -07:00
Eric Davis 729acf200d [#4327] Add a select field to selecting the deliverable for an issue. 2010-08-02 16:07:59 -07:00
Eric Davis ad8dec8638 [#4327] Show the current deliverable on the issue details page. 2010-08-02 15:14:05 -07:00
Eric Davis 39edad3e07 Fix broken test. 2010-08-02 13:39:07 -07:00
Eric Davis 76a0573341 [#4281] Change the title for editing a contract. 2010-07-19 17:45:44 -07:00
Eric Davis 231066098b [#4281] Change the title for editing a Deliverable. 2010-07-19 17:44:41 -07:00
Eric Davis 7209871e87 [#4280] Add the $ as a hint to the Deliverable total form. 2010-07-19 17:39:35 -07:00
Eric Davis 1670fb5a73 [#4279] Move the total field to the bottom of the deliverable form. 2010-07-19 17:36:59 -07:00
Eric Davis 4100972d46 [#4278] Increase the size of the Contract's small wiki fields. 2010-07-19 17:33:55 -07:00
Eric Davis e4ce899cd5 Sort deliverable managers in the select field. 2010-07-19 17:31:05 -07:00
Eric Davis c8ede6a05d [#4277] Only show project members for account executives. 2010-07-19 17:30:51 -07:00
37 changed files with 1533 additions and 154 deletions
+21 -11
View File
@@ -6,12 +6,14 @@ module ContractsHelper
end
end
def format_budget_for_deliverable(deliverable, spent, total)
def format_budget_for_deliverable(deliverable, spent, total, options={})
extra_css_class = options[:class] || ''
if total > 0 || spent > 0
content_tag(:div, h(number_to_currency(spent, :unit => '')), :class => 'spent-amount') +
content_tag(:div, h(number_to_currency(total, :unit => '')), :class => 'total-amount')
content_tag(:td, h(number_to_currency(spent, :unit => '')), :class => 'spent-amount ' + extra_css_class) +
content_tag(:td, h(number_to_currency(total, :unit => '')), :class => 'total-amount white ' + extra_css_class)
else
'---'
content_tag(:td, '----', :colspan => '2', :class => 'no-value ' + extra_css_class)
end
end
@@ -23,10 +25,12 @@ module ContractsHelper
# </p>
def show_field(object, field, options={}, &block)
html_options = options[:html_options] || {}
label = content_tag(:span, l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) + ": ", :class => 'contract-details-label')
label_html_options = options[:label_html_options] || {}
label = content_tag(:strong, l(("field_" + field.to_s.gsub(/\_id$/, "")).to_sym) + ": ", :class => 'contract-details-label')
formatter = options[:format]
raw_content = options[:raw] || false
wrap_in_td = options[:wrap_in_td] || true
content = ''
@@ -39,10 +43,16 @@ module ContractsHelper
object.send(field)
end
end
if raw_content
field_content = content
else
field_content = h(content)
end
content_tag(:p,
label +
(raw_content ? content : h(content)),
content_tag(:tr,
content_tag(:td, label, label_html_options) +
(wrap_in_td ? content_tag(:td, field_content) : field_content),
html_options)
end
@@ -52,10 +62,10 @@ module ContractsHelper
spent_content = send(formatter, object.send(spent_field))
total_content = send(formatter, object.send(total_field))
show_field(object, spent_field, options.merge(:raw => true)) do
show_field(object, spent_field, options.merge(:raw => true, :wrap_in_td => false)) do
content_tag(:span, h(spent_content), :class => 'spent') +
content_tag(:span, h(total_content), :class => 'budget')
content_tag(:td, h(spent_content), :class => 'spent') +
content_tag(:td, h(total_content), :class => 'budget')
end
end
+5
View File
@@ -53,6 +53,11 @@ class Deliverable < ActiveRecord::Base
issues.inject(0) {|total, issue| total += issue.spent_hours }
end
# Wrapper for the old Budget plugins' API
def due
end_date
end
if Rails.env.test?
generator_for :title, :method => :next_title
+24 -11
View File
@@ -1,24 +1,37 @@
<% form.inputs do %>
<div class="box tabular">
<% form.inputs :name => l(:text_general_legend) do %>
<%= form.input :name, :required => true %>
<%= form.input :account_executive, :required => true %>
<%= form.input :account_executive, :required => true, :collection => @project.users.sort %>
<li class="boolean optional">
<%= label('contract', 'executed') %>
<%= check_box 'contract', 'executed' %>
</li>
<%= form.input :start_date, :required => true, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('contract_start_date') %>
<%= form.input :end_date, :required => true, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('contract_end_date') %>
<%= form.input :billable_rate, :input_html => {:size => 20}, :hint => l(:field_billable_rate_hint) %>
<%= form.input :discount, :input_html => {:size => 20}, :hint => l(:field_discount_hint) %>
<%= form.input :discount_note, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
<%= form.input :payment_terms, :as => :select, :collection => resource.payment_terms_for_select %>
<%= form.input :client_ap_contact_information, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
<%= form.input :po_number %>
<%= form.input :client_point_of_contact, :input_html => {:class => 'wiki-edit', :rows => '3'} %>
<%= form.input :details, :input_html => {:class => 'wiki-edit'} %>
<% end %>
<% form.inputs :name => l(:text_budget_legend) do %>
<%= form.input :billable_rate, :input_html => {:size => 20}, :hint => l(:field_billable_rate_hint) %>
<%= form.input :discount, :input_html => {:size => 20}, :hint => l(:field_discount_hint) %>
<%= form.input :discount_note, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
<% end %>
<% form.inputs :name => l(:text_account_legend) do %>
<%= form.input :payment_terms, :as => :select, :collection => resource.payment_terms_for_select %>
<%= form.input :po_number %>
<%= form.input :client_ap_contact_information, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
<%= form.input :client_point_of_contact, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
<% end %>
</div>
<% form.buttons do %>
<%= form.commit_button %>
<%= link_to(l(:button_cancel), cancel_path) %>
<ol>
<li class="commit">
<%= submit_tag(l(:text_save_contract)) %>
<%= link_to(l(:button_cancel), cancel_path) %>
</li>
</ol>
<% end %>
<%= wikitoolbar_for 'contract_discount_note' %>
+25
View File
@@ -0,0 +1,25 @@
<div class="title-bar" id="upper-title-bar">
<%= content_tag(:h2, h(contract.name)) %>
<div class="title-bar-actions">
<span><a href="#TODO-release-4" class="">Log time (Release 4)</a></span>
<span class="meta-sep"> | </span>
<span id="watcher"><a class="icon icon-fav-off" href="#TODO-release-3" onclick="">Watch (Release 3)</a></span>
<span class="meta-sep"> | </span>
<span><a href="#TODO">Copy (Release ?)</a></span>
<span class="meta-sep"> | </span>
<div class="update button-large">
<%= link_to(l(:button_update), edit_contract_path(@project, contract)) %>
</div>
</div>
</div>
<%# TODO: release 3/4, contract messages
<div class="error_msg">
<p>There is $243 worth of time clocked to issues that are not assigned to any deliverables. <span>Please update the orphaned issues.</span> </p>
</div>
%>
+2 -1
View File
@@ -4,8 +4,9 @@
</div>
<% end %>
<%= content_tag(:h2, h(resource.name)) %>
<%= content_tag(:h2, h(l(:text_edit_contract_name, :name => resource.name))) %>
<% semantic_form_for resource, :url => contract_path(@project, resource), :html => {:class => 'tabular'} do |form| %>
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contract_path(@project, resource)} %>
<% end %>
+50 -26
View File
@@ -1,26 +1,50 @@
<% if collection.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table class="list" cellspacing="0" border="0" cellpadding="0" id="contracts">
<thead>
<th><%= l(:field_id) %></th>
<th><%= l(:field_name) %></th>
<%# TODO: Status %>
<%# TODO: Type %>
<th><%= l(:field_account_executive_short) %></th>
<th><%= l(:field_total_budget) %></th>
<th><%= l(:field_end_date) %></th>
</thead>
<tbody>
<% collection.each do |contract| %>
<% content_tag_for(:tr, contract) do %>
<td class="id"><%= link_to(h(contract.id), contract_path(@project, contract)) %></td>
<td class="name"><%= link_to(h(contract.name), contract_path(@project, contract)) %></td>
<td class="account-executive"><%= h contract.account_executive.name %></td>
<td class="total-budget"><%= h(number_to_currency(contract.total_budget)) %></td>
<td class="end-date"><%= h format_date(contract.end_date) %></td>
<% end %>
<% end %>
</tbody>
</table>
<% end %>
<div id="contract_list">
<div class="title-bar">
<div class="contextual">
<div class="new-issue button-large">
<%= link_to(l(:text_new_contract), new_contract_path) %>
</div>
</div>
<h2>Active Contacts</h2>
</div>
<% if collection.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table class="list" cellspacing="0" border="0" cellpadding="0" id="contracts">
<thead>
<th><%= l(:field_id) %></th>
<th><%= l(:field_name) %></th>
<%# TODO: Release 5, Status %>
<%# TODO: Release 5, Type %>
<th><%= l(:field_account_executive_short) %></th>
<th><%= l(:field_total_budget) %></th>
<th><%= l(:field_end_date) %></th>
</thead>
<tbody>
<% collection.each do |contract| %>
<% content_tag_for(:tr, contract, :class => cycle('','odd')) do %>
<td class="id"><%= link_to(h(contract.id), contract_path(@project, contract)) %></td>
<td class="name"><%= link_to(h(contract.name), contract_path(@project, contract)) %></td>
<td class="account-executive"><%= h contract.account_executive.name %></td>
<td class="total-budget"><%= h(number_to_currency(contract.total_budget)) %></td>
<td class="end-date"><%= h format_date(contract.end_date) %></td>
<% end %>
<% end %>
</tbody>
</table>
<% end %>
<div class="title-bar">
<h2>Inactive Contracts</h2>
</div>
<%# TODO: split contracts by active and inactive %>
<p class="nodata"><%= l(:label_no_data) %></p>
<p>TODO: Release 5 (Contract Status)</p>
</div>
+281 -69
View File
@@ -1,82 +1,294 @@
<%# TODO: Need skinning %>
<%= render :partial => 'title', :locals => {:contract => resource} %>
<% div_for(resource) do %>
<%= content_tag(:h2, h(resource.name)) %>
<%= show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
<%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %>
<%= show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %>
<%= show_field(resource, :start_date, :format => :format_date, :html_options => {:class => 'contract-start-date'}) %>
<%= show_field(resource, :end_date, :format => :format_date, :html_options => {:class => 'contract-end-date'}) %>
<%= avatar(resource.account_executive, :size => 40) %>
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}) %>
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :html_options => {:class => 'contract-overhead'}) %>
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
<div id="contract-terms" style="border: 1px solid #000">
<%= show_field(resource, :client_point_of_contact, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-point-of-contact'}) %>
<%= show_field(resource, :executed, :html_options => {:class => 'contract-executed'}) %>
<%= show_field(resource, :discount_note, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-discount-note'}) %>
<%= show_field(resource, :payment_terms, :format => :format_payment_terms, :html_options => {:class => 'contract-payment-terms'}) %>
<%= show_field(resource, :client_ap_contact_information, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-ap-contact-information'}) %>
<%= show_field(resource, :po_number, :html_options => {:class => 'contract-po-number'}) %>
<%= show_field(resource, :details, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-details'}) %>
<div id="contract-finances" style="border: 1px solid #000">
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}) %>
<div class="c_overview">
<table class="left">
<%= show_field(resource, :status, :html_options => {:class => 'contract-status'}) %>
<%= show_field(resource, :account_executive, :html_options => {:class => 'contract-account-manager'}) %>
<%= show_field(resource, :contract_type, :html_options => {:class => 'contract-type'}) %>
</table>
<table class="middle">
<%= show_field(resource, :start_date, :format => :format_date, :html_options => {:class => 'contract-start-date'}) %>
<%= show_field(resource, :end_date, :format => :format_date, :html_options => {:class => 'contract-end-date'}) %>
</table>
<table class="right">
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '49%'}) %>
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :html_options => {:class => 'contract-overhead'}) %>
<%= show_budget_field(resource, :fixed_spent, :fixed_budget, :html_options => {:class => 'contract-fixed'}) %>
<%= show_budget_field(resource, :markup_spent, :markup_budget, :html_options => {:class => 'contract-markup'}) %>
<%= show_budget_field(resource, :profit_spent, :profit_budget, :html_options => {:class => 'contract-profit'}) %>
<%= show_budget_field(resource, :discount_spent, :discount_budget, :html_options => {:class => 'contract-discount'}) %>
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
</table>
<div class="clear"></div>
<a href="#" id="expand_terms" class="expand"><%= l(:text_terms_and_financial_overview) %></a>
<div id="contract-terms">
<div class="left">
<table class="info">
<%= show_field(resource, :client_point_of_contact, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-point-of-contact padd'}, :label_html_options => {:width => '25%'}) %>
<%= show_field(resource, :executed, :html_options => {:class => 'contract-executed'}) %>
<%= show_field(resource, :discount_note, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-discount-note'}) %>
<%= show_field(resource, :payment_terms, :format => :format_payment_terms, :html_options => {:class => 'contract-payment-terms'}) %>
<%= show_field(resource, :client_ap_contact_information, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-client-ap-contact-information'}) %>
<%= show_field(resource, :po_number, :html_options => {:class => 'contract-po-number'}) %>
<%= show_field(resource, :details, :format => :textilizable, :raw => true, :html_options => {:class => 'contract-details'}) %>
</table>
</div>
<%= show_field(resource, :billable_rate, :format => :format_hourly_rate, :html_options => {:class => 'contract-billable-rate'}) %>
<%= show_budget_field(resource, :estimated_hour_spent, :estimated_hour_budget, :format => :l_hours, :html_options => {:class => 'contract-estimated-hour'}) %>
<table class="finance">
<%= show_budget_field(resource, :labor_spent, :labor_budget, :html_options => {:class => 'contract-labor'}, :label_html_options => {:width => '46%'}) %>
<%= show_budget_field(resource, :overhead_spent, :overhead_budget, :html_options => {:class => 'contract-overhead'}) %>
<%= show_budget_field(resource, :fixed_spent, :fixed_budget, :html_options => {:class => 'contract-fixed'}) %>
<%= show_budget_field(resource, :markup_spent, :markup_budget, :html_options => {:class => 'contract-markup'}) %>
<%= show_budget_field(resource, :profit_spent, :profit_budget, :html_options => {:class => 'contract-profit'}) %>
<%= show_budget_field(resource, :discount_spent, :discount_budget, :html_options => {:class => 'contract-discount'}) %>
<tr>
<td colspan="3">
<div class="hr"></div>
</td>
</tr>
<%= show_budget_field(resource, :total_spent, :total_budget, :html_options => {:class => 'contract-total'}) %>
<%= show_field(resource, :billable_rate, :format => :format_hourly_rate, :html_options => {:class => 'contract-billable-rate total'}) %>
<%= show_budget_field(resource, :estimated_hour_spent, :estimated_hour_budget, :format => :l_hours, :html_options => {:class => 'contract-estimated-hour total'}) %>
</table>
<div class="clear"></div>
</div>
</div>
<div class="clear"></div>
<div id="deliverables">
<%= content_tag(:h3, l(:field_deliverable_plural)) %>
<div class="actions">
<a href="#TODO-release-2">CSV (Release 2)</a>
<a href="#TODO-release?">View All (9) (Release ?)</a>
<%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') %>
</div>
</div>
<% end %>
<div class="clear"></div>
<%= link_to(l(:button_update), edit_contract_path(@project, resource)) %>
<% if resource.deliverables.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table width="100%" class="texpand" id="deliverables">
<thead>
<tr>
<th><%= l(:field_end_date) %></th>
<th>&nbsp;</th>
<th><%= l(:field_title) %></th>
<th><%= l(:field_status) %></th>
<th><%= l(:field_manager) %></th>
<th colspan="2"><%= l(:field_labor) %></th>
<th colspan="2"><%= l(:field_overhead) %></th>
<th colspan="2"><%= l(:field_fixed) %></th>
</tr>
</thead>
<%= link_to(l(:button_add_new), new_contract_deliverable_path(@project, resource), :id => 'new-deliverable') %>
<tbody>
<% resource.deliverables.each do |deliverable| %>
<% content_tag_for(:tr, deliverable, :class => 'click ' + cycle('even','')) do %>
<td width="10%" class="arrow end-date"><span><%= h format_date(deliverable.end_date) %></span></td>
<td width="2%" class="type"><%= h deliverable.short_type %></td>
<td width="25%" class="title"><%= h deliverable.title %></td>
<td width="15%">TODO: Release 5</td>
<td width="15%" class="manager"><%= h deliverable.manager.try(:name) %></td>
<%= format_budget_for_deliverable(deliverable, deliverable.labor_budget_spent, deliverable.labor_budget_total, :class => 'labor') %>
<%= format_budget_for_deliverable(deliverable, deliverable.overhead_spent, deliverable.overhead_budget_total, :class => 'overhead') %>
<%= format_budget_for_deliverable(deliverable, 0, 0) %><%# TODO: Release 2, Fixed Budgets %>
<% end %>
<% if resource.deliverables.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<table class="list" cellspacing="0" border="0" cellpadding="0" id="deliverables">
<thead>
<th><%= l(:field_end_date) %></th>
<th>&nbsp;</th>
<th><%= l(:field_title) %></th>
<%# TODO: Status %>
<th><%= l(:field_manager) %></th>
<th><%= l(:field_labor) %></th>
<th><%= l(:field_overhead) %></th>
<th><%= l(:field_fixed) %></th>
<th><%= l(:button_edit) %></th>
<th><%= l(:button_delete) %></th>
</thead>
<tbody>
<% resource.deliverables.each do |deliverable| %>
<% content_tag_for(:tr, deliverable) do %>
<td class="end-date"><%= h format_date(deliverable.end_date) %></td>
<td class="type"><%= h deliverable.short_type %></td>
<td class="title"><%= h deliverable.title %></td>
<%# TODO: Status %>
<td class="manager"><%= h deliverable.manager.try(:name) %></td>
<td class="labor">
<%= format_budget_for_deliverable(deliverable, deliverable.labor_budget_spent, deliverable.labor_budget_total) %>
</td>
<td class="overhead">
<%= format_budget_for_deliverable(deliverable, deliverable.overhead_spent, deliverable.overhead_budget_total) %>
</td>
<td class="fixed">---</td>
<td><%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, resource, deliverable), :class => 'icon icon-edit') %></td>
<td><%= link_to(l(:button_delete), contract_deliverable_path(@project, resource, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %></td>
<tr id="deliverable_details_<%= h(deliverable.id) %>" class="ign">
<td colspan="11">
<div class="expanded">
<div class="info">
<div class="title">
<%= link_to(l(:button_edit), edit_contract_deliverable_path(@project, resource, deliverable), :class => 'icon icon-edit') %>
<%= link_to(l(:button_delete), contract_deliverable_path(@project, resource, deliverable), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %>
</div>
<%= textilizable(deliverable, :notes) %>
<table>
<%= show_field(deliverable, :start_date, :format => :format_date, :html_options => {:class => 'deliverable-start-date'}) %>
<%= show_field(deliverable, :end_date, :format => :format_date, :html_options => {:class => 'deliverable-end-date'}) %>
</table>
<% end %>
</div>
<%# TODO: Release 2, skinning port %>
<div class="finance">
<table>
<thead>
<tr>
<th> </th>
<th>Spent</th>
<th>Budget</th>
<th>Hours</th>
</tr>
</thead>
<tfoot>
<tr class="fill">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tfoot>
<tbody>
<tr>
<td class="l"><a href="#"><strong>Labor</strong></a></td>
<td><%= h(deliverable.labor_budget_spent) %></td>
<td><%= h(deliverable.labor_budget_total) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<tr>
<td class="l"><a href="#"><strong>Overhead</strong></a></td>
<td><%= h(deliverable.overhead_spent) %></td>
<td><%= h(deliverable.overhead_budget_total) %></td>
<td> TODO: Release 2 / TODO hrs </td>
</tr>
<%# TODO: Release 2, Fixed %>
<%# TODO: Release 2, Markup %>
<tr>
<td class="l">Profit</td>
<td><%= h(deliverable.profit_left) %></td>
<td><%= h(deliverable.profit_budget) %></td>
<td></td>
</tr>
<tr class="total">
<td class="l"><strong>Total:</strong></td>
<td><strong><%= h(deliverable.total_spent) %></strong></td>
<td><strong><%= h(deliverable.total) %></strong></td>
<td><strong>TODO: Release 2</strong></td>
</tr>
</tbody>
</table>
</div>
<div class="issue_status">
<table>
<thead>
<tr>
<th colspan="2"><%= l(:label_issue_status_plural) %></th>
</tr>
</thead>
<%# TODO: Release 2 Issue status counters
<tr>
<td>Proposed</td>
<td class="number">1</td>
</tr>
<tr>
<td>On Hold</td>
<td class="number">1</td>
</tr>
<tr>
<td>Complete</td>
<td class="number">14</td>
</tr>
<tr>
<td><strong>All</strong></td>
<td class="number">16</td>
</tr>
%>
</table>
</div>
<div class="clear"></div>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
</tbody>
</table>
<% end %>
<div class="clear"></div>
</div>
<% end %> <%# div_for(resource) %>
<p>TODO: Release 4, Milestones</p>
<!--
<div id="milestones">
<h3>Milestones</h3>
<div class="options">
<input type="checkbox" checked="checked" /> Event
<input type="checkbox" checked="checked" /> Financial
<input type="checkbox" checked="checked" /> Dependency
</div>
<div class="actions">
<a href="#">iCal</a>
<a href="#">View All (37)</a>
<a href="#" id="new-deliverable">Add New</a>
</div>
<div class="clear"></div>
<table>
<thead>
<tr>
<th class="c">Done</th>
<th>Date</th>
<th>Title</th>
<th>Status</th>
<th>Type</th>
<th>Alert</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="c"><input type="checkbox" value="1" /></td>
<td>11/19/2009</td>
<td>ACME delivers branding guide</td>
<td><span class="red"><strong>Overdue</strong></span></td>
<td>Dependency</td>
<td>1 day</td>
</tr>
<tr class="even">
<td class="c"><input type="checkbox" value="1" /></td>
<td>12/01/2009</td>
<td>Send Invoice #2</td>
<td><span class="red"><strong>Overdue</strong></span></td>
<td>Financial</td>
<td>EoD</td>
</tr>
<tr class="odd highlight">
<td class="c"><input type="checkbox" value="1" /></td>
<td>12/11/2009</td>
<td>QA Signoff on 1.2</td>
<td>Pending</td>
<td>Event</td>
<td>3 days</td>
</tr>
<tr class="even">
<td class="c"><input type="checkbox" value="1" /></td>
<td>12/15/2009</td>
<td>Release 1.2</td>
<td>Pending</td>
<td>Event</td>
<td></td>
</tr>
<tr class="odd">
<td class="c"><input type="checkbox" value="1" /></td>
<td>12/15/2009</td>
<td>Deliverable: Load and Release Software</td>
<td>Pending</td>
<td>Event</td>
<td>BoD</td>
</tr>
</tbody>
</table>
</div>
-->
<p>TODO: Release 2+, history</p>
+65 -17
View File
@@ -1,11 +1,12 @@
<% form.inputs do %>
<div class="box tabular">
<% form.inputs :name => l(:text_deliverable_details_legend) do %>
<%= form.input :title, :required => true %>
<% if resource.new_record? %>
<%= form.input(:type, :required => true, :as => :select, :collection => [["Fixed", "FixedDeliverable"],["Hourly", "HourlyDeliverable"]], :include_blank => false, :input_html => {:class => 'type'}) %>
<% else %>
<%= form.input :type, :as => :hidden, :class => 'type' %>
<% end %>
<%= form.input :manager, :required => true, :collection => @project.users %>
<%= form.input :manager, :required => true, :collection => @project.users.sort %>
<%= form.input :start_date, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('deliverable_start_date') %>
<%= form.input :end_date, :as => :string, :input_html => {:size => 10}, :hint => calendar_for('deliverable_end_date') %>
<%= form.input :notes, :input_html => {:class => 'wiki-edit', :rows => '5'} %>
@@ -20,30 +21,77 @@
<%= check_box resource.class.to_s.underscore, 'warranty_sign_off' %>
</li>
<% end %>
<% end %>
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'} %>
<% form.inputs :name => l(:text_deliverable_finances), :id => 'deliverable-finances' do %>
<% form.inputs :name => l(:text_deliverable_finances), :id => 'deliverable-finances' do %>
<% form.semantic_fields_for :labor_budgets do |labor_budget| %>
<% labor_budget.inputs :name => l(:field_labor), :id => 'deliverable-labor' do %>
<%= labor_budget.input :hours, :label => l(:text_short_hours), :input_html => {:size => 10} %>
<%= labor_budget.input :budget, :label => l(:text_dollar_sign), :input_html => {:size => 10} %>
<li class="numeric optional">
<%= content_tag(:label, l(:field_labor)) %>
<table id="deliverable-labor" class="deliverable_finance_table">
<% form.fields_for :labor_budgets do |labor_budget| %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:hours, l(:text_short_hours)) %></p>
<%= labor_budget.text_field(:hours, :size => 10) %>
</td>
<td>
<p class="inline-hints"><%= labor_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= labor_budget.text_field(:budget, :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
<% end %>
</table>
</li>
<% form.semantic_fields_for :overhead_budgets do |overhead_budget| %>
<% overhead_budget.inputs :name => l(:field_overhead), :id => 'deliverable-overhead' do %>
<%= overhead_budget.input :hours, :label => l(:text_short_hours), :input_html => {:size => 10} %>
<%= overhead_budget.input :budget, :label => l(:text_dollar_sign), :input_html => {:size => 10} %>
<li class="numeric optional">
<%= content_tag(:label, l(:field_overhead)) %>
<table id="deliverable-overhead" class="deliverable_finance_table">
<% form.fields_for :overhead_budgets do |overhead_budget| %>
<tr>
<%# TODO: Select field for the Time Entry Activity in a td %>
<td>
<select><option>TODO: Release 3</option></select>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:hours, l(:text_short_hours)) %></p>
<%= overhead_budget.text_field(:hours, :size => 10) %>
</td>
<td>
<p class="inline-hints"><%= overhead_budget.label(:budget, l(:text_dollar_sign)) %></p>
<%= overhead_budget.text_field(:budget, :size => 10) %>
</td>
<%# TODO: Green Add button for multiple records %>
<td>
Todo: Add button (Release 3)
</td>
</tr>
<% end %>
<% end %>
<% end %>
</table>
</li>
<%= form.input :total, :input_html => {:size => 10}, :wrapper_html => {:class => 'deliverable_total_input'}, :hint => l(:text_dollar_sign) %>
<% end %>
</div>
<% form.buttons do %>
<%= form.commit_button :label => l(:button_save) %>
<%= link_to(l(:button_cancel), cancel_path) %>
<ol>
<li class="commit">
<%= submit_tag(l(:button_save)) %>
<%= link_to(l(:button_cancel), cancel_path) %>
</li>
</ol>
<% end %>
<%= wikitoolbar_for resource.to_underscore + '_notes' %>
+3 -1
View File
@@ -1,4 +1,6 @@
<%= content_tag(:h2, h(resource.title)) %>
<%= render :partial => 'contracts/title', :locals => {:contract => @contract} %>
<%= content_tag(:h2, h(l(:text_edit_deliverable_title, :title => resource.title))) %>
<% semantic_form_for [@project, @contract, setup_nested_deliverable_records(resource)], :url => contract_deliverable_path(@project, @contract, resource), :html => {:class => 'deliverable tabular'} do |form| %>
<%= render :partial => 'form', :object => form, :locals => {:cancel_path => contract_path(@project, @contract)} %>
+2
View File
@@ -1,3 +1,5 @@
<%= render :partial => 'contracts/title', :locals => {:contract => @contract} %>
<%= content_tag(:h2, l(:text_new_deliverable)) %>
<% semantic_form_for [@project, @contract, setup_nested_deliverable_records(resource)], :url => contract_deliverables_path(@project, @contract), :html => {:class => 'deliverable tabular'} do |form| %>
@@ -0,0 +1,14 @@
<% if project.module_enabled?(:contracts) %>
<p>
<%= label_tag(:deliverable_id, l(:field_deliverable)) %>
<% options = project.contracts.inject([]) {|data, contract|
data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
} %>
<%= select_tag('deliverable_id',
content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') +
grouped_options_for_select(options)) %>
</p>
<% end %>
@@ -0,0 +1,9 @@
<% if project.module_enabled?(:contracts) %>
<p>
<% options = project.contracts.inject([]) {|data, contract|
data << [contract.name, contract.deliverables.collect {|d| [d.title, d.id]} ]
} %>
<%= form.select(:deliverable_id, grouped_options_for_select(options, issue.deliverable_id), {:include_blank => true}) %>
</p>
<% end %>
@@ -0,0 +1,7 @@
<% if project.module_enabled?(:contracts) %>
<tr>
<th class="deliverable"><%= l(:field_deliverable) %>:</th>
<td class="deliverable"><%= h(issue.deliverable.title) if issue.deliverable.present? %></td>"
</tr>
<% end %>
+46
View File
@@ -1,4 +1,27 @@
jQuery(function($) {
var right_align = $('#contract-terms .finance tr td:nth-child ~ td, .c_overview table.right tr td:nth-child ~ td, #deliverables table tr.click td:nth-child(5) ~ td, .deliverable_finance_table tr.aright td:nth-child ~ td');
if (right_align.length > 0) {
right_align.after().css("text-align", "right");
}
$("#deliverables table tbody tr td:contains('---')").css("text-align", "center");
$(".texpand").jExpand();
$(".texpand").find("tr.even").next('tr:first').addClass("even");
$(window).resize(function() {
});
$('#expand_terms').click( function(){
$(this).next().slideToggle();
$(this).toggleClass('alt');
});
toggleSpecificDeliverableFields = function(form) {
var deliverableType = form.find('.type').val();
@@ -17,3 +40,26 @@ jQuery(function($) {
toggleSpecificDeliverableFields($('form.deliverable'));
});
});
/* Jquery Table Expander Plugin */
(function($){
$.fn.jExpand = function(){
var element = this;
$(element).find("tr.ign").hide();
$(element).find("tr.click").click(function() {
$(this).toggleClass("noborder");
$(this).next("tr").toggle();
$(this).find('.arrow').toggleClass("alt");
var box_height = $(this).next().find('.expanded').height();
var table_height = $(this).next().find('.finance table').height();
if(box_height-table_height > 0){
$(this).next().find('.finance table .fill td').css("padding-top", box_height-table_height+2);
}
});
}
})(jQuery);
+476 -2
View File
@@ -12,6 +12,14 @@ html>body .tabular li {overflow:hidden;}
.tabular .inline-hints { color:#666; margin:0.5em 0 0 0; padding: 0px; display: inline; }
.deliverable_finance_table .inline-hints {
padding-top:4px;
}
.no-value {
text-align:center;
color:#999;
}
.tabular li.required label { color: #484848; }
.tabular li.required label span.required {color: #bb0000;}
@@ -21,11 +29,477 @@ html>body .tabular li {overflow:hidden;}
a.contract-delete {color: red; }
/* Positioning */
#deliverable-finances ol li { display: inline; }
#deliverable-finances ol li label { float: none; margin-left: 0; }
#deliverables td.labor, #deliverables td.overhead, #deliverables td.fixed { text-align: center; }
#deliverables td.labor div, #deliverables td.overhead div, #deliverables td.fixed div { text-align: right; display: inline; padding: 0 5px; width: 50%; }
.spent, .budget { text-align: right; padding: 0 5px; }
span.contract-details-label { font-weight: bold; }
/* Design */
.tabular li{
padding-left: 120px; /*width of left column containing the label elements*/
}
.tabular li.commit {
padding-left: 0px; /* Don't pad submit buttons */
}
#content .error_msg{
background: #ffd5d5;
color: #a40000;
border: 1px solid #a40000;
margin: 5px 0 10px 0;
}
#content .error_msg p{
padding: 6px;
}
#content .error_msg p span{
font-weight: bold;
text-decoration: underline;
}
.contract .gravatar{
float: left;
border: 1px solid #b4d1d9;
padding: 0;
margin-top: 10px;
margin-left: 6px;
margin-right: 25px;
}
#content .contract .fixed-item,
#content .contract .fixed-item-form {
border-top: 1px dotted #999;
padding-top: 15px;
}
#content .contract .fixed-items {
padding-bottom: 15px;
border-bottom: 1px dotted #999;
}
.add_fixed a{
display: block;
margin: 15px 0 15px 0;
color: #2A5685;
text-decoration:none;
}
.select_dropdown{
width: 190px;
}
#content .contract table td,
#content .contract table th{
padding: 2px 5px 2px 5px;
}
#deliverable-overhead ol li,
#deliverable-labor ol li{
padding-left: 155px
}
#content table.deliverable_finance_table{
width: 600px;
}
#content table.deliverable_finance_table select{
width: 145px;
}
#content table.deliverable_finance_table input{
text-align: right;
width: 65px;
}
#content table.deliverable_finance_table,
#content table.deliverable_finance_table tr,
#content table.deliverable_finance_table tr td{
border: 0;
}
.deliverable_finance_table p.inline-hints label {font-weight: normal; float: none; display: auto; margin-left: 0px; } /* An inline label so it needs to be reset from .tabular */
#expand_terms{
margin: 10px 0 10px 0;
display: block;
}
#content #contract_list table{
margin-bottom: 20px;
}
#content #contract_list table tbody tr td.name a{
color: #2f8fa5;
text-decoration: none;
}
#content #contract_list table tbody tr td.name a:hover{
text-decoration: underline;
}
#content #contract_list table tbody tr td.priority_1{
background: #ffaaaa;
}
#content .c_overview{
background: #edf6fa;
border: 1px solid #dcebf0;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 6px;
margin-bottom: 20px;
}
#content .c_overview table{
width: 230px;
float: left;
border: 0;
}
#content .c_overview table.left{
float:left;
width:30%;
}
#content .c_overview table.middle{
margin-right:3%;
margin-left:3%;
width:auto;
}
#content .c_overview table.right{
width:30%;
max-width:300px;
float:right;
}
#content .c_overview table td{
border: 0;
text-align: left;
}
#content .c_overview table tr:hover td,
#content .c_overview table tr:hover,
#content .c_overview table td:hover{
background: transparent;
}
#content .c_overview a.expand{
background: url(../../../images/arrow_collapsed.png) no-repeat;
padding-left: 15px;
}
#content .c_overview a.expand.alt{
background: url(../../../images/arrow_expanded.png) no-repeat;
}
#deliverables .arrow{
background: url(../../../images/arrow_collapsed.png) no-repeat;
background-position: 0 6px;
}
#deliverables .arrow.alt{
background: url(../../../images/arrow_expanded.png) no-repeat;
background-position: 0 6px;
}
#deliverables .arrow span{
padding-left: 15px;
}
#contract-terms,
.deliverable_overview{
background: #f6fbfd;
border: 1px solid #d5dde1;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
display: none;
}
#contract-terms table .hr{
border-top: 1px solid #dee0e1;
}
#content #contract-terms table tr.padd td{
padding-top: 10px;
}
#contract-terms table.info{
width: 60%;
background: #f6fbfd;
}
#contract-terms table.finance{
width: 30%;
max-width:300px;
border-left: 1px solid #bed3dd;
float: right;
margin: 0;
background: #fafdfe;
}
#contract-terms table.info td,
#contract-terms table.finance td{
padding: 5px;
}
#contract-terms table tr.grey td,
#content .contract .grey{
color: #808080;
}
#contract-terms span.red,
#deliverables span.red,
#milestones span.red{
color: #990000;
}
#deliverables h3,
#milestones h3,
#content #history h3{
float: left;
font-size: 22px;
border: 0;
padding-left: 0;
}
#content #deliverables .actions,
#content #milestones .actions{
float: right;
margin-top: 20px;
}
#content #deliverables .actions a,
#content #milestones .actions a{
font-size: 13px;
color: #3e3e3e;
margin-left: 20px;
}
#deliverables table{
width: 100%;
margin-bottom: 20px;
}
#content #deliverables table thead tr th{
border-right: 1px solid #e6e6e6;
text-align: center;
font-weight: bold;
}
#content #deliverables table thead tr{
border-bottom: 1px solid #e6e6e6;
}
#content #deliverables table th,
#content #deliverables table td{
padding: 6px;
}
#content #deliverables table td, #content #deliverables table th,
#content #milestones table td, #content #milestones table th,
#content #contract_list table td, #content #contract_list table th {
border:1px solid #e6e6e6;
border-style: solid;
text-align:left;
position: static;
vertical-align:top;
z-index: 99;
}
#content #deliverables table tr.noborder td{
border-bottom: 0;
}
#content #deliverables table tr.ign td{
border-top: 0;
padding: 0;
}
#content #deliverables table tr.ign td table td{
padding: 2px 5px 2px 5px;
}
#content #deliverables table tr{
background-color: #edf6fa;
}
#content #deliverables table tr td.white{
background-color: #f6fafd;
}
#content #deliverables table tr.even,
#content #deliverables table tr.even:hover {
background-color:#dff3fc;
}
#content #deliverables table tr.ign tr{
background: transparent;
}
#content #deliverables div.title{
background: #FFF;
padding: 6px;
border-bottom: 1px solid #e4eff4;
}
#content #deliverables .title a {
margin-right: 6px;
}
#content #deliverables .title img{
vertical-align: middle;
}
#content #deliverables .click td{
cursor: pointer;
}
#content #deliverables .expanded{
border: 1px solid #c8d0d4;
margin: 5px;
margin-top: 0;
background: #f6fbfd;
}
#content #deliverables .expanded fieldset{
border: none;
}
#content #deliverables .info{
width: 45%;
float: left;
}
#content #deliverables .info p{
margin: 10px 0 10px 10px;
}
#content #deliverables .info form fieldset{
margin-left: 10px;
margin-bottom: 10px;
}
#content #deliverables .info table{
width: auto;
margin: 0 0 0 10px;
}
#content #deliverables .info table td{
border: 0;
padding: 2px 5px 2px 5px;
}
#content #deliverables .finance table{
border-left: 1px solid #e4eff4;
}
#content #deliverables .finance table td,
#content #deliverables .issue_status table td{
border: 0px;
border-right: 1px solid #e4eff4;
text-align: right;
padding: 2px 5px 2px 5px;
}
#content #deliverables .finance table td.l,
#content #deliverables .issue_status table td.l{
text-align: left;
}
#content #deliverables .finance{
float: left;
width: 39%;
}
#content #deliverables .finance table{
margin: 0;
padding: 0;
}
#content #deliverables .finance table thead th,
#content #deliverables .issue_status table thead th{
background: #FFF;
border: 0;
font-weight: bold;
border-bottom: 1px solid #e4eff4;
text-align: center;
padding: 6px;
}
#content #deliverables .finance table thead th{
border-right: 1px solid #e4eff4;
}
#content #deliverables .finance table tbody tr.total td,
#contract-terms table tbody tr.total td{
border-top: 1px solid #bed3dd;
}
#content #deliverables .issue_status{
float: left;
width: 16%;
}
#content #deliverables .issue_status table tbody tr td{
border-right: 0;
color: #277d94;
text-align: left;
}
#content #deliverables .issue_status table tbody tr td.number {
text-align: right;
}
#content #deliverables a,
#content .c_overview a{
color: #277d94;
}
#content #milestones .options{
float: left;
width: 300px;
margin-top: 20px;
margin-left: 10px;
}
#content #milestones table{
margin: 0;padding: 0;
}
#content #milestones table thead th{
font-weight: bold;
text-align: center;
}
#content #milestones table tr.odd{
background: #dff3fc;
}
#content #milestones table tr.even{
background: #edf6fa;
}
#content #milestones table tr.highlight td{
border-top: 2px solid #5fccf9;
}
#content #milestones .c{
text-align: center;
}
#edit_contract_1 input#contract_start_date,
#edit_contract_1 input#contract_end_date{
width: 85px;
}
#edit_contract_1 li#contract_billable_rate_input div{
margin-bottom: 10px;
}
+11
View File
@@ -7,6 +7,7 @@ en:
field_account_executive_short: "Acct. Mgr."
field_end_date: "End Date"
text_new_contract: "New Contract"
text_edit_contract_name: "Edit {{name}}"
field_billable_rate: "Billable Rate"
field_billable_rate_hint: "$"
field_discount: "Discount"
@@ -22,6 +23,7 @@ en:
text_payment_terms_net_45: "Net 45"
button_add_new: Add New
text_new_deliverable: New Deliverable
text_edit_deliverable_title: "Edit {{title}}"
field_manager: Manager
field_labor: Labor
field_overhead: Overhead
@@ -50,3 +52,12 @@ en:
field_total_budget: "Total Budget"
field_total_spent: "Contract Total"
field_contract_type: "Type"
field_deliverable: "Deliverable"
field_deliverable_plural: "Deliverables"
text_terms_and_financial_overview: "Contract Terms and Financial Overview"
text_general_legend: "General"
text_budget_legend: "Budget"
text_account_legend: "Account Management"
text_deliverable_details_legend: "Deliverable Details"
text_save_contract: "Save Contract"
+6
View File
@@ -66,3 +66,9 @@ Dispatcher.to_prepare :redmine_contracts do
end
require 'redmine_contracts/hooks/view_layouts_base_html_head_hook'
require 'redmine_contracts/hooks/view_issues_show_details_bottom_hook'
require 'redmine_contracts/hooks/view_issues_form_details_bottom_hook'
require 'redmine_contracts/hooks/controller_issues_edit_before_save_hook'
require 'redmine_contracts/hooks/view_issues_bulk_edit_details_bottom_hook'
require 'redmine_contracts/hooks/controller_issues_bulk_edit_before_save_hook'
require 'redmine_contracts/hooks/helper_issues_show_detail_after_setting_hook'
@@ -1,5 +1,3 @@
require 'pp'
module RedmineContracts
class BudgetPluginInstalledError < StandardError; end
@@ -7,10 +5,6 @@ module RedmineContracts
set_table_name 'deliverables'
end
# TODO: decide how this is activated.
# Have the user dump yaml themselves? Or rename the tables and
# SQL select the data out into yaml?
# Deliverable.all.collect {|d| d.attributes}.to_yaml
class BudgetPluginMigration
@@data = nil
@@ -82,8 +76,17 @@ module RedmineContracts
end
end
@deliverable_mapper.each do |old, new|
Issue.update_all(["deliverable_id = ?", new], ["deliverable_id = ?", old])
# Slower than update_all but update_all could potentially hit an issue
# multiple times depending on the migration order. Example:
#
# - Issue 1 has Deliverable 1
# - Deliverable 1 updates Issue 1 to have the new deliverable id of 3
# - Deliverable 3 runs and updates Issue 1 again to have the new deliverable id of 5
#
Issue.all.each do |issue|
next if issue.deliverable_id.blank?
issue.update_attribute(:deliverable_id, @deliverable_mapper[issue.deliverable_id])
end
end
@@ -0,0 +1,23 @@
module RedmineContracts
module Hooks
class ControllerIssuesBulkEditBeforeSaveHook < Redmine::Hook::ViewListener
# Context:
# * :issue => Issue being saved
# * :params => HTML parameters
#
def controller_issues_bulk_edit_before_save(context={})
case
when context[:params][:deliverable_id].blank?
# Do nothing
when context[:params][:deliverable_id] == 'none'
# Unassign deliverable
context[:issue].deliverable = nil
else
context[:issue].deliverable = Deliverable.find(context[:params][:deliverable_id])
end
return ''
end
end
end
end
@@ -0,0 +1,25 @@
module RedmineContracts
module Hooks
class ControllerIssuesEditBeforeSaveHook < Redmine::Hook::ViewListener
def controller_issues_edit_before_save(context={})
if context[:params] && context[:params][:issue]
if context[:params][:issue][:deliverable_id].present?
deliverable = Deliverable.find_by_id(context[:params][:issue][:deliverable_id])
if deliverable.contract.project == context[:issue].project
context[:issue].deliverable = deliverable
end
else
context[:issue].deliverable = nil
end
end
return ''
end
alias_method :controller_issues_new_before_save, :controller_issues_edit_before_save
end
end
end
@@ -0,0 +1,23 @@
module RedmineContracts
module Hooks
class HelperIssuesShowDetailAfterSettingHook < Redmine::Hook::ViewListener
# Deliverable changes for the journal use the Deliverable subject
# instead of the id
#
# Context:
# * :detail => Detail about the journal change
#
def helper_issues_show_detail_after_setting(context = { })
# TODO Later: Overwritting the caller is bad juju
if context[:detail].prop_key == 'deliverable_id'
d = Deliverable.find_by_id(context[:detail].value)
context[:detail].value = d.title if d.present? && d.title.present?
d = Deliverable.find_by_id(context[:detail].old_value)
context[:detail].old_value = d.title if d.present? && d.title.present?
end
''
end
end
end
end
@@ -0,0 +1,9 @@
module RedmineContracts
module Hooks
class ViewIssuesBulkEditDetailsBottomHook < Redmine::Hook::ViewListener
render_on(:view_issues_bulk_edit_details_bottom, :partial => 'issues/bulk_edit_deliverable', :layout => false)
end
end
end
@@ -0,0 +1,7 @@
module RedmineContracts
module Hooks
class ViewIssuesFormDetailsBottomHook < Redmine::Hook::ViewListener
render_on(:view_issues_form_details_bottom, :partial => 'issues/edit_deliverable', :layout => false)
end
end
end
@@ -0,0 +1,9 @@
module RedmineContracts
module Hooks
class ViewIssuesShowDetailsBottomHook < Redmine::Hook::ViewListener
include Redmine::I18n
render_on(:view_issues_show_details_bottom, :partial => 'issues/show_deliverable', :layout => false)
end
end
end
@@ -175,6 +175,7 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
setup do
@issue1 = Issue.generate_for_project!(@project_two, :deliverable_id => 2)
@issue2 = Issue.generate_for_project!(@project_two, :deliverable_id => 4)
@issue3 = Issue.generate_for_project!(@project_one, :deliverable_id => 1)
end
@@ -189,7 +190,9 @@ class BudgetPluginMigrationTest < ActionController::IntegrationTest
RedmineContracts::BudgetPluginMigration.migrate(@data)
# The "third" deliverable has an id of 4
assert_equal "Deliverable 2", @issue1.reload.deliverable.title
assert_equal "Version 1.0", @issue2.reload.deliverable.title
assert_equal "Deliverable One", @issue3.reload.deliverable.title
end
end
+6 -3
View File
@@ -5,7 +5,10 @@ class ContractsEditTest < ActionController::IntegrationTest
def setup
@project = Project.generate!(:identifier => 'main')
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15')
@account_executive = User.generate!
@role = Role.generate!
User.add_to_project(@account_executive, @project, @role)
@contract = Contract.generate!(:project => @project, :name => 'A Contract', :payment_terms => 'net_15', :account_executive => @account_executive)
end
should "allow any user to edit the contract" do
@@ -17,7 +20,7 @@ class ContractsEditTest < ActionController::IntegrationTest
assert_response :success
assert_template 'contracts/edit'
assert_select "h2", :text => @contract.name
assert_select "h2", :text => /#{@contract.name}/
assert_select "form#edit_contract_#{@contract.id}.contract" do
assert_select "input[value=?]", /#{@contract.name}/
assert_select "select#contract_payment_terms" do
@@ -26,7 +29,7 @@ class ContractsEditTest < ActionController::IntegrationTest
end
fill_in "Name", :with => 'An updated name'
click_button "Update Contract"
click_button "Save Contract"
assert_response :success
assert_template 'contracts/show'
+3 -1
View File
@@ -17,6 +17,8 @@ class ContractsNewTest < ActionController::IntegrationTest
should "create a new contract" do
@account_executive = User.generate!
@role = Role.generate!
User.add_to_project(@account_executive, @project, @role)
visit_contracts_for_project(@project)
click_link 'New Contract'
@@ -28,7 +30,7 @@ class ContractsNewTest < ActionController::IntegrationTest
fill_in "End Date", :with => '2010-12-31'
select "Net 30", :from => "Payment Terms"
click_button "Create Contract"
click_button "Save Contract"
assert_response :success
assert_template 'contracts/show'
+1 -1
View File
@@ -15,7 +15,7 @@ class ContractsShowTest < ActionController::IntegrationTest
assert_template 'contracts/show'
assert_equal "/projects/main/contracts/#{@contract.id}", current_url
assert_select "div#contract_#{@contract.id}.contract" do
assert_select "div.title-bar" do
assert_select 'h2', :text => @contract.name
end
end
+1 -1
View File
@@ -13,7 +13,7 @@ class DeliverablesDeleteTest < ActionController::IntegrationTest
should "allow anyone to delete the deliverable" do
visit_contract_page(@contract)
click_link_within "#fixed_deliverable_#{@deliverable.id}", 'Delete'
click_link_within "#deliverable_details_#{@deliverable.id}", 'Delete'
assert_response :success
assert_template 'contracts/show'
+2 -2
View File
@@ -16,7 +16,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
should "allow any user to edit the Fixed deliverable" do
visit_contract_page(@contract)
click_link_within "#fixed_deliverable_#{@fixed_deliverable.id}", 'Edit'
click_link_within "#deliverable_details_#{@fixed_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
@@ -45,7 +45,7 @@ class DeliverablesEditTest < ActionController::IntegrationTest
should "allow any user to edit the Hourly deliverable" do
visit_contract_page(@contract)
click_link_within "#hourly_deliverable_#{@hourly_deliverable.id}", 'Edit'
click_link_within "#deliverable_details_#{@hourly_deliverable.id}", 'Edit'
assert_response :success
assert_template 'deliverables/edit'
@@ -0,0 +1,54 @@
require File.dirname(__FILE__) + '/../../../test_helper'
class RedmineContracts::Hooks::ControllerIssuesBulkEditBeforeSaveHookTest < ActionController::IntegrationTest
include Redmine::Hook::Helper
context "#view_issues_bulk_edit_details_bottom" do
setup do
@project = Project.generate!
@issue = Issue.generate_for_project!(@project)
@issue2 = Issue.generate_for_project!(@project)
@issue3 = Issue.generate_for_project!(@project)
@issues = [@issue, @issue2, @issue3]
@contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title 1')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title 2')
@issue.deliverable = @deliverable1
login_as('manager', 'existing')
end
context "when saving multiple issues" do
setup do
visit_issue_bulk_edit_page(@issues)
end
should "allow clearing all of the deliverables" do
select "none", :from => "Deliverable"
click_button "Submit"
assert_response :success
@issues.each do |issue|
assert_equal nil, issue.reload.deliverable
end
end
should "allow assigning a deliverable" do
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issues.each do |issue|
assert_equal @deliverable2, issue.reload.deliverable
end
end
end
end
end
@@ -0,0 +1,59 @@
require File.dirname(__FILE__) + '/../../../test_helper'
class RedmineContracts::Hooks::ControllerIssuesEditBeforeSaveTest < ActionController::IntegrationTest
include Redmine::Hook::Helper
context "#controller_issues_edit_before_save" do
setup do
@project = Project.generate!
IssueStatus.generate!(:is_default => true)
@issue = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing', :admin => true)
@role = Role.generate!(:permissions => [:view_issues, :add_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title for 1')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title for 2')
login_as('manager', 'existing')
@project.reload
end
context "for a new issue" do
setup do
visit_project(@project)
click_link "New issue"
end
should "set the issue's deliverable" do
fill_in "Subject", :with => 'Hook test'
select @deliverable2.title, :from => "Deliverable"
click_button "Create"
assert_response :success
assert_equal @deliverable2, Issue.last.deliverable
end
end
context "for an existing issue" do
setup do
visit_issue_page(@issue)
end
should "update the issue's deliverable" do
select @deliverable2.title, :from => "Deliverable"
click_button "Submit"
assert_response :success
@issue.reload
assert_equal @deliverable2, @issue.deliverable
end
end
end
end
@@ -0,0 +1,58 @@
require File.dirname(__FILE__) + '/../../../test_helper'
class RedmineContracts::Hooks::HelperIssuesShowDetailAfterSettingHookTest < ActionController::IntegrationTest
include Redmine::Hook::Helper
context "#helper_issues_show_detail_after_setting" do
setup do
@project = Project.generate!
@issue = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title 1')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title 2')
# Set first
@issue.init_journal(@manager)
@issue.deliverable = @deliverable1
@issue.save!
# Change
@issue.init_journal(@manager)
@issue.deliverable = @deliverable2
@issue.save!
# Unset
@issue.init_journal(@manager)
@issue.deliverable = nil
@issue.save!
login_as('manager', 'existing')
visit_issue_page(@issue)
assert_response :success
end
should "show when a deliverable is set" do
assert_select ".details" do
assert_select "li", :text => /Deliverable set to #{@deliverable1.title}/
end
end
should "show when a deliverable is changed" do
assert_select ".details" do
assert_select "li", :text => /Deliverable changed from #{@deliverable1.title} to #{@deliverable2.title}/
end
end
should "show when a deliverable is removed" do
assert_select ".details" do
assert_select "li", :text => /Deliverable deleted .*#{@deliverable2.title}/
end
end
end
end
@@ -0,0 +1,55 @@
require File.dirname(__FILE__) + '/../../../test_helper'
class RedmineContracts::Hooks::ViewIssuesBulkEditDetailsBottomHookTest < ActionController::IntegrationTest
include Redmine::Hook::Helper
context "#view_issues_bulk_edit_details_bottom" do
setup do
@project = Project.generate!
@issue = Issue.generate_for_project!(@project)
@issue2 = Issue.generate_for_project!(@project)
@issue3 = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
@issue.deliverable = @deliverable1
login_as('manager', 'existing')
end
context "with Contracts Enabled" do
setup do
visit_issue_bulk_edit_page([@issue, @issue2, @issue3])
end
should "render the a select field for the deliverables with all of the deliverables grouped by contract" do
assert_select "select#deliverable_id" do
assert_select "optgroup[label=?]", @contract1.name do
assert_select "option", :text => /#{@deliverable1.title}/
end
assert_select "optgroup[label=?]", @contract2.name do
assert_select "option", :text => /#{@deliverable2.title}/
end
end
end
end
context "with Contracts Disabled" do
setup do
@project.enabled_modules.collect {|m| m.destroy if m.name == 'contracts' }
visit_issue_bulk_edit_page([@issue, @issue2, @issue3])
end
should "not render the deliverable select field" do
assert_select 'select#deliverable_id', :count => 0
end
end
end
end
@@ -0,0 +1,52 @@
require File.dirname(__FILE__) + '/../../../test_helper'
class RedmineContracts::Hooks::ViewIssuesFormDetailsBottomTest < ActionController::IntegrationTest
include Redmine::Hook::Helper
context "#view_issues_form_details_bottom" do
setup do
@project = Project.generate!
@issue = Issue.generate_for_project!(@project)
@contract1 = Contract.generate!(:project => @project)
@contract2 = Contract.generate!(:project => @project)
@manager = User.generate!(:login => 'manager', :password => 'existing', :password_confirmation => 'existing')
@role = Role.generate!(:permissions => [:view_issues, :edit_issues])
User.add_to_project(@manager, @project, @role)
@deliverable1 = FixedDeliverable.generate!(:contract => @contract1, :manager => @manager, :title => 'The Title')
@deliverable2 = FixedDeliverable.generate!(:contract => @contract2, :manager => @manager, :title => 'The Title')
@issue.deliverable = @deliverable1
login_as('manager', 'existing')
end
context "with Contracts Enabled" do
setup do
visit_issue_page(@issue)
end
should "render the a select field for the deliverables with all of the deliverables grouped by contract" do
assert_select "select#issue_deliverable_id" do
assert_select "optgroup[label=?]", @contract1.name do
assert_select "option", :text => /#{@deliverable1.title}/
end
assert_select "optgroup[label=?]", @contract2.name do
assert_select "option", :text => /#{@deliverable2.title}/
end
end
end
end
context "with Contracts Disabled" do
setup do
@project.enabled_modules.collect {|m| m.destroy if m.name == 'contracts' }
visit_issue_page(@issue)
end
should "not render the deliverable select field" do
assert_select 'select#issue_deliverable_id', :count => 0
end
end
end
end
+8
View File
@@ -71,6 +71,14 @@ module IntegrationTestHelper
assert_template 'contracts/show'
end
def visit_issue_page(issue)
visit '/issues/' + issue.id.to_s
end
def visit_issue_bulk_edit_page(issues)
visit url_for(:controller => 'issues', :action => 'bulk_edit', :ids => issues.collect(&:id))
end
def assert_forbidden
assert_response :forbidden
assert_template 'common/403'
@@ -0,0 +1,77 @@
require File.dirname(__FILE__) + '/../../../../test_helper'
class RedmineContracts::Hooks::ViewIssuesShowDetailsBottomTest < ActionController::TestCase
include Redmine::Hook::Helper
# Bloody bloody hack to work around Rails coupling of _VC.
def template
t = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
def t.template_format
"html"
end
# Rendered views aren't getting access to the controller's I18n module
t.send(:extend, Redmine::I18n)
t
end
def controller
@controller ||= ApplicationController.new
@controller.class.send(:include, ::Redmine::I18n)
@controller.response ||= ActionController::TestResponse.new
# Hack to support render_on
@controller.instance_variable_set('@template', template)
@controller.response = response
@controller
end
def request
@request ||= ActionController::TestRequest.new
end
# Hack to support render_on
def response
@response.template ||= template
@response
end
def hook(args={})
call_hook :view_issues_show_details_bottom, args
end
context "#view_issues_show_details_bottom" do
setup do
@project = Project.generate!
@issue = Issue.generate_for_project!(@project)
@contract = Contract.generate!(:project => @project)
@manager = User.generate!
@role = Role.generate!
User.add_to_project(@manager, @project, @role)
@deliverable = FixedDeliverable.generate!(:contract => @contract, :manager => @manager, :title => 'The Title')
@issue.deliverable = @deliverable
end
context "with Contracts Enabled" do
should "render the deliverable's name" do
@response.body = hook(:project => @project, :issue => @issue, :controller => controller)
assert_select "tr" do
assert_select "td", :text => /#{@deliverable.title}/
end
end
end
context "with Contracts Disabled" do
setup do
@project.enabled_modules.destroy_all
end
should "not render the deliverable's name" do
@response.body = hook(:project => @project, :issue => @issue, :controller => controller)
assert_no_match /#{@deliverable.title}/, @response.body
end
end
end
end