From 5add325bf03b5999fe8222984bd8d13b0b839700 Mon Sep 17 00:00:00 2001 From: hjust Date: Wed, 26 Aug 2009 16:27:24 +0000 Subject: [PATCH] Display end edit of deliverables git-svn-id: https://dev.finn.de/svn/cockpit/trunk@56 7926756e-e54e-46e6-9721-ed318f58905e --- app/controllers/deliverables_controller.rb | 58 +++++++++----- app/helpers/costlog_helper.rb | 12 +++ app/helpers/deliverables_helper.rb | 76 +++++++++++-------- app/models/cost_based_deliverable.rb | 21 +++++ app/models/deliverable.rb | 11 ++- app/models/deliverable_cost.rb | 28 +++++++ .../deliverables/_deliverable_cost.rhtml | 2 +- app/views/deliverables/_edit.rhtml | 31 ++++++++ app/views/deliverables/_form.rhtml | 21 ++--- app/views/deliverables/_list.rhtml | 10 +-- .../_show_cost_based_deliverable.rhtml | 60 +++++++++++++++ app/views/deliverables/edit.rhtml | 4 + app/views/deliverables/index.rhtml | 2 + app/views/deliverables/new.rhtml | 9 ++- app/views/deliverables/show.rhtml | 45 ++++++++--- 15 files changed, 307 insertions(+), 83 deletions(-) create mode 100644 app/views/deliverables/_edit.rhtml create mode 100644 app/views/deliverables/_show_cost_based_deliverable.rhtml create mode 100644 app/views/deliverables/edit.rhtml diff --git a/app/controllers/deliverables_controller.rb b/app/controllers/deliverables_controller.rb index 4b11e2fb84..2f5c3a2e10 100644 --- a/app/controllers/deliverables_controller.rb +++ b/app/controllers/deliverables_controller.rb @@ -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 - @deliverable.deliverable_costs.build - @deliverable.deliverable_hours.build + 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 \ No newline at end of file diff --git a/app/helpers/costlog_helper.rb b/app/helpers/costlog_helper.rb index 4da912430b..8942d9e84e 100644 --- a/app/helpers/costlog_helper.rb +++ b/app/helpers/costlog_helper.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/deliverables_helper.rb b/app/helpers/deliverables_helper.rb index 755ae53c11..d04352e25b 100644 --- a/app/helpers/deliverables_helper.rb +++ b/app/helpers/deliverables_helper.rb @@ -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 = '» ' + 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}'") %> - 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 + 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 + + + end \ No newline at end of file diff --git a/app/models/cost_based_deliverable.rb b/app/models/cost_based_deliverable.rb index 8323757118..9fd9497427 100644 --- a/app/models/cost_based_deliverable.rb +++ b/app/models/cost_based_deliverable.rb @@ -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 diff --git a/app/models/deliverable.rb b/app/models/deliverable.rb index 57d41455d9..eaeb33d88d 100644 --- a/app/models/deliverable.rb +++ b/app/models/deliverable.rb @@ -2,19 +2,23 @@ # 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}} acts_as_activity_provider :find_options => {:include => [:project, :author]}, :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 diff --git a/app/models/deliverable_cost.rb b/app/models/deliverable_cost.rb index 06ee82ebb5..4fce77f1c1 100644 --- a/app/models/deliverable_cost.rb +++ b/app/models/deliverable_cost.rb @@ -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 \ No newline at end of file diff --git a/app/views/deliverables/_deliverable_cost.rhtml b/app/views/deliverables/_deliverable_cost.rhtml index 38b3c7ce2f..d501ca09bb 100644 --- a/app/views/deliverables/_deliverable_cost.rhtml +++ b/app/views/deliverables/_deliverable_cost.rhtml @@ -15,7 +15,7 @@ <%= cost_form.text_field :units, :index => id_or_index, :size => 3 %> "> - <%= h deliverable_cost.cost_type.unit_plural %> + <%=h deliverable_cost.cost_type.unit_plural %> diff --git a/app/views/deliverables/_edit.rhtml b/app/views/deliverables/_edit.rhtml new file mode 100644 index 0000000000..a426f6900d --- /dev/null +++ b/app/views/deliverables/_edit.rhtml @@ -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' %> +
+
<%= l(:label_deliverable) %> + <%= render :partial => 'form', :locals => {:f => f} %> +
+
<%= l(:label_attachment_plural )%> +

<%= render :partial => 'attachments/form' %>

+
+
+ <%= 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 %> + +
+ +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'scm' %> + <%= stylesheet_link_tag 'costs', :plugin => 'redmine_costs' %> + <%= javascript_include_tag 'deliverables', :plugin => 'redmine_costs' %> +<% end %> diff --git a/app/views/deliverables/_form.rhtml b/app/views/deliverables/_form.rhtml index e807e4d6cb..d56beb747e 100644 --- a/app/views/deliverables/_form.rhtml +++ b/app/views/deliverables/_form.rhtml @@ -1,20 +1,11 @@ - - +<% if @deliverable.new_record? %>

<%= f.select :kind, [[l(:label_cost_based_deliverable), CostBasedDeliverable.name], [l(:label_fixed_deliverable), FixedDeliverable.name]], {:required => true, :prompt => true }, :autocomplete => 'off' %>

<%= observe_field :deliverable_kind, :url => { :action => :new }, :update => :content, :with => "Form.serialize('deliverable-form')" %>
+<% end %>

<%= f.text_field :subject, :size => 80, :required => true %>

<%= f.text_area :description, @@ -37,7 +28,7 @@ <%= l(:caption_cost_unit_plural)%> <%= l(:caption_cost_type) %> - <% if User.current.allowed_to?(:view_unit_price, @project)%><%= l(:caption_overall_costs) %><%end%> + <% if User.current.allowed_to?(:view_unit_price, @project)%><%= l(:caption_budget) %><%end%> @@ -56,7 +47,7 @@ <%= l(:field_hours)%> <%= l(:label_user) %> <% if User.current.allowed_to?(:view_all_rates, @project) || User.current.allowed_to?(:view_own_rate, @project) %> - <%= l(:caption_overall_costs) %> + <%= l(:caption_budget) %> <% end %> @@ -84,4 +75,8 @@

+<% if @deliverable.new_record? %> +

<%= render :partial => 'attachments/form' %>

+<% end %> + <%= wikitoolbar_for 'deliverable_description' %> diff --git a/app/views/deliverables/_list.rhtml b/app/views/deliverables/_list.rhtml index 5622b3ada0..7ab1de7ed0 100644 --- a/app/views/deliverables/_list.rhtml +++ b/app/views/deliverables/_list.rhtml @@ -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%')) %> <% end %> diff --git a/app/views/deliverables/_show_cost_based_deliverable.rhtml b/app/views/deliverables/_show_cost_based_deliverable.rhtml new file mode 100644 index 0000000000..563a69acd5 --- /dev/null +++ b/app/views/deliverables/_show_cost_based_deliverable.rhtml @@ -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) +-%> + +
+

<%= l(:caption_materials_budget)%>

+ <% unless @deliverable.deliverable_costs.empty? %> + + + + + <% if show_cost_budget_col %> + + <%end%> + + + <% @deliverable.deliverable_costs.each do |deliverable_cost| %> + + + + <% if show_cost_budget_col %> + + <% end %> + + <% end %> + <% if show_cost_budget_col %> + + <% end %> + +
<%= l(:caption_cost_unit_plural)%><%= l(:caption_cost_type) %><%= l(:caption_budget) %>
<%=h pluralize(deliverable_cost.units, deliverable_cost.cost_type.unit, deliverable_cost.cost_type.unit_plural) %><%=h deliverable_cost.cost_type.name %><%= number_to_currency(deliverable_cost.costs) %>
<%= number_to_currency(@deliverable.materials_budget) %>
+ <% end %> +
+

<%= l(:caption_labor_budget)%>

+ <% unless @deliverable.deliverable_hours.empty? %> + + + + + <% if show_hour_budget_col %> + + <%end%> + + + <% @deliverable.deliverable_hours.each do |deliverable_hour| %> + + + + <% if show_hour_budget_col %> + + <% end %> + + <% end %> + <% if User.current.allowed_to?(:view_all_rates, @project) %> + + <% end %> + +
<%= l(:field_hours)%><%= l(:label_user) %><%= l(:caption_budget) %>
<%= deliverable_hour.hours %>h<%=h deliverable_hour.user.name %><%= number_to_currency(deliverable_hour.costs) %>
<%= number_to_currency(@deliverable.labor_budget) %>
+ <% end %> +
\ No newline at end of file diff --git a/app/views/deliverables/edit.rhtml b/app/views/deliverables/edit.rhtml new file mode 100644 index 0000000000..246127f6a9 --- /dev/null +++ b/app/views/deliverables/edit.rhtml @@ -0,0 +1,4 @@ +

<%=h "Deliverable ##{@deliverable.id}" %>

+ +<%= render :partial => 'edit' %> +<%= javascript_tag "Form.Element.focus('deliverable_subject');" %> diff --git a/app/views/deliverables/index.rhtml b/app/views/deliverables/index.rhtml index d5f8f9d2e0..7f1144e44a 100644 --- a/app/views/deliverables/index.rhtml +++ b/app/views/deliverables/index.rhtml @@ -16,7 +16,9 @@ <%= l(:label_export_to) %> <%= link_to 'CSV', {:format => 'csv'}, :class => 'csv' %> +

diff --git a/app/views/deliverables/new.rhtml b/app/views/deliverables/new.rhtml index 674f11e1d8..2f28b5e2f0 100644 --- a/app/views/deliverables/new.rhtml +++ b/app/views/deliverables/new.rhtml @@ -1,6 +1,6 @@

<%=l(:label_deliverable_new)%>

-<% labelled_tabular_form_for :deliverable, @deliverable, +<% labelled_tabular_form_for :deliverable, @deliverable, :html => {:multipart => true, :id => 'deliverable-form'} do |f| %> <%= error_messages_for 'deliverable' %>
@@ -26,3 +26,10 @@ <%= stylesheet_link_tag 'costs', :plugin => 'redmine_costs' %> <%= javascript_include_tag 'deliverables', :plugin => 'redmine_costs' %> <% end %> + + + + + + + diff --git a/app/views/deliverables/show.rhtml b/app/views/deliverables/show.rhtml index 879d55bdce..eb3724d336 100644 --- a/app/views/deliverables/show.rhtml +++ b/app/views/deliverables/show.rhtml @@ -1,6 +1,5 @@
-<%= 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' %>
@@ -16,21 +15,47 @@ - - + + + + + + + + + + + + + +
<%=l(:field_type)%>:<%= @deliverable.type_label %><%=l(:field_fixed_date)%>:<%= format_date(@deliverable.fixed_date) %><%=l(:field_type)%>:<%= @deliverable.type_label %><%=l(:field_fixed_date)%>:<%= format_date(@deliverable.fixed_date) %>
<%=l(:field_status)%>:<%= @deliverable.status %><%=l(:field_progress)%>:<%= extended_progress_bar(@deliverable.progress, :width => '80px', :legend => "#{@deliverable.progress}%") %>
+

<%=l(:field_description)%>

+
+ <%= textilizable @deliverable, :description, :attachments => @deliverable.attachments %> +
+ + <%= link_to_attachments @deliverable %> + + <%= render :partial => "show_#{@deliverable.kind.underscore}" %>
-<% labelled_tabular_form_for :deliverable, @deliverable, - :html => {:multipart => true, :id => 'deliverable-form'} do |f| %> - <%= error_messages_for 'deliverable' %> -
- <%= render :partial => 'form', :locals => {:f => f} %> -
+
+ +<% if authorize_for('deliverables', 'edit') %> + <% end %> +

+<%= l(:label_export_to) %> +<%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %> +

+ <% html_title "#{l(:label_deliverable)} ##{@deliverable.id}: #{@deliverable.subject}" %> <% content_for :sidebar do %>