summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/gantts_controller.rb24
-rw-r--r--app/controllers/issues_controller.rb1
-rw-r--r--app/helpers/application_helper.rb7
-rw-r--r--app/helpers/gantt_helper.rb24
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/models/issue.rb27
-rw-r--r--app/models/project.rb44
-rw-r--r--app/models/version.rb12
-rw-r--r--app/views/gantts/show.html.erb80
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>&nbsp;<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">&nbsp;</div>
- <% end %>
- <% if d_width > 0 %>
- <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="<%= css %> task_done">&nbsp;</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">&nbsp;</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 ) %>
<%
#