diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/gantts_controller.rb | 24 | ||||
-rw-r--r-- | app/controllers/issues_controller.rb | 1 | ||||
-rw-r--r-- | app/helpers/application_helper.rb | 7 | ||||
-rw-r--r-- | app/helpers/gantt_helper.rb | 24 | ||||
-rw-r--r-- | app/helpers/issues_helper.rb | 4 | ||||
-rw-r--r-- | app/models/issue.rb | 27 | ||||
-rw-r--r-- | app/models/project.rb | 44 | ||||
-rw-r--r-- | app/models/version.rb | 12 | ||||
-rw-r--r-- | app/views/gantts/show.html.erb | 80 |
9 files changed, 133 insertions, 90 deletions
diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb index 6a6071e86..50fd8c13d 100644 --- a/app/controllers/gantts_controller.rb +++ b/app/controllers/gantts_controller.rb @@ -4,6 +4,7 @@ class GanttsController < ApplicationController rescue_from Query::StatementInvalid, :with => :query_statement_invalid + helper :gantt helper :issues helper :projects helper :queries @@ -14,32 +15,17 @@ class GanttsController < ApplicationController def show @gantt = Redmine::Helpers::Gantt.new(params) + @gantt.project = @project retrieve_query @query.group_by = nil - if @query.valid? - events = [] - # Issues that have start and due dates - events += @query.issues(:include => [:tracker, :assigned_to, :priority], - :order => "start_date, due_date", - :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] - ) - # Issues that don't have a due date but that are assigned to a version with a date - events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version], - :order => "start_date, effective_date", - :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] - ) - # Versions - events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to]) - - @gantt.events = events - end + @gantt.query = @query if @query.valid? basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' respond_to do |format| format.html { render :action => "show", :layout => !request.xhr? } - format.png { send_data(@gantt.to_image(@project), :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') - format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") } + format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') + format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 0364e307c..9f58cb0a1 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -47,6 +47,7 @@ class IssuesController < ApplicationController include SortHelper include IssuesHelper helper :timelog + helper :gantt include Redmine::Export::PDF verify :method => [:post, :delete], diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0fb44a22b..34b17c760 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -121,6 +121,11 @@ module ApplicationHelper link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) end + + def link_to_project(project, options={}) + options[:class] ||= 'project' + link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => options[:class]) + end # Generates a link to a project if active # Examples: @@ -832,6 +837,8 @@ module ApplicationHelper email = $1 end return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil + else + '' end end diff --git a/app/helpers/gantt_helper.rb b/app/helpers/gantt_helper.rb new file mode 100644 index 000000000..38f3765e9 --- /dev/null +++ b/app/helpers/gantt_helper.rb @@ -0,0 +1,24 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module GanttHelper + def number_of_issues_on_versions(gantt) + versions = gantt.events.collect {|event| (event.is_a? Version) ? event : nil}.compact + + versions.sum {|v| v.fixed_issues.for_gantt.with_query(@query).count} + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 617822986..284aae91a 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -35,8 +35,10 @@ module IssuesHelper @cached_label_due_date ||= l(:field_due_date) @cached_label_assigned_to ||= l(:field_assigned_to) @cached_label_priority ||= l(:field_priority) - + @cached_label_project ||= l(:field_project) + link_to_issue(issue) + "<br /><br />" + + "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" + "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" + "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" + "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" + diff --git a/app/models/issue.rb b/app/models/issue.rb index 7d0682df1..80db48108 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -62,10 +62,28 @@ class Issue < ActiveRecord::Base named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status - named_scope :recently_updated, :order => "#{self.table_name}.updated_on DESC" + named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" named_scope :with_limit, lambda { |limit| { :limit => limit} } named_scope :on_active_project, :include => [:status, :project, :tracker], :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] + named_scope :for_gantt, lambda { + { + :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], + :order => "#{Issue.table_name}.due_date ASC, #{Issue.table_name}.start_date ASC, #{Issue.table_name}.id ASC" + } + } + + named_scope :without_version, lambda { + { + :conditions => { :fixed_version_id => nil} + } + } + + named_scope :with_query, lambda {|query| + { + :conditions => Query.merge_conditions(query.statement) + } + } before_create :default_assign before_save :reschedule_following_issues, :close_duplicates, :update_done_ratio_from_issue_status @@ -357,6 +375,13 @@ class Issue < ActiveRecord::Base def overdue? !due_date.nil? && (due_date < Date.today) && !status.is_closed? end + + # Is the amount of work done less than it should for the due date + def behind_schedule? + return false if start_date.nil? || due_date.nil? + done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor + return done_date <= Date.today + end # Users the issue can be assigned to def assignable_users diff --git a/app/models/project.rb b/app/models/project.rb index 931f89b55..5ef7915de 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -412,6 +412,50 @@ class Project < ActiveRecord::Base def short_description(length = 255) description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description end + + # The earliest start date of a project, based on it's issues and versions + def start_date + if module_enabled?(:issue_tracking) + [ + issues.minimum('start_date'), + shared_versions.collect(&:effective_date), + shared_versions.collect {|v| v.fixed_issues.minimum('start_date')} + ].flatten.compact.min + end + end + + # The latest due date of an issue or version + def due_date + if module_enabled?(:issue_tracking) + [ + issues.maximum('due_date'), + shared_versions.collect(&:effective_date), + shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} + ].flatten.compact.max + end + end + + def overdue? + active? && !due_date.nil? && (due_date < Date.today) + end + + # Returns the percent completed for this project, based on the + # progress on it's versions. + def completed_percent(options={:include_subprojects => false}) + if options.delete(:include_subprojects) + total = self_and_descendants.collect(&:completed_percent).sum + + total / self_and_descendants.count + else + if versions.count > 0 + total = versions.collect(&:completed_pourcent).sum + + total / versions.count + else + 100 + end + end + end # Return true if this project is allowed to do the specified action. # action can be: diff --git a/app/models/version.rb b/app/models/version.rb index 07e66434d..c3969fe87 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -73,6 +73,18 @@ class Version < ActiveRecord::Base def completed? effective_date && (effective_date <= Date.today) && (open_issues_count == 0) end + + def behind_schedule? + if completed_pourcent == 100 + return false + elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong... + start_date = fixed_issues.minimum('start_date') + done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor + return done_date <= Date.today + else + false # No issues so it's not late + end + end # Returns the completion percentage of this version based on the amount of open/closed issues # and the time spent on the open issues. diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index 5d4ef0dbf..ce8c67b26 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -1,3 +1,4 @@ +<% @gantt.view = self %> <h2><%= l(:label_gantt) %></h2> <% form_tag(gantt_path(:month => params[:month], :year => params[:year], :months => params[:months]), :method => :put, :id => 'query_form') do %> @@ -55,11 +56,12 @@ if @gantt.zoom >1 end end +# Width of the entire chart g_width = (@gantt.date_to - @gantt.date_from + 1)*zoom -g_height = [(20 * @gantt.events.length + 6)+150, 206].max +# Collect the number of issues on Versions +g_height = [(20 * (@gantt.number_of_rows + 6))+150, 206].max t_height = g_height + headers_height %> - <table width="100%" style="border:0; border-collapse: collapse;"> <tr> <td style="width:<%= subject_width %>px; padding:0px;"> @@ -67,26 +69,10 @@ t_height = g_height + headers_height <div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;"> <div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div> <div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div> -<% -# -# Tasks subjects -# -top = headers_height + 8 -@gantt.events.each do |i| -left = 4 + (i.is_a?(Issue) ? i.level * 16 : 0) - %> - <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:<%= left %>px;overflow:hidden;"><small> - <% if i.is_a? Issue %> - <%= h("#{i.project} -") unless @project && @project == i.project %> - <%= link_to_issue i %> - <% else %> - <span class="icon icon-package"> - <%= link_to_version i %> - </span> - <% end %> - </small></div> - <% top = top + 20 -end %> +<% top = headers_height + 8 %> + +<%= @gantt.subjects(:headers_height => headers_height, :top => top, :g_width => g_width) %> + </div> </td> <td> @@ -164,53 +150,9 @@ if show_days end end %> -<% -# -# Tasks -# -top = headers_height + 10 -@gantt.events.each do |i| - if i.is_a? Issue - i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from ) - i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to ) - - i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor - i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date ) - i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date ) - - i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today - - i_left = ((i_start_date - @gantt.date_from)*zoom).floor - i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders) - d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width - l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width - css = "task " + (i.leaf? ? 'leaf' : 'parent') - %> - <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="<%= css %> task_todo"><div class="left"></div> <div class="right"></div></div> - <% if l_width > 0 %> - <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="<%= css %> task_late"> </div> - <% end %> - <% if d_width > 0 %> - <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="<%= css %> task_done"> </div> - <% end %> - <div style="top:<%= top %>px;left:<%= i_left + i_width + 8 %>px;background:#fff;" class="<%= css %>"> - <%= i.status.name %> - <%= (i.done_ratio).to_i %>% - </div> - <div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;"> - <span class="tip"> - <%= render_issue_tooltip i %> - </span></div> -<% else - i_left = ((i.start_date - @gantt.date_from)*zoom).floor - %> - <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone"> </div> - <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task"> - <strong><%= format_version_name i %></strong> - </div> -<% end %> - <% top = top + 20 -end %> +<% top = headers_height + 10 %> + +<%= @gantt.lines(:top => top, :zoom => zoom, :g_width => g_width ) %> <% # |