Adds version status to limit issue assignments (#1245).

Available version statuses are:
* open: no restriction
* locked: can not assign new issues to the version
* closed: can not assign new issues and can not reopen assigned issues

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3020 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang
2009-11-08 13:03:41 +00:00
parent 7c14c6d42e
commit d201c54455
49 changed files with 293 additions and 15 deletions
+25
View File
@@ -143,6 +143,14 @@ class Issue < ActiveRecord::Base
if start_date && soonest_start && start_date < soonest_start
errors.add :start_date, :invalid
end
if fixed_version
if !assignable_versions.include?(fixed_version)
errors.add :fixed_version_id, :inclusion
elsif reopened? && fixed_version.closed?
errors.add_to_base I18n.t(:error_can_not_reopen_issue_on_closed_version)
end
end
end
def validate_on_create
@@ -193,6 +201,18 @@ class Issue < ActiveRecord::Base
self.status.is_closed?
end
# Return true if the issue is being reopened
def reopened?
if !new_record? && status_id_changed?
status_was = IssueStatus.find_by_id(status_id_was)
status_new = IssueStatus.find_by_id(status_id)
if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
return true
end
end
false
end
# Returns true if the issue is overdue
def overdue?
!due_date.nil? && (due_date < Date.today) && !status.is_closed?
@@ -203,6 +223,11 @@ class Issue < ActiveRecord::Base
project.assignable_users
end
# Versions that the issue can be assigned to
def assignable_versions
@assignable_versions ||= (project.versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
end
# Returns true if this issue is blocked by another issue that is still open
def blocked?
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
+10 -1
View File
@@ -22,11 +22,16 @@ class Version < ActiveRecord::Base
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
VERSION_STATUSES = %w(open locked closed)
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 60
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
validates_inclusion_of :status, :in => VERSION_STATUSES
named_scope :open, :conditions => {:status => 'open'}
def start_date
effective_date
end
@@ -45,6 +50,10 @@ class Version < ActiveRecord::Base
@spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
end
def closed?
status == 'closed'
end
# Returns true if the version is completed: due date reached and no open issues
def completed?
effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
+3 -3
View File
@@ -32,9 +32,9 @@
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
<% end %>
<%= content_tag('p', f.select(:fixed_version_id,
(@project.versions.sort.collect {|v| [v.name, v.id]}),
{ :include_blank => true })) unless @project.versions.empty? %>
<% unless @issue.assignable_versions.empty? %>
<p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
<% end %>
</div>
<div class="splitcontentright">
+3 -3
View File
@@ -5,8 +5,8 @@
</div>
<div class="splitcontentright">
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
<%= content_tag('p', f.select(:fixed_version_id,
(@project.versions.sort.collect {|v| [v.name, v.id]}),
{ :include_blank => true })) unless @project.versions.empty? %>
<% unless @issue.assignable_versions.empty? %>
<p><%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %></p>
<% end %>
</div>
</div>
+1 -1
View File
@@ -27,7 +27,7 @@
<label><%= l(:field_fixed_version) %>:
<%= select_tag('fixed_version_id', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') +
options_from_collection_for_select(@project.versions.sort, :id, :name)) %></label>
options_from_collection_for_select(@project.versions.open.sort, :id, :name)) %></label>
</p>
<p>
+2 -2
View File
@@ -27,11 +27,11 @@
<% end -%>
</ul>
</li>
<% unless @project.nil? || @project.versions.empty? -%>
<% unless @project.nil? || @project.versions.open.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
<ul>
<% @project.versions.sort.each do |v| -%>
<% @project.versions.open.sort.each do |v| -%>
<li><%= context_menu_link v.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), 'fixed_version_id' => v, :back_to => @back}, :method => :post,
:selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %></li>
<% end -%>
+8 -5
View File
@@ -1,22 +1,25 @@
<% if @project.versions.any? %>
<table class="list">
<table class="list versions">
<thead>
<th><%= l(:label_version) %></th>
<th><%= l(:field_effective_date) %></th>
<th><%= l(:field_description) %></th>
<th><%= l(:field_status) %></th>
<th><%= l(:label_wiki_page) unless @project.wiki.nil? %></th>
<th style="width:15%"></th>
<th style="width:15%"></th>
</thead>
<tbody>
<% for version in @project.versions.sort %>
<tr class="<%= cycle 'odd', 'even' %>">
<tr class="version <%= cycle 'odd', 'even' %> <%=h version.status %>">
<td><%= link_to h(version.name), :controller => 'versions', :action => 'show', :id => version %></td>
<td align="center"><%= format_date(version.effective_date) %></td>
<td><%=h version.description %></td>
<td><%= l("version_status_#{version.status}") %></td>
<td><%= link_to(h(version.wiki_page_title), :controller => 'wiki', :page => Wiki.titleize(version.wiki_page_title)) unless version.wiki_page_title.blank? || @project.wiki.nil? %></td>
<td align="center"><%= link_to_if_authorized l(:button_edit), { :controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %></td>
<td align="center"><%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %></td>
<td class="buttons">
<%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
<%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
</td>
</tr>
<% end; reset_cycle %>
</tbody>
+1
View File
@@ -3,6 +3,7 @@
<div class="box">
<p><%= f.text_field :name, :size => 60, :required => true %></p>
<p><%= f.text_field :description, :size => 60 %></p>
<p><%= f.select :status, Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]} %></p>
<p><%= f.text_field :wiki_page_title, :label => :label_wiki_page, :size => 60, :disabled => @project.wiki.nil? %></p>
<p><%= f.text_field :effective_date, :size => 10 %><%= calendar_for('version_effective_date') %></p>
</div>