Index: app/models/issue.rb
===================================================================
--- app/models/issue.rb	(revision 1680)
+++ app/models/issue.rb	(working copy)
@@ -113,6 +113,14 @@
     if start_date && soonest_start && start_date < soonest_start
       errors.add :start_date, :activerecord_error_invalid
     end
+    
+    if fixed_version_id
+      #status and version have to retrieved from their id, because the objects only get updated upon saving
+      new_status = IssueStatus.find_by_id(status_id)
+      if !new_status.is_closed? && Version.find_by_id(fixed_version_id).completed?
+        errors.add :fixed_version, :activerecord_error_version_completed
+      end    
+    end
   end
   
   def validate_on_create
Index: app/models/version.rb
===================================================================
--- app/models/version.rb	(revision 1680)
+++ app/models/version.rb	(working copy)
@@ -26,6 +26,12 @@
   validates_length_of :name, :maximum => 60
   validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :activerecord_error_not_a_date, :allow_nil => true
   
+  def validate
+    if completed? && open_issues_count > 0
+      errors.add :completed, :activerecord_error_open_issues
+    end
+  end
+  
   def start_date
     effective_date
   end
@@ -44,11 +50,6 @@
     @spent_hours ||= TimeEntry.sum(:hours, :include => :issue, :conditions => ["#{Issue.table_name}.fixed_version_id = ?", id]).to_f
   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)
-  end
-  
   def completed_pourcent
     if fixed_issues.count == 0
       0
@@ -69,7 +70,7 @@
   
   # Returns true if the version is overdue: due date reached and some open issues
   def overdue?
-    effective_date && (effective_date < Date.today) && (open_issues_count > 0)
+    effective_date && (effective_date < Date.today) && (!self.completed?)
   end
   
   def open_issues_count
Index: app/models/project.rb
===================================================================
--- app/models/project.rb	(revision 1680)
+++ app/models/project.rb	(working copy)
@@ -171,6 +171,10 @@
     children.select {|child| child.active?}
   end
   
+  def incomplete_versions
+    versions.reject(&:completed?).sort
+  end
+  
   # Returns an array of the trackers used by the project and its sub projects
   def rolled_up_trackers
     @rolled_up_trackers ||=
Index: app/controllers/issues_controller.rb
===================================================================
--- app/controllers/issues_controller.rb	(revision 1680)
+++ app/controllers/issues_controller.rb	(working copy)
@@ -99,6 +99,7 @@
     @journals.each_with_index {|j,i| j.indice = i+1}
     @journals.reverse! if User.current.wants_comments_in_reverse_order?
     @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
+    @targetable_versions = ([@issue.fixed_version] + @project.incomplete_versions).compact.uniq.sort
     @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
     @priorities = Enumeration::get_values('IPRI')
     @time_entry = TimeEntry.new
@@ -133,6 +134,7 @@
     end    
     @issue.status = default_status
     @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
+    @targetable_versions = ([@issue.fixed_version] + @project.incomplete_versions).compact.uniq.sort
     
     if request.get? || request.xhr?
       @issue.start_date ||= Date.today
@@ -158,6 +160,7 @@
   
   def edit
     @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
+    @targetable_versions = ([@issue.fixed_version] + @project.incomplete_versions).compact.uniq.sort
     @priorities = Enumeration::get_values('IPRI')
     @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
     
@@ -323,15 +326,17 @@
   
   def context_menu
     @issues = Issue.find_all_by_id(params[:ids], :include => :project)
+    projects = @issues.collect(&:project).compact.uniq
+    @project = projects.first if projects.size == 1
+    
     if (@issues.size == 1)
       @issue = @issues.first
       @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
       @assignables = @issue.assignable_users
       @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
+      @targetable_versions = ([@issue.fixed_version] + @project.incomplete_versions).compact.uniq.sort
     end
-    projects = @issues.collect(&:project).compact.uniq
-    @project = projects.first if projects.size == 1
-
+    
     @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
             :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
             :update => (@issue && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && !@allowed_statuses.empty?))),
Index: app/controllers/projects_controller.rb
===================================================================
--- app/controllers/projects_controller.rb	(revision 1680)
+++ app/controllers/projects_controller.rb	(working copy)
@@ -213,8 +213,7 @@
   def roadmap
     @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
     retrieve_selected_tracker_ids(@trackers)
-    @versions = @project.versions.sort
-    @versions = @versions.select {|v| !v.completed? } unless params[:completed]
+    @versions = params[:completed] ? @project.versions.sort : @project.incomplete_versions
   end
   
   def activity
Index: app/views/versions/_form.rhtml
===================================================================
--- app/views/versions/_form.rhtml	(revision 1680)
+++ app/views/versions/_form.rhtml	(working copy)
@@ -5,4 +5,5 @@
 <p><%= f.text_field :description, :size => 60 %></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>
+<p><%= f.check_box :completed, :label => :label_completed_version %>
 </div>
Index: app/views/issues/_form.rhtml
===================================================================
--- app/views/issues/_form.rhtml	(revision 1680)
+++ app/views/issues/_form.rhtml	(working copy)
@@ -30,8 +30,8 @@
                      {:controller => 'projects', :action => 'add_issue_category', :id => @project},
                      :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></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? %>
+                              (@targetable_versions.collect {|v| [v.name, v.id]}),
+                              { :include_blank => true })) unless @targetable_versions.empty? %>
 </div>
 
 <div class="splitcontentright">
Index: app/views/issues/bulk_edit.rhtml
===================================================================
--- app/views/issues/bulk_edit.rhtml	(revision 1680)
+++ app/views/issues/bulk_edit.rhtml	(working copy)
@@ -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, :id, :name)) %></label>
+                                   options_from_collection_for_select(@project.incomplete_versions, :id, :name)) %></label>
 </p>
 
 <p>
Index: app/views/issues/_form_update.rhtml
===================================================================
--- app/views/issues/_form_update.rhtml	(revision 1680)
+++ app/views/issues/_form_update.rhtml	(working copy)
@@ -5,6 +5,6 @@
 <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? %>
+                          (@targetable_versions.collect {|v| [v.name, v.id]}),
+                          { :include_blank => true })) unless @targetable_versions.empty? %>
 </div>
Index: app/views/issues/context_menu.rhtml
===================================================================
--- app/views/issues/context_menu.rhtml	(revision 1680)
+++ app/views/issues/context_menu.rhtml	(working copy)
@@ -20,11 +20,11 @@
 		<% end -%>
 		</ul>
 	</li>
-	<% unless @project.versions.empty? -%>
+	<% unless @targetable_versions.empty? -%>
 	<li class="folder">			
 		<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
 		<ul>
-		<% @project.versions.sort.each do |v| -%>
+		<% @targetable_versions.each do |v| -%>
 		    <li><%= context_menu_link v.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[fixed_version_id]' => v, :back_to => @back}, :method => :post,
 		                              :selected => (v == @issue.fixed_version), :disabled => !@can[:update] %></li>
 		<% end -%>
Index: lang/en.yml
===================================================================
--- lang/en.yml	(revision 1680)
+++ lang/en.yml	(working copy)
@@ -35,6 +35,8 @@
 activerecord_error_greater_than_start_date: must be greater than start date
 activerecord_error_not_same_project: doesn't belong to the same project
 activerecord_error_circular_dependency: This relation would create a circular dependency
+activerecord_error_version_completed: is a completed version
+activerecord_error_open_issues: There are still open issues
 
 general_fmt_age: %d yr
 general_fmt_age_plural: %d yrs
@@ -464,6 +466,7 @@
 label_stay_logged_in: Stay logged in
 label_disabled: disabled
 label_show_completed_versions: Show completed versions
+label_completed_version: Completed
 label_me: me
 label_board: Forum
 label_board_new: New forum
Index: lang/fr.yml
===================================================================
--- lang/fr.yml	(revision 1680)
+++ lang/fr.yml	(working copy)
@@ -35,6 +35,8 @@
 activerecord_error_greater_than_start_date: doit être postérieur à la date de début
 activerecord_error_not_same_project: n'appartient pas au même projet
 activerecord_error_circular_dependency: Cette relation créerait une dépendance circulaire
+activerecord_error_version_completed: est une version complétée
+activerecord_error_open_issues: Il reste encore des demandes ouvertes
 
 general_fmt_age: %d an
 general_fmt_age_plural: %d ans
@@ -464,6 +466,7 @@
 label_stay_logged_in: Rester connecté
 label_disabled: désactivé
 label_show_completed_versions: Voir les versions passées
+label_completed_version: Complété
 label_me: moi
 label_board: Forum
 label_board_new: Nouveau forum
Index: lang/nl.yml
===================================================================
--- lang/nl.yml	(revision 1680)
+++ lang/nl.yml	(working copy)
@@ -35,6 +35,8 @@
 activerecord_error_greater_than_start_date: moet hoger zijn dan startdatum
 activerecord_error_not_same_project: hoort niet bij hetzelfde project
 activerecord_error_circular_dependency: Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben
+activerecord_error_version_completed: is een afgeronde versie
+activerecord_error_open_issues: Er zijn nog open issues
 
 general_fmt_age: %d jr
 general_fmt_age_plural: %d jr
@@ -403,6 +405,7 @@
 label_stay_logged_in: Blijf ingelogd
 label_disabled: uitgeschakeld
 label_show_completed_versions: Toon afgeronde versies
+label_completed_version: Afgerond
 label_me: ik
 label_board: Forum
 label_board_new: Nieuw forum
Index: db/migrate/095_add_versions_completed_status.rb
===================================================================
--- db/migrate/095_add_versions_completed_status.rb	(revision 0)
+++ db/migrate/095_add_versions_completed_status.rb	(revision 0)
@@ -0,0 +1,9 @@
+class AddVersionsCompletedStatus < ActiveRecord::Migration
+  def self.up
+    add_column :versions, :completed, :boolean, :default => false
+  end
+
+  def self.down
+    remove_column :versions, :completed
+  end
+end
