##// END OF EJS Templates
Adds custom fields for versions (#4219)....
Jean-Philippe Lang -
r2950:ba7cf9c3ce6c
parent child
Show More
@@ -0,0 +1,22
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class VersionCustomField < CustomField
19 def type_name
20 :label_version_plural
21 end
22 end
@@ -1,69 +1,71
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 menu_item :roadmap
19 menu_item :roadmap
20 before_filter :find_version, :except => :close_completed
20 before_filter :find_version, :except => :close_completed
21 before_filter :find_project, :only => :close_completed
21 before_filter :find_project, :only => :close_completed
22 before_filter :authorize
22 before_filter :authorize
23
23
24 helper :custom_fields
25
24 def show
26 def show
25 end
27 end
26
28
27 def edit
29 def edit
28 if request.post? and @version.update_attributes(params[:version])
30 if request.post? and @version.update_attributes(params[:version])
29 flash[:notice] = l(:notice_successful_update)
31 flash[:notice] = l(:notice_successful_update)
30 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
32 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
31 end
33 end
32 end
34 end
33
35
34 def close_completed
36 def close_completed
35 if request.post?
37 if request.post?
36 @project.close_completed_versions
38 @project.close_completed_versions
37 end
39 end
38 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
40 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
39 end
41 end
40
42
41 def destroy
43 def destroy
42 @version.destroy
44 @version.destroy
43 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
45 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
44 rescue
46 rescue
45 flash[:error] = l(:notice_unable_delete_version)
47 flash[:error] = l(:notice_unable_delete_version)
46 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
48 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
47 end
49 end
48
50
49 def status_by
51 def status_by
50 respond_to do |format|
52 respond_to do |format|
51 format.html { render :action => 'show' }
53 format.html { render :action => 'show' }
52 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
54 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
53 end
55 end
54 end
56 end
55
57
56 private
58 private
57 def find_version
59 def find_version
58 @version = Version.find(params[:id])
60 @version = Version.find(params[:id])
59 @project = @version.project
61 @project = @version.project
60 rescue ActiveRecord::RecordNotFound
62 rescue ActiveRecord::RecordNotFound
61 render_404
63 render_404
62 end
64 end
63
65
64 def find_project
66 def find_project
65 @project = Project.find(params[:project_id])
67 @project = Project.find(params[:project_id])
66 rescue ActiveRecord::RecordNotFound
68 rescue ActiveRecord::RecordNotFound
67 render_404
69 render_404
68 end
70 end
69 end
71 end
@@ -1,92 +1,93
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module CustomFieldsHelper
18 module CustomFieldsHelper
19
19
20 def custom_fields_tabs
20 def custom_fields_tabs
21 tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
21 tabs = [{:name => 'IssueCustomField', :partial => 'custom_fields/index', :label => :label_issue_plural},
22 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
22 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', :label => :label_spent_time},
23 {:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
23 {:name => 'ProjectCustomField', :partial => 'custom_fields/index', :label => :label_project_plural},
24 {:name => 'VersionCustomField', :partial => 'custom_fields/index', :label => :label_version_plural},
24 {:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
25 {:name => 'UserCustomField', :partial => 'custom_fields/index', :label => :label_user_plural},
25 {:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
26 {:name => 'GroupCustomField', :partial => 'custom_fields/index', :label => :label_group_plural},
26 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
27 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', :label => TimeEntryActivity::OptionName},
27 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
28 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', :label => IssuePriority::OptionName},
28 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
29 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', :label => DocumentCategory::OptionName}
29 ]
30 ]
30 end
31 end
31
32
32 # Return custom field html tag corresponding to its format
33 # Return custom field html tag corresponding to its format
33 def custom_field_tag(name, custom_value)
34 def custom_field_tag(name, custom_value)
34 custom_field = custom_value.custom_field
35 custom_field = custom_value.custom_field
35 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
36 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
36 field_id = "#{name}_custom_field_values_#{custom_field.id}"
37 field_id = "#{name}_custom_field_values_#{custom_field.id}"
37
38
38 case custom_field.field_format
39 case custom_field.field_format
39 when "date"
40 when "date"
40 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
41 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
41 calendar_for(field_id)
42 calendar_for(field_id)
42 when "text"
43 when "text"
43 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
44 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
44 when "bool"
45 when "bool"
45 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
46 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, :id => field_id)
46 when "list"
47 when "list"
47 blank_option = custom_field.is_required? ?
48 blank_option = custom_field.is_required? ?
48 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
49 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
49 '<option></option>'
50 '<option></option>'
50 select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
51 select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
51 else
52 else
52 text_field_tag(field_name, custom_value.value, :id => field_id)
53 text_field_tag(field_name, custom_value.value, :id => field_id)
53 end
54 end
54 end
55 end
55
56
56 # Return custom field label tag
57 # Return custom field label tag
57 def custom_field_label_tag(name, custom_value)
58 def custom_field_label_tag(name, custom_value)
58 content_tag "label", custom_value.custom_field.name +
59 content_tag "label", custom_value.custom_field.name +
59 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
60 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
60 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
61 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
61 :class => (custom_value.errors.empty? ? nil : "error" )
62 :class => (custom_value.errors.empty? ? nil : "error" )
62 end
63 end
63
64
64 # Return custom field tag with its label tag
65 # Return custom field tag with its label tag
65 def custom_field_tag_with_label(name, custom_value)
66 def custom_field_tag_with_label(name, custom_value)
66 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
67 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
67 end
68 end
68
69
69 # Return a string used to display a custom value
70 # Return a string used to display a custom value
70 def show_value(custom_value)
71 def show_value(custom_value)
71 return "" unless custom_value
72 return "" unless custom_value
72 format_value(custom_value.value, custom_value.custom_field.field_format)
73 format_value(custom_value.value, custom_value.custom_field.field_format)
73 end
74 end
74
75
75 # Return a string used to display a custom value
76 # Return a string used to display a custom value
76 def format_value(value, field_format)
77 def format_value(value, field_format)
77 return "" unless value && !value.empty?
78 return "" unless value && !value.empty?
78 case field_format
79 case field_format
79 when "date"
80 when "date"
80 begin; format_date(value.to_date); rescue; value end
81 begin; format_date(value.to_date); rescue; value end
81 when "bool"
82 when "bool"
82 l(value == "1" ? :general_text_Yes : :general_text_No)
83 l(value == "1" ? :general_text_Yes : :general_text_No)
83 else
84 else
84 value
85 value
85 end
86 end
86 end
87 end
87
88
88 # Return an array of custom field formats which can be used in select_tag
89 # Return an array of custom field formats which can be used in select_tag
89 def custom_field_formats_for_select
90 def custom_field_formats_for_select
90 CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
91 CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
91 end
92 end
92 end
93 end
@@ -1,162 +1,163
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 before_destroy :check_integrity
19 before_destroy :check_integrity
20 belongs_to :project
20 belongs_to :project
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
21 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id'
22 acts_as_customizable
22 acts_as_attachable :view_permission => :view_files,
23 acts_as_attachable :view_permission => :view_files,
23 :delete_permission => :manage_files
24 :delete_permission => :manage_files
24
25
25 VERSION_STATUSES = %w(open locked closed)
26 VERSION_STATUSES = %w(open locked closed)
26
27
27 validates_presence_of :name
28 validates_presence_of :name
28 validates_uniqueness_of :name, :scope => [:project_id]
29 validates_uniqueness_of :name, :scope => [:project_id]
29 validates_length_of :name, :maximum => 60
30 validates_length_of :name, :maximum => 60
30 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
31 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
31 validates_inclusion_of :status, :in => VERSION_STATUSES
32 validates_inclusion_of :status, :in => VERSION_STATUSES
32
33
33 named_scope :open, :conditions => {:status => 'open'}
34 named_scope :open, :conditions => {:status => 'open'}
34
35
35 def start_date
36 def start_date
36 effective_date
37 effective_date
37 end
38 end
38
39
39 def due_date
40 def due_date
40 effective_date
41 effective_date
41 end
42 end
42
43
43 # Returns the total estimated time for this version
44 # Returns the total estimated time for this version
44 def estimated_hours
45 def estimated_hours
45 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
46 @estimated_hours ||= fixed_issues.sum(:estimated_hours).to_f
46 end
47 end
47
48
48 # Returns the total reported time for this version
49 # Returns the total reported time for this version
49 def spent_hours
50 def spent_hours
50 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
51 @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
51 end
52 end
52
53
53 def closed?
54 def closed?
54 status == 'closed'
55 status == 'closed'
55 end
56 end
56
57
57 # Returns true if the version is completed: due date reached and no open issues
58 # Returns true if the version is completed: due date reached and no open issues
58 def completed?
59 def completed?
59 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
60 effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
60 end
61 end
61
62
62 # Returns the completion percentage of this version based on the amount of open/closed issues
63 # Returns the completion percentage of this version based on the amount of open/closed issues
63 # and the time spent on the open issues.
64 # and the time spent on the open issues.
64 def completed_pourcent
65 def completed_pourcent
65 if issues_count == 0
66 if issues_count == 0
66 0
67 0
67 elsif open_issues_count == 0
68 elsif open_issues_count == 0
68 100
69 100
69 else
70 else
70 issues_progress(false) + issues_progress(true)
71 issues_progress(false) + issues_progress(true)
71 end
72 end
72 end
73 end
73
74
74 # Returns the percentage of issues that have been marked as 'closed'.
75 # Returns the percentage of issues that have been marked as 'closed'.
75 def closed_pourcent
76 def closed_pourcent
76 if issues_count == 0
77 if issues_count == 0
77 0
78 0
78 else
79 else
79 issues_progress(false)
80 issues_progress(false)
80 end
81 end
81 end
82 end
82
83
83 # Returns true if the version is overdue: due date reached and some open issues
84 # Returns true if the version is overdue: due date reached and some open issues
84 def overdue?
85 def overdue?
85 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
86 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
86 end
87 end
87
88
88 # Returns assigned issues count
89 # Returns assigned issues count
89 def issues_count
90 def issues_count
90 @issue_count ||= fixed_issues.count
91 @issue_count ||= fixed_issues.count
91 end
92 end
92
93
93 # Returns the total amount of open issues for this version.
94 # Returns the total amount of open issues for this version.
94 def open_issues_count
95 def open_issues_count
95 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
96 @open_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, false], :include => :status)
96 end
97 end
97
98
98 # Returns the total amount of closed issues for this version.
99 # Returns the total amount of closed issues for this version.
99 def closed_issues_count
100 def closed_issues_count
100 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
101 @closed_issues_count ||= Issue.count(:all, :conditions => ["fixed_version_id = ? AND is_closed = ?", self.id, true], :include => :status)
101 end
102 end
102
103
103 def wiki_page
104 def wiki_page
104 if project.wiki && !wiki_page_title.blank?
105 if project.wiki && !wiki_page_title.blank?
105 @wiki_page ||= project.wiki.find_page(wiki_page_title)
106 @wiki_page ||= project.wiki.find_page(wiki_page_title)
106 end
107 end
107 @wiki_page
108 @wiki_page
108 end
109 end
109
110
110 def to_s; name end
111 def to_s; name end
111
112
112 # Versions are sorted by effective_date and name
113 # Versions are sorted by effective_date and name
113 # Those with no effective_date are at the end, sorted by name
114 # Those with no effective_date are at the end, sorted by name
114 def <=>(version)
115 def <=>(version)
115 if self.effective_date
116 if self.effective_date
116 version.effective_date ? (self.effective_date == version.effective_date ? self.name <=> version.name : self.effective_date <=> version.effective_date) : -1
117 version.effective_date ? (self.effective_date == version.effective_date ? self.name <=> version.name : self.effective_date <=> version.effective_date) : -1
117 else
118 else
118 version.effective_date ? 1 : (self.name <=> version.name)
119 version.effective_date ? 1 : (self.name <=> version.name)
119 end
120 end
120 end
121 end
121
122
122 private
123 private
123 def check_integrity
124 def check_integrity
124 raise "Can't delete version" if self.fixed_issues.find(:first)
125 raise "Can't delete version" if self.fixed_issues.find(:first)
125 end
126 end
126
127
127 # Returns the average estimated time of assigned issues
128 # Returns the average estimated time of assigned issues
128 # or 1 if no issue has an estimated time
129 # or 1 if no issue has an estimated time
129 # Used to weigth unestimated issues in progress calculation
130 # Used to weigth unestimated issues in progress calculation
130 def estimated_average
131 def estimated_average
131 if @estimated_average.nil?
132 if @estimated_average.nil?
132 average = fixed_issues.average(:estimated_hours).to_f
133 average = fixed_issues.average(:estimated_hours).to_f
133 if average == 0
134 if average == 0
134 average = 1
135 average = 1
135 end
136 end
136 @estimated_average = average
137 @estimated_average = average
137 end
138 end
138 @estimated_average
139 @estimated_average
139 end
140 end
140
141
141 # Returns the total progress of open or closed issues. The returned percentage takes into account
142 # Returns the total progress of open or closed issues. The returned percentage takes into account
142 # the amount of estimated time set for this version.
143 # the amount of estimated time set for this version.
143 #
144 #
144 # Examples:
145 # Examples:
145 # issues_progress(true) => returns the progress percentage for open issues.
146 # issues_progress(true) => returns the progress percentage for open issues.
146 # issues_progress(false) => returns the progress percentage for closed issues.
147 # issues_progress(false) => returns the progress percentage for closed issues.
147 def issues_progress(open)
148 def issues_progress(open)
148 @issues_progress ||= {}
149 @issues_progress ||= {}
149 @issues_progress[open] ||= begin
150 @issues_progress[open] ||= begin
150 progress = 0
151 progress = 0
151 if issues_count > 0
152 if issues_count > 0
152 ratio = open ? 'done_ratio' : 100
153 ratio = open ? 'done_ratio' : 100
153
154
154 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
155 done = fixed_issues.sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}",
155 :include => :status,
156 :include => :status,
156 :conditions => ["is_closed = ?", !open]).to_f
157 :conditions => ["is_closed = ?", !open]).to_f
157 progress = done / (estimated_average * issues_count)
158 progress = done / (estimated_average * issues_count)
158 end
159 end
159 progress
160 progress
160 end
161 end
161 end
162 end
162 end
163 end
@@ -1,9 +1,13
1 <%= error_messages_for 'version' %>
1 <%= error_messages_for 'version' %>
2
2
3 <div class="box">
3 <div class="box">
4 <p><%= f.text_field :name, :size => 60, :required => true %></p>
4 <p><%= f.text_field :name, :size => 60, :required => true %></p>
5 <p><%= f.text_field :description, :size => 60 %></p>
5 <p><%= f.text_field :description, :size => 60 %></p>
6 <p><%= f.select :status, Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} %></p>
6 <p><%= f.select :status, Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} %></p>
7 <p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p>
7 <p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p>
8 <p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p>
8 <p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p>
9
10 <% @version.custom_field_values.each do |value| %>
11 <p><%= custom_field_tag_with_label :version, value %></p>
12 <% end %>
9 </div>
13 </div>
@@ -1,20 +1,27
1 <% if version.completed? %>
1 <% if version.completed? %>
2 <p><%= format_date(version.effective_date) %></p>
2 <p><%= format_date(version.effective_date) %></p>
3 <% elsif version.effective_date %>
3 <% elsif version.effective_date %>
4 <p><strong><%= due_date_distance_in_words(version.effective_date) %></strong> (<%= format_date(version.effective_date) %>)</p>
4 <p><strong><%= due_date_distance_in_words(version.effective_date) %></strong> (<%= format_date(version.effective_date) %>)</p>
5 <% end %>
5 <% end %>
6
6
7 <p><%=h version.description %></p>
7 <p><%=h version.description %></p>
8 <ul>
9 <% version.custom_values.each do |custom_value| %>
10 <% if !custom_value.value.blank? %>
11 <li><%=h custom_value.custom_field.name %>: <%=h show_value(custom_value) %></li>
12 <% end %>
13 <% end %>
14 </ul>
8
15
9 <% if version.fixed_issues.count > 0 %>
16 <% if version.fixed_issues.count > 0 %>
10 <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %>
17 <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %>
11 <p class="progress-info">
18 <p class="progress-info">
12 <%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
19 <%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %>
13 (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%)
20 (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%)
14 &#160;
21 &#160;
15 <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
22 <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %>
16 (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%)
23 (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%)
17 </p>
24 </p>
18 <% else %>
25 <% else %>
19 <p><em><%= l(:label_roadmap_no_issues) %></em></p>
26 <p><em><%= l(:label_roadmap_no_issues) %></em></p>
20 <% end %>
27 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now