Display end edit of deliverables

git-svn-id: https://dev.finn.de/svn/cockpit/trunk@56 7926756e-e54e-46e6-9721-ed318f58905e
pull/6827/head
hjust 15 years ago
parent 1b23ee97e0
commit 5add325bf0
  1. 54
      app/controllers/deliverables_controller.rb
  2. 12
      app/helpers/costlog_helper.rb
  3. 76
      app/helpers/deliverables_helper.rb
  4. 21
      app/models/cost_based_deliverable.rb
  5. 9
      app/models/deliverable.rb
  6. 28
      app/models/deliverable_cost.rb
  7. 2
      app/views/deliverables/_deliverable_cost.rhtml
  8. 31
      app/views/deliverables/_edit.rhtml
  9. 21
      app/views/deliverables/_form.rhtml
  10. 10
      app/views/deliverables/_list.rhtml
  11. 60
      app/views/deliverables/_show_cost_based_deliverable.rhtml
  12. 4
      app/views/deliverables/edit.rhtml
  13. 2
      app/views/deliverables/index.rhtml
  14. 7
      app/views/deliverables/new.rhtml
  15. 43
      app/views/deliverables/show.rhtml

@ -22,8 +22,11 @@ class DeliverablesController < ApplicationController
include SortHelper
helper :projects
include ProjectsHelper
helper :attachments
include AttachmentsHelper
helper :costlog
include CostlogHelper
include Redmine::Export::PDF
def index
# TODO: This is a very naiive implementation.
@ -31,6 +34,12 @@ class DeliverablesController < ApplicationController
# (see issues_controller.rb)
limit = per_page_option
respond_to do |format|
format.html { }
format.csv { limit = Setting.issues_export_limit.to_i }
format.pdf { limit = Setting.issues_export_limit.to_i }
end
sort_columns = {'id' => "#{Deliverable.table_name}.id",
'subject' => "#{Deliverable.table_name}.subject",
@ -53,6 +62,8 @@ class DeliverablesController < ApplicationController
respond_to do |format|
format.html { render :action => 'index', :layout => !request.xhr? }
format.csv { send_data(deliverables_to_csv(@deliverables, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
format.pdf { send_data(deliverables_to_pdf(@deliverables, @project), :type => 'application/pdf', :filename => 'export.pdf') }
end
end
@ -75,7 +86,7 @@ class DeliverablesController < ApplicationController
end
@deliverable ||= Deliverable.new
@deliverable.project_id = @project.id unless @deliverable.project
@deliverable.project_id = @project.id
@deliverable.author_id = User.current.id
# fixed_date must be set before deliverable_costs and deliverable_hours
@ -99,11 +110,37 @@ class DeliverablesController < ApplicationController
end
end
case @deliverable.kind
when CostBasedDeliverable.name
@deliverable.deliverable_costs.build
@deliverable.deliverable_hours.build
end
render :layout => !request.xhr?
end
# Blacklist of attributes which can not be updated in edit
EDIT_BLACK_LIST = %w(kind type)
def edit
if params[:deliverable]
attrs = params[:deliverable].dup
attrs.delete_if {|k,v| EDIT_BLACK_LIST.include?(k)}
@deliverable.attributes = attrs
end
if request.post?
if @deliverable.save
flash[:notice] = l(:notice_successful_update)
redirect_to(params[:back_to] || {:action => 'show', :id => @deliverable})
end
end
rescue ActiveRecord::StaleObjectError
# Optimistic locking exception
flash.now[:error] = l(:notice_locking_conflict)
end
def preview
@deliverable = Deliverables.find_by_id(params[:id]) unless params[:id].blank?
@text = params[:notes] || (params[:deliverable] ? params[:deliverable][:description] : nil)
@ -207,19 +244,4 @@ private
rescue ActiveRecord::RecordNotFound
render_404
end
def desired_type
if params[:deliverable]
case params[:deliverable].delete(:desired_type)
when "FixedDeliverable"
FixedDeliverable
when "CostBasedDeliverable"
CostBasedDeliverable
else
Deliverable
end
else
Deliverable
end
end
end

@ -69,5 +69,17 @@ module CostlogHelper
export
end
def extended_progress_bar(pcts, options={})
return progress_bar(pcts, options) unless pcts.is_a?(Numeric) && pcts > 100
width = options[:width] || '100px;'
legend = options[:legend] || ''
content_tag('table',
content_tag('tr',
content_tag('td', '', :style => "width: 100%;", :class => 'exceeded')
), :class => 'progress', :style => "width: #{width};") +
content_tag('p', legend, :class => 'pourcent')
end
end

@ -7,44 +7,56 @@ module DeliverablesHelper
return User.current.allowed_to?(:edit_deliverables, @project)
end
def allowed_projects_for_edit
cond = ARCondition.new
cond << Project.allowed_to_condition(User.current, :edit_deliverables)
projects = User.current.projects.find(:all, :conditions => cond.conditions, :include => :parent).group_by(&:root)
result = []
projects.keys.sort.each do |root|
result << root
projects[root].sort.each do |project|
next if project == root
project.name = '&#187; ' + h(project.name)
result << project
end
end
result
end
def add_deliverable_cost_link(name)
link_to_function name do |page|
page.insert_html :bottom, :deliverable_costs_body, :partial => "deliverable_cost", :object => DeliverableCost.new
end
end
def fields_for_deliverable_cost(deliverable_cost, &block)
prefix = deliverable_cost.new_record? ? "new" : "existing"
fields_for("deliverable[#{prefix}_deliverable_cost_attributes][]", deliverable_cost, &block)
end
def function_update_deliverable_cost
#remote_function(:url => {:action => :update_deliverable_costs_row},
# :with => "'cost_type_id=' + encodeURIComponent(value) + '&units=' + encodeURIComponent(this.parentElement.previousSibling.firstChild.value) + '&element_id=deliverable_costs#{suffix}_#{row_id}'") %>
def deliverables_to_csv(deliverables, project)
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
decimal_separator = l(:general_csv_decimal_separator)
export = StringIO.new
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
# csv header fields
headers = [ "#",
l(:field_status),
l(:field_project),
l(:field_subject),
l(:field_score),
l(:field_author),
l(:field_fixed_date),
l(:field_materials_budget),
l(:field_labor_budget),
l(:field_spent),
l(:field_created_on),
l(:field_updated_on),
l(:field_description)
]
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
# csv lines
deliverables.each do |issue|
fields = [issue.id,
deliverable.status.name,
deliverable.project.name,
deliverable.subject,
deliverable.score,
deliverable.author.name,
format_date(deliverable.fixed_date),
deliverable.kind == "CostBasedDeliverable" ? number_to_currency(deliverable.materials_budget) : "",
deliverable.kind == "CostBasedDeliverable" ? number_to_currency(deliverable.labor_budget) : "",
deliverable.kind == "CostBasedDeliverable" ? number_to_currency(deliverable.spent) : "",
format_time(deliverable.created_on),
format_time(deliverable.updated_on),
deliverable.description
]
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
end
end
export.rewind
export
end
def add_deliverable_hour_link(name)
link_to_remote name do |page|
page.insert_html :bottom, "deliverable_hours_body", :partial => "deliverable_hour", :object => DeliverableHour.new
end
end
end

@ -5,6 +5,11 @@ class CostBasedDeliverable < Deliverable
validates_associated :deliverable_costs
validates_associated :deliverable_hours
def before_save
# set budget to correct value
self.budget = deliverable_hours.inject(0.0) {|sum,d| sum + d.costs} + deliverable_costs.inject(0.0) {|sum, d| d.costs + sum}
end
def copy_from(arg)
deliverable = arg.is_a?(CostBasedDeliverable) ? arg : CostBasedDeliverable.find(arg)
self.attributes = deliverable.attributes.dup
@ -17,6 +22,22 @@ class CostBasedDeliverable < Deliverable
return l(:label_cost_based_deliverable)
end
def materials_budget
if User.current.allowed_to?(:view_unit_price, project)
deliverable_costs.inject(0.0) {|sum, d| d.costs + sum}
else
nil
end
end
def labor_budget
if User.current.allowed_to?(:view_all_rates, project)
deliverable_hours.inject(0.0) {|sum,d| sum + d.costs}
else
nil
end
end
def new_deliverable_cost_attributes=(deliverable_cost_attributes)
deliverable_cost_attributes.each do |index, attributes|
deliverable_costs.build(attributes) if attributes[:units].to_i > 0

@ -2,12 +2,13 @@
# contain a collection of issues.
class Deliverable < ActiveRecord::Base
unloadable
validates_presence_of :subject
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :project
has_many :issues
acts_as_attachable :after_remove => :attachment_removed
acts_as_event :title => Proc.new {|o| "#{l(:label_deliverable)} ##{o.id}: #{o.subject}"},
:url => Proc.new {|o| {:controller => 'deliverables', :action => 'show', :id => o.id}}
@ -15,6 +16,9 @@ class Deliverable < ActiveRecord::Base
:timestamp => "#{table_name}.updated_on",
:author_key => :author_id
validates_presence_of :subject, :project, :author
validates_length_of :subject, :maximum => 255
def copy_from(arg)
deliverable = arg.is_a?(Deliverable) ? arg : Deliverable.find(arg)
self.attributes = deliverable.attributes.dup
@ -64,6 +68,7 @@ class Deliverable < ActiveRecord::Base
# Amount spent. Virtual accessor that is overriden by subclasses.
def spent
# TODO: check rights to see rates
0
end
@ -93,7 +98,7 @@ class Deliverable < ActiveRecord::Base
def progress
return 0 unless self.issues.size > 0
total ||= self.issues.collect(&:estimated_hours).compact.sum || 0
total = self.issues.collect(&:estimated_hours).compact.sum || 0
return 0 unless total > 0
balance = 0.0

@ -34,4 +34,32 @@ class DeliverableCost < ActiveRecord::Base
def costs
rate && units ? rate.rate * units : 0.0
end
def progres
# TODO: not yet finished
raise NotImplementedError.new
return 0 unless self.issues.size > 0
total_hours =
total_costs = self.issues.collect{|i| i.cost_entries.collect(&:cost).compact.sum || 0}.compact.sum || 0
return 0 unless total > 0
balance = 0.0
self.issues.each do |issue|
if use_issue_status_for_done_ratios?
balance += issue.status.default_done_ratio * issue.estimated_hours unless issue.estimated_hours.nil?
else
balance += issue.done_ratio * issue.estimated_hours unless issue.estimated_hours.nil?
end
end
return (balance / total).round
end
end

@ -15,7 +15,7 @@
<td class="cost_units">
<%= cost_form.text_field :units, :index => id_or_index, :size => 3 %>
<span id="<%= "#{id_prefix}_unit_name" %>">
<%= h deliverable_cost.cost_type.unit_plural %>
<%=h deliverable_cost.cost_type.unit_plural %>
</span>
</td>
<td>

@ -0,0 +1,31 @@
<% labelled_tabular_form_for :deliverable, @deliverable,
:url => {:action => 'edit', :id => @deliverable},
:html => {:multipart => true,
:id => 'deliverable-form',
:class => nil} do |f| %>
<%= error_messages_for 'deliverable' %>
<div class="box">
<fieldset class="tabular"><legend><%= l(:label_deliverable) %></legend>
<%= render :partial => 'form', :locals => {:f => f} %>
</fieldset>
<fieldset><legend><%= l(:label_attachment_plural )%></legend>
<p><%= render :partial => 'attachments/form' %></p>
</fieldset>
</div>
<%= submit_tag l(:button_submit) %>
<%= link_to_remote l(:label_preview),
{ :url => { :controller => 'deliverables', :action => 'preview', :project_id => @project },
:method => 'post',
:update => 'preview',
:with => "Form.serialize('deliverable-form')",
:complete => "Element.scrollTo('preview')"
}, :accesskey => accesskey(:preview) %>
<% end %>
<div id="preview" class="wiki"></div>
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'scm' %>
<%= stylesheet_link_tag 'costs', :plugin => 'redmine_costs' %>
<%= javascript_include_tag 'deliverables', :plugin => 'redmine_costs' %>
<% end %>

@ -1,20 +1,11 @@
<script type="text/javascript">
//<![CDATA[
<%-
deliverable_costs_row_id = @deliverable_costs_new ? @deliverable_costs_new.length + 1 : 1
deliverable_hours_row_id = @deliverable_hours_new ? @deliverable_hours_new.length + 1 : 1
-%>
deliverableCostsRowId = <%= deliverable_costs_row_id %>;
deliverableHoursRowId = <%= deliverable_hours_row_id %>;
//]]>
</script>
<% if @deliverable.new_record? %>
<p><%= f.select :kind, [[l(:label_cost_based_deliverable), CostBasedDeliverable.name], [l(:label_fixed_deliverable), FixedDeliverable.name]], {:required => true, :prompt => true }, :autocomplete => 'off' %></p>
<%= observe_field :deliverable_kind, :url => { :action => :new },
:update => :content,
:with => "Form.serialize('deliverable-form')" %>
<hr />
<% end %>
<p><%= f.text_field :subject, :size => 80, :required => true %></p>
<p><%= f.text_area :description,
@ -37,7 +28,7 @@
<thead><tr>
<th><%= l(:caption_cost_unit_plural)%></th>
<th><%= l(:caption_cost_type) %></th>
<% if User.current.allowed_to?(:view_unit_price, @project)%><th id="deliverable_costs_price"><%= l(:caption_overall_costs) %></th><%end%>
<% if User.current.allowed_to?(:view_unit_price, @project)%><th id="deliverable_costs_price"><%= l(:caption_budget) %></th><%end%>
<th></th>
</tr></thead>
<tbody id="deliverable_costs_body">
@ -56,7 +47,7 @@
<th><%= l(:field_hours)%></th>
<th><%= l(:label_user) %></th>
<% if User.current.allowed_to?(:view_all_rates, @project) || User.current.allowed_to?(:view_own_rate, @project) %>
<th id="deliverable_hours_price"><%= l(:caption_overall_costs) %></th>
<th id="deliverable_hours_price"><%= l(:caption_budget) %></th>
<% end %>
<th></th>
</tr></thead>
@ -84,4 +75,8 @@
<div style="clear: both;"> </div>
<% if @deliverable.new_record? %>
<p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
<% end %>
<%= wikitoolbar_for 'deliverable_description' %>

@ -23,12 +23,12 @@
<%= content_tag(:td, h(deliverable.status), :class => 'status') %>
<%= content_tag(:td, link_to(h(deliverable.subject), :controller => 'deliverables', :action => 'show', :id => deliverable), :class => 'subject') %>
<%= content_tag(:td, h(deliverable.score.to_i), :class => 'score') %>
<%= content_tag(:td, number_to_currency(deliverable.budget || 0.0, :precision => 0), :class => 'budget') %>
<%= content_tag(:td, number_to_currency(deliverable.labor_budget || 0.0, :precision => 0), :class => 'budget') %>
<%= content_tag(:td, number_to_currency(deliverable.materials_budget || 0.0, :precision => 0), :class => 'budget') %>
<%= content_tag(:td, number_to_currency(deliverable.spent, :precision => 0), :class => 'spent') %>
<%= content_tag(:td, number_to_currency(deliverable.budget || 0.0, :precision => 0), :class => 'currency') %>
<%= content_tag(:td, number_to_currency(deliverable.labor_budget || 0.0, :precision => 0), :class => 'currency') %>
<%= content_tag(:td, number_to_currency(deliverable.materials_budget || 0.0, :precision => 0), :class => 'currency') %>
<%= content_tag(:td, number_to_currency(deliverable.spent, :precision => 0), :class => 'currency') %>
<%= content_tag(:td, format_date(deliverable.fixed_date), :class => 'fixed_date') %>
<%= content_tag(:td, progress_bar(deliverable.progress, :width => '100%', :class => 'done_ratio')) %>
<%= content_tag(:td, extended_progress_bar(deliverable.progress, :width => '100%')) %>
</tr>
<% end %>
</tbody>

@ -0,0 +1,60 @@
<%
show_cost_budget_col = User.current.allowed_to?(:view_unit_price, @project)
show_hour_budget_col = User.current.allowed_to?(:view_all_rates, @project) || User.current.allowed_to?(:view_own_rate, @project)
-%>
<table width="100%"><tr style="vertical-align: top"><td width="50%">
<h4><%= l(:caption_materials_budget)%></h4>
<% unless @deliverable.deliverable_costs.empty? %>
<table class="deliverable_costs">
<thead><tr>
<th><%= l(:caption_cost_unit_plural)%></th>
<th><%= l(:caption_cost_type) %></th>
<% if show_cost_budget_col %>
<th><%= l(:caption_budget) %></th>
<%end%>
</tr></thead>
<tbody>
<% @deliverable.deliverable_costs.each do |deliverable_cost| %>
<tr>
<td><%=h pluralize(deliverable_cost.units, deliverable_cost.cost_type.unit, deliverable_cost.cost_type.unit_plural) %></td>
<td><%=h deliverable_cost.cost_type.name %></td>
<% if show_cost_budget_col %>
<td class="currency"><%= number_to_currency(deliverable_cost.costs) %></td>
<% end %>
</tr>
<% end %>
<% if show_cost_budget_col %>
<tr><td colspan="3" class="currency"><strong><%= number_to_currency(@deliverable.materials_budget) %></strong></td></tr>
<% end %>
</tbody>
</table>
<% end %>
</td><td width="50%">
<h4><%= l(:caption_labor_budget)%></h4>
<% unless @deliverable.deliverable_hours.empty? %>
<table class="deliverable_hours">
<thead><tr>
<th><%= l(:field_hours)%></th>
<th><%= l(:label_user) %></th>
<% if show_hour_budget_col %>
<th><%= l(:caption_budget) %></th>
<%end%>
</tr></thead>
<tbody>
<% @deliverable.deliverable_hours.each do |deliverable_hour| %>
<tr>
<td class=""><%= deliverable_hour.hours %>h</td>
<td><%=h deliverable_hour.user.name %></td>
<% if show_hour_budget_col %>
<td class="currency"><%= number_to_currency(deliverable_hour.costs) %></td>
<% end %>
</tr>
<% end %>
<% if User.current.allowed_to?(:view_all_rates, @project) %>
<tr><td colspan="3" class="currency"><strong><%= number_to_currency(@deliverable.labor_budget) %></strong></td></tr>
<% end %>
</tbody>
</table>
<% end %>
</td></tr></table>

@ -0,0 +1,4 @@
<h2><%=h "Deliverable ##{@deliverable.id}" %></h2>
<%= render :partial => 'edit' %>
<%= javascript_tag "Form.Element.focus('deliverable_subject');" %>

@ -16,7 +16,9 @@
<%= l(:label_export_to) %>
<!--
<span><%= link_to 'Atom', {:query_id => @query, :format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
-->
<span><%= link_to 'CSV', {:format => 'csv'}, :class => 'csv' %></span>
<!--
<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
-->
</p>

@ -26,3 +26,10 @@
<%= stylesheet_link_tag 'costs', :plugin => 'redmine_costs' %>
<%= javascript_include_tag 'deliverables', :plugin => 'redmine_costs' %>
<% end %>

@ -1,6 +1,5 @@
<div class="contextual">
<%= link_to_if_authorized l(:button_add_deliverable), {:controller => 'deliverables', :action => 'new', :project_id => @project }, :class => 'icon icon-add' %>
<%= link_to_if_authorized l(:button_update), {:controller => 'deliverables', :action => 'edit', :id => @deliverable}, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
<%= link_to_if_authorized l(:button_update), {:controller => 'deliverables', :action => 'edit', :id => @deliverable}, :onclick => 'showAndScrollTo("update", "deliverable_description"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
<%= link_to_if_authorized l(:button_copy), {:controller => 'deliverables', :action => 'new', :project_id => @project, :copy_from => @deliverable }, :class => 'icon icon-copy' %>
<%= link_to_if_authorized l(:button_delete), {:controller => 'deliverables', :action => 'destroy', :id => @deliverable}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
</div>
@ -16,21 +15,47 @@
<table width="100%">
<tr>
<td style="width:15%" class="type"><b><%=l(:field_type)%>:</b></td><td style="width:35%" class="type"><%= @deliverable.type_label %></td>
<td style="width:15%" class="due-date"><b><%=l(:field_fixed_date)%>:</b></td><td style="width:35%"><%= format_date(@deliverable.fixed_date) %></td>
<td style="width:15%" class="type"><strong><%=l(:field_type)%>:</strong></td>
<td style="width:35%" class="type"><%= @deliverable.type_label %></td>
<td style="width:15%" class="due-date"><strong><%=l(:field_fixed_date)%>:</strong></td>
<td style="width:35%"><%= format_date(@deliverable.fixed_date) %></td>
</tr>
<tr>
<td class="status"><strong><%=l(:field_status)%>:</strong></td>
<td class="status"><%= @deliverable.status %></td>
<td class="progress"><strong><%=l(:field_progress)%>:</strong></td>
<td class="progress"><%= extended_progress_bar(@deliverable.progress, :width => '80px', :legend => "#{@deliverable.progress}%") %></td>
</tr>
</table>
<p><strong><%=l(:field_description)%></strong></p>
<div class="wiki">
<%= textilizable @deliverable, :description, :attachments => @deliverable.attachments %>
</div>
<%= link_to_attachments @deliverable %>
<%= render :partial => "show_#{@deliverable.kind.underscore}" %>
</div>
<% labelled_tabular_form_for :deliverable, @deliverable,
:html => {:multipart => true, :id => 'deliverable-form'} do |f| %>
<%= error_messages_for 'deliverable' %>
<div class="box">
<%= render :partial => 'form', :locals => {:f => f} %>
<div style="clear: both;"></div>
<% if authorize_for('deliverables', 'edit') %>
<div id="update" style="display:none;">
<h3><%= l(:button_update) %></h3>
<%= render :partial => 'edit' %>
</div>
<% end %>
<p class="other-formats">
<%= l(:label_export_to) %>
<span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
</p>
<% html_title "#{l(:label_deliverable)} ##{@deliverable.id}: #{@deliverable.subject}" %>
<% content_for :sidebar do %>

Loading…
Cancel
Save