Index: app/models/issue.rb =================================================================== --- app/models/issue.rb (revision 499) +++ app/models/issue.rb (working copy) @@ -22,7 +22,6 @@ belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' - belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' @@ -32,6 +31,7 @@ has_many :custom_values, :dependent => :delete_all, :as => :customized has_many :custom_fields, :through => :custom_values has_and_belongs_to_many :changesets, :order => "revision ASC" + has_and_belongs_to_many :fixed_versions, :class_name => 'Version' acts_as_watchable Index: app/models/version.rb =================================================================== --- app/models/version.rb (revision 499) +++ app/models/version.rb (working copy) @@ -18,7 +18,7 @@ class Version < ActiveRecord::Base before_destroy :check_integrity belongs_to :project - has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id' + has_and_belongs_to_many :fixed_issues, :class_name => 'Issue' has_many :attachments, :as => :container, :dependent => :destroy validates_presence_of :name Index: app/controllers/issues_controller.rb =================================================================== --- app/controllers/issues_controller.rb (revision 499) +++ app/controllers/issues_controller.rb (working copy) @@ -46,6 +46,7 @@ @priorities = Enumeration::get_values('IPRI') if request.get? @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) } + setup_versions() else begin @issue.init_journal(self.logged_in_user) @@ -53,6 +54,7 @@ @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } @issue.custom_values = @custom_values @issue.attributes = params[:issue] + ensure_versions_saved() if @issue.save flash[:notice] = l(:notice_successful_update) redirect_to :action => 'show', :id => @issue @@ -81,11 +83,13 @@ def change_status @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user @new_status = IssueStatus.find(params[:new_status_id]) + setup_versions() if params[:confirm] begin journal = @issue.init_journal(self.logged_in_user, params[:notes]) @issue.status = @new_status if @issue.update_attributes(params[:issue]) + ensure_versions_saved() # Save attachments params[:attachments].each { |file| next unless file.size > 0 @@ -156,4 +160,14 @@ rescue ActiveRecord::RecordNotFound render_404 end + + def setup_versions + @released_versions = [] + @unreleased_versions = [] + @project.versions.find(:all, :order => "#{Version.table_name}.effective_date ASC").each { |v| (v.effective_date < Date.today ? @released_versions : @unreleased_versions) << v } + end + + def ensure_versions_saved + @issue.fixed_versions = [] unless params[:issue][:fixed_version_ids] + end end Index: app/controllers/projects_controller.rb =================================================================== --- app/controllers/projects_controller.rb (revision 499) +++ app/controllers/projects_controller.rb (working copy) @@ -434,13 +434,11 @@ def changelog @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position') retrieve_selected_tracker_ids(@trackers) - - @fixed_issues = @project.issues.find(:all, - :include => [ :fixed_version, :status, :tracker ], - :conditions => [ "#{IssueStatus.table_name}.is_closed=? and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}) and #{Issue.table_name}.fixed_version_id is not null", true], - :order => "#{Version.table_name}.effective_date DESC, #{Issue.table_name}.id DESC" + + @versions = @project.versions.find(:all, + :conditions => [ "#{Version.table_name}.effective_date "#{Version.table_name}.effective_date DESC" ) unless @selected_tracker_ids.empty? - @fixed_issues ||= [] end def roadmap Index: app/views/projects/changelog.rhtml =================================================================== --- app/views/projects/changelog.rhtml (revision 499) +++ app/views/projects/changelog.rhtml (working copy) @@ -13,18 +13,30 @@ <% end %> -<% if @fixed_issues.empty? %>

<%= l(:label_no_data) %>

<% end %> +<% if @versions.empty? %>

<%= l(:label_no_data) %>

<% end %> -<% ver_id = nil - @fixed_issues.each do |issue| %> - <% unless ver_id == issue.fixed_version_id %> - <% if ver_id %><% end %> -

<%= issue.fixed_version.name %>

-

<%= format_date(issue.fixed_version.effective_date) %>
- <%=h issue.fixed_version.description %>

+<% @versions.each do |version| %> +

<%= version.name %>

+

<%= format_date(version.effective_date) %>
+

<%=h version.description %>

+ + <% + issues = version.fixed_issues.find(:all, + :include => :status, + :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')}) and is_closed is true"], + :order => "position") + %> + <% end %> - \ No newline at end of file + Index: app/views/issues/show.rhtml =================================================================== --- app/views/issues/show.rhtml (revision 499) +++ app/views/issues/show.rhtml (working copy) @@ -27,7 +27,7 @@ <%=l(:field_done_ratio)%> :<%= @issue.done_ratio %> % - <%=l(:field_fixed_version)%> :<%= @issue.fixed_version ? @issue.fixed_version.name : "-" %> + <%=l(:field_fixed_version_plural)%> :<%= @issue.fixed_versions.map{|v| v.name}.join(", ") %> <%=l(:label_spent_time)%> : <%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %> Index: app/views/issues/edit.rhtml =================================================================== --- app/views/issues/edit.rhtml (revision 499) +++ app/views/issues/edit.rhtml (working copy) @@ -25,7 +25,8 @@

<%= custom_field_tag_with_label @custom_value %>

<% end %> -

<%= f.select :fixed_version_id, (@project.versions.collect {|v| [v.name, v.id]}), { :include_blank => true } %>

+

<%= render(:partial => 'fixed_versions') %>

+ @@ -52,4 +53,4 @@ <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> <%= javascript_include_tag 'calendar/calendar-setup' %> <%= stylesheet_link_tag 'calendar' %> -<% end %> \ No newline at end of file +<% end %> Index: app/views/issues/_fixed_versions.rhtml =================================================================== --- app/views/issues/_fixed_versions.rhtml (revision 0) +++ app/views/issues/_fixed_versions.rhtml (revision 0) @@ -0,0 +1,9 @@ +<% selected_fixed_versions = @issue.fixed_versions.collect { |v| v.id.to_i } %> + Index: app/views/issues/change_status.rhtml =================================================================== --- app/views/issues/change_status.rhtml (revision 499) +++ app/views/issues/change_status.rhtml (working copy) @@ -11,7 +11,7 @@

<%= @new_status.name %>

<%= f.select :assigned_to_id, (@issue.project.members.collect {|m| [m.name, m.user_id]}), :include_blank => true %>

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

-

<%= f.select :fixed_version_id, (@project.versions.collect {|v| [v.name, v.id]}), { :include_blank => true } %>

+

<%= render(:partial => 'fixed_versions') %>

<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>

Index: lang/en.yml =================================================================== --- lang/en.yml (revision 499) +++ lang/en.yml (working copy) @@ -109,6 +109,9 @@ field_assigned_to: Assigned to field_priority: Priority field_fixed_version: Fixed version +field_fixed_version_plural: Fixed versions +field_fixed_versions_released: Released versions +field_fixed_versions_unreleased: Unreleased versions field_user: User field_role: Role field_homepage: Homepage @@ -258,6 +261,7 @@ label_news_latest: Latest news label_news_view_all: View all news label_change_log: Change log +label_change_log_no_issues: No issues for this version label_settings: Settings label_overview: Overview label_version: Version Index: db/migrate/042_issue_version_relationship.rb =================================================================== --- db/migrate/042_issue_version_relationship.rb (revision 0) +++ db/migrate/042_issue_version_relationship.rb (revision 0) @@ -0,0 +1,42 @@ +class IssueVersionRelationship < ActiveRecord::Migration + class Issue < ActiveRecord::Base + belongs_to :old_fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' + has_and_belongs_to_many :versions + end + + class Version < ActiveRecord::Base + has_and_belongs_to_many :issues + end + + def self.up + create_table :issues_versions, :id => false do |t| + t.column :issue_id, :integer + t.column :version_id, :integer + end + + Issue.find(:all).each do |issue| + if issue.old_fixed_version + issue.versions << issue.old_fixed_version + end + end + + remove_column :issues, :fixed_version_id + end + + def self.down + add_column :issues, :fixed_version_id, :integer + + Issue.find(:all).each do |issue| + + if issue.versions.size > 1 + # We cannot rollback if any issues point to multiple versions + remove_column :issues, :fixed_version_id + raise IrreversibleMigration + end + + issue.old_fixed_version = issue.versions[0] if issue.versions.size == 1 + end + + drop_table :issues_versions + end +end Index: public/stylesheets/application.css =================================================================== --- public/stylesheets/application.css (revision 499) +++ public/stylesheets/application.css (working copy) @@ -665,3 +665,12 @@ padding-left: 26px; vertical-align: bottom; } + +optgroup { +background-color: #EEE; +font-style: normal; +} + +option { +background-color: white; +}