@@ -0,0 +1,19 | |||
|
1 | class CreateProjectsTrackers < ActiveRecord::Migration | |
|
2 | def self.up | |
|
3 | create_table :projects_trackers, :id => false do |t| | |
|
4 | t.column :project_id, :integer, :default => 0, :null => false | |
|
5 | t.column :tracker_id, :integer, :default => 0, :null => false | |
|
6 | end | |
|
7 | add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id | |
|
8 | ||
|
9 | # Associates all trackers to all projects (as it was before) | |
|
10 | tracker_ids = Tracker.find(:all).collect(&:id) | |
|
11 | Project.find(:all).each do |project| | |
|
12 | project.tracker_ids = tracker_ids | |
|
13 | end | |
|
14 | end | |
|
15 | ||
|
16 | def self.down | |
|
17 | drop_table :projects_trackers | |
|
18 | end | |
|
19 | end |
@@ -0,0 +1,46 | |||
|
1 | --- | |
|
2 | projects_trackers_012: | |
|
3 | project_id: 4 | |
|
4 | tracker_id: 3 | |
|
5 | projects_trackers_001: | |
|
6 | project_id: 1 | |
|
7 | tracker_id: 1 | |
|
8 | projects_trackers_013: | |
|
9 | project_id: 5 | |
|
10 | tracker_id: 1 | |
|
11 | projects_trackers_002: | |
|
12 | project_id: 1 | |
|
13 | tracker_id: 2 | |
|
14 | projects_trackers_014: | |
|
15 | project_id: 5 | |
|
16 | tracker_id: 2 | |
|
17 | projects_trackers_003: | |
|
18 | project_id: 1 | |
|
19 | tracker_id: 3 | |
|
20 | projects_trackers_015: | |
|
21 | project_id: 5 | |
|
22 | tracker_id: 3 | |
|
23 | projects_trackers_004: | |
|
24 | project_id: 2 | |
|
25 | tracker_id: 1 | |
|
26 | projects_trackers_005: | |
|
27 | project_id: 2 | |
|
28 | tracker_id: 2 | |
|
29 | projects_trackers_006: | |
|
30 | project_id: 2 | |
|
31 | tracker_id: 3 | |
|
32 | projects_trackers_007: | |
|
33 | project_id: 3 | |
|
34 | tracker_id: 1 | |
|
35 | projects_trackers_008: | |
|
36 | project_id: 3 | |
|
37 | tracker_id: 2 | |
|
38 | projects_trackers_009: | |
|
39 | project_id: 3 | |
|
40 | tracker_id: 3 | |
|
41 | projects_trackers_010: | |
|
42 | project_id: 4 | |
|
43 | tracker_id: 1 | |
|
44 | projects_trackers_011: | |
|
45 | project_id: 4 | |
|
46 | tracker_id: 2 |
@@ -194,6 +194,7 class IssuesController < ApplicationController | |||
|
194 | 194 | :change_status => User.current.allowed_to?(:change_issue_status, @project), |
|
195 | 195 | :add => User.current.allowed_to?(:add_issues, @project), |
|
196 | 196 | :move => User.current.allowed_to?(:move_issues, @project), |
|
197 | :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), | |
|
197 | 198 | :delete => User.current.allowed_to?(:delete_issues, @project)} |
|
198 | 199 | render :layout => false |
|
199 | 200 | end |
@@ -57,11 +57,13 class ProjectsController < ApplicationController | |||
|
57 | 57 | # Add a new project |
|
58 | 58 | def add |
|
59 | 59 | @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") |
|
60 | @trackers = Tracker.all | |
|
60 | 61 | @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}") |
|
61 | 62 | @project = Project.new(params[:project]) |
|
62 | 63 | @project.enabled_module_names = Redmine::AccessControl.available_project_modules |
|
63 | 64 | if request.get? |
|
64 | 65 | @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) } |
|
66 | @project.trackers = Tracker.all | |
|
65 | 67 | else |
|
66 | 68 | @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids] |
|
67 | 69 | @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) } |
@@ -80,7 +82,7 class ProjectsController < ApplicationController | |||
|
80 | 82 | @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role} |
|
81 | 83 | @subprojects = @project.active_children |
|
82 | 84 | @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") |
|
83 | @trackers = Tracker.find(:all, :order => 'position') | |
|
85 | @trackers = @project.trackers | |
|
84 | 86 | @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false]) |
|
85 | 87 | @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id]) |
|
86 | 88 | @total_hours = @project.time_entries.sum(:hours) |
@@ -92,6 +94,7 class ProjectsController < ApplicationController | |||
|
92 | 94 | @custom_fields = IssueCustomField.find(:all) |
|
93 | 95 | @issue_category ||= IssueCategory.new |
|
94 | 96 | @member ||= @project.members.new |
|
97 | @trackers = Tracker.all | |
|
95 | 98 | @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) } |
|
96 | 99 | @repository ||= @project.repository |
|
97 | 100 | @wiki ||= @project.wiki |
@@ -207,7 +210,7 class ProjectsController < ApplicationController | |||
|
207 | 210 | @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue]) |
|
208 | 211 | @issue.project = @project |
|
209 | 212 | @issue.author = User.current |
|
210 |
@issue.tracker ||= |
|
|
213 | @issue.tracker ||= @project.trackers.find(params[:tracker_id]) | |
|
211 | 214 | |
|
212 | 215 | default_status = IssueStatus.default |
|
213 | 216 | unless default_status |
@@ -293,6 +296,7 class ProjectsController < ApplicationController | |||
|
293 | 296 | def move_issues |
|
294 | 297 | @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids] |
|
295 | 298 | redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues |
|
299 | ||
|
296 | 300 | @projects = [] |
|
297 | 301 | # find projects to which the user is allowed to move the issue |
|
298 | 302 | if User.current.admin? |
@@ -301,14 +305,14 class ProjectsController < ApplicationController | |||
|
301 | 305 | else |
|
302 | 306 | User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)} |
|
303 | 307 | end |
|
304 | # issue can be moved to any tracker | |
|
305 | @trackers = Tracker.find(:all) | |
|
306 | if request.post? && params[:new_project_id] && @projects.collect(&:id).include?(params[:new_project_id].to_i) && params[:new_tracker_id] | |
|
307 | new_project = Project.find_by_id(params[:new_project_id]) | |
|
308 |
new_tracker = params[:new_tracker_id].blank? ? nil : |
|
|
308 | @target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] | |
|
309 | @target_project ||= @project | |
|
310 | @trackers = @target_project.trackers | |
|
311 | if request.post? | |
|
312 | new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) | |
|
309 | 313 | unsaved_issue_ids = [] |
|
310 | 314 | @issues.each do |issue| |
|
311 |
unsaved_issue_ids << issue.id unless issue.move_to( |
|
|
315 | unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker) | |
|
312 | 316 | end |
|
313 | 317 | if unsaved_issue_ids.empty? |
|
314 | 318 | flash[:notice] = l(:notice_successful_update) unless @issues.empty? |
@@ -316,7 +320,9 class ProjectsController < ApplicationController | |||
|
316 | 320 | flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #')) |
|
317 | 321 | end |
|
318 | 322 | redirect_to :controller => 'issues', :action => 'index', :project_id => @project |
|
323 | return | |
|
319 | 324 | end |
|
325 | render :layout => false if request.xhr? | |
|
320 | 326 | end |
|
321 | 327 | |
|
322 | 328 | # Add a news to @project |
@@ -354,13 +360,13 class ProjectsController < ApplicationController | |||
|
354 | 360 | |
|
355 | 361 | # Show changelog for @project |
|
356 | 362 | def changelog |
|
357 |
@trackers = |
|
|
363 | @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position') | |
|
358 | 364 | retrieve_selected_tracker_ids(@trackers) |
|
359 | 365 | @versions = @project.versions.sort |
|
360 | 366 | end |
|
361 | 367 | |
|
362 | 368 | def roadmap |
|
363 |
@trackers = |
|
|
369 | @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true]) | |
|
364 | 370 | retrieve_selected_tracker_ids(@trackers) |
|
365 | 371 | @versions = @project.versions.sort |
|
366 | 372 | @versions = @versions.select {|v| !v.completed? } unless params[:completed] |
@@ -25,7 +25,7 class ReportsController < ApplicationController | |||
|
25 | 25 | case params[:detail] |
|
26 | 26 | when "tracker" |
|
27 | 27 | @field = "tracker_id" |
|
28 | @rows = Tracker.find :all, :order => 'position' | |
|
28 | @rows = @project.trackers | |
|
29 | 29 | @data = issues_by_tracker |
|
30 | 30 | @report_title = l(:field_tracker) |
|
31 | 31 | render :template => "reports/issue_report_details" |
@@ -60,7 +60,7 class ReportsController < ApplicationController | |||
|
60 | 60 | @report_title = l(:field_subproject) |
|
61 | 61 | render :template => "reports/issue_report_details" |
|
62 | 62 | else |
|
63 | @trackers = Tracker.find(:all, :order => 'position') | |
|
63 | @trackers = @project.trackers | |
|
64 | 64 | @versions = @project.versions.sort |
|
65 | 65 | @priorities = Enumeration::get_values('IPRI') |
|
66 | 66 | @categories = @project.issue_categories |
@@ -190,7 +190,7 module ProjectsHelper | |||
|
190 | 190 | end if Object.const_defined?(:Magick) |
|
191 | 191 | |
|
192 | 192 | def new_issue_selector |
|
193 | trackers = Tracker.find(:all, :order => 'position') | |
|
193 | trackers = @project.trackers | |
|
194 | 194 | # can't use form tag inside helper |
|
195 | 195 | content_tag('form', |
|
196 | 196 | select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"), |
@@ -31,9 +31,9 protected | |||
|
31 | 31 | when 'float' |
|
32 | 32 | begin; !value.blank? && Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end |
|
33 | 33 | when 'date' |
|
34 |
errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value. |
|
|
34 | errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.blank? | |
|
35 | 35 | when 'list' |
|
36 |
errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include? |
|
|
36 | errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include?(value) or value.blank? | |
|
37 | 37 | end |
|
38 | 38 | end |
|
39 | 39 | end |
@@ -40,7 +40,7 class Issue < ActiveRecord::Base | |||
|
40 | 40 | acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"}, |
|
41 | 41 | :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}} |
|
42 | 42 | |
|
43 | validates_presence_of :subject, :description, :priority, :tracker, :author, :status | |
|
43 | validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status | |
|
44 | 44 | validates_length_of :subject, :maximum => 255 |
|
45 | 45 | validates_inclusion_of :done_ratio, :in => 0..100 |
|
46 | 46 | validates_numericality_of :estimated_hours, :allow_nil => true |
@@ -106,6 +106,10 class Issue < ActiveRecord::Base | |||
|
106 | 106 | end |
|
107 | 107 | end |
|
108 | 108 | |
|
109 | def validate_on_create | |
|
110 | errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker) | |
|
111 | end | |
|
112 | ||
|
109 | 113 | def before_create |
|
110 | 114 | # default assignment based on category |
|
111 | 115 | if assigned_to.nil? && category && category.assigned_to |
@@ -24,6 +24,7 class Project < ActiveRecord::Base | |||
|
24 | 24 | has_many :users, :through => :members |
|
25 | 25 | has_many :custom_values, :dependent => :delete_all, :as => :customized |
|
26 | 26 | has_many :enabled_modules, :dependent => :delete_all |
|
27 | has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" | |
|
27 | 28 | has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker] |
|
28 | 29 | has_many :issue_changes, :through => :issues, :source => :journals |
|
29 | 30 | has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" |
@@ -29,6 +29,10 class Tracker < ActiveRecord::Base | |||
|
29 | 29 | |
|
30 | 30 | def to_s; name end |
|
31 | 31 | |
|
32 | def self.all | |
|
33 | find(:all, :order => 'position') | |
|
34 | end | |
|
35 | ||
|
32 | 36 | private |
|
33 | 37 | def check_integrity |
|
34 | 38 | raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id]) |
@@ -1,4 +1,4 | |||
|
1 | <% if authorize_for('projects', 'add_issue') %> | |
|
1 | <% if authorize_for('projects', 'add_issue') && @project.trackers.any? %> | |
|
2 | 2 | <h3><%= l(:label_issue_new) %></h3> |
|
3 | 3 | <%= l(:label_tracker) %>: <%= new_issue_selector %> |
|
4 | 4 | <% end %> |
@@ -32,7 +32,7 | |||
|
32 | 32 | </ul> |
|
33 | 33 | </li> |
|
34 | 34 | <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue}, |
|
35 |
:class => 'icon-copy', :disabled => !@can[: |
|
|
35 | :class => 'icon-copy', :disabled => !@can[:copy] %></li> | |
|
36 | 36 | <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, |
|
37 | 37 | :class => 'icon-move', :disabled => !@can[:move] %> |
|
38 | 38 | <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, |
@@ -17,16 +17,32 | |||
|
17 | 17 | <% for @custom_value in @custom_values %> |
|
18 | 18 | <p><%= custom_field_tag_with_label @custom_value %></p> |
|
19 | 19 | <% end %> |
|
20 | </div> | |
|
21 | ||
|
22 | <% unless @trackers.empty? %> | |
|
23 | <fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend> | |
|
24 | <% @trackers.each do |tracker| %> | |
|
25 | <label class="floating"> | |
|
26 | <%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.include?(tracker) %> | |
|
27 | <%= tracker %> | |
|
28 | </label> | |
|
29 | <% end %> | |
|
30 | <%= hidden_field_tag 'project[tracker_ids][]', '' %> | |
|
31 | </fieldset> | |
|
32 | <% end %> | |
|
20 | 33 | |
|
21 | 34 | <% unless @custom_fields.empty? %> |
|
22 |
< |
|
|
35 | <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend> | |
|
23 | 36 | <% for custom_field in @custom_fields %> |
|
37 | <label class="floating"> | |
|
24 | 38 | <%= check_box_tag "custom_field_ids[]", custom_field.id, ((@project.custom_fields.include? custom_field) or custom_field.is_for_all?), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %> |
|
25 |
<%= custom_field.name %> |
|
|
26 | <% end %></p> | |
|
39 | <%= custom_field.name %> | |
|
40 | </label> | |
|
41 | <% end %> | |
|
42 | </fieldset> | |
|
27 | 43 | <% end %> |
|
28 | 44 | <!--[eoform:project]--> |
|
29 | </div> | |
|
45 | ||
|
30 | 46 | |
|
31 | 47 | <% content_for :header_tags do %> |
|
32 | 48 | <%= javascript_include_tag 'calendar/calendar' %> |
@@ -3,13 +3,13 | |||
|
3 | 3 | <% labelled_tabular_form_for :project, @project, :url => { :action => "add" } do |f| %> |
|
4 | 4 | <%= render :partial => 'form', :locals => { :f => f } %> |
|
5 | 5 | |
|
6 | <div class="box"> | |
|
7 | <p><label><%= l(:label_module_plural) %></label> | |
|
6 | <fieldset class="box"><legend><%= l(:label_module_plural) %></legend> | |
|
8 | 7 | <% Redmine::AccessControl.available_project_modules.each do |m| %> |
|
9 | <%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %> <%= m.to_s.humanize %> | |
|
10 | <% end %></p> | |
|
11 | </div> | |
|
12 | ||
|
8 | <label class="floating"> | |
|
9 | <%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %> <%= m.to_s.humanize %> | |
|
10 | </label> | |
|
11 | <% end %> | |
|
12 | </fieldset> | |
|
13 | 13 | |
|
14 | 14 | <%= submit_tag l(:button_save) %> |
|
15 | 15 | <% end %> |
@@ -1,7 +1,7 | |||
|
1 | 1 | <h2><%=l(:button_move)%></h2> |
|
2 | 2 | |
|
3 | 3 | |
|
4 |
<% form_tag({:action => 'move_issues', :id => @project}, :class => |
|
|
4 | <% form_tag({:action => 'move_issues', :id => @project}, :class => 'tabular', :id => 'move_form') do %> | |
|
5 | 5 | |
|
6 | 6 | <div class="box"> |
|
7 | 7 | <p><label><%= l(:label_issue_plural) %> :</label> |
@@ -15,7 +15,12 | |||
|
15 | 15 | |
|
16 | 16 | <!--[form:issue]--> |
|
17 | 17 | <p><label for="new_project_id"><%=l(:field_project)%> :</label> |
|
18 | <%= select_tag "new_project_id", options_from_collection_for_select(@projects, "id", "name", @project.id) %></p> | |
|
18 | <%= select_tag "new_project_id", | |
|
19 | options_from_collection_for_select(@projects, 'id', 'name', @target_project.id), | |
|
20 | :onchange => remote_function(:url => {:action => 'move_issues' , :id => @project}, | |
|
21 | :method => :get, | |
|
22 | :update => 'content', | |
|
23 | :with => "Form.serialize('move_form')") %></p> | |
|
19 | 24 | |
|
20 | 25 | <p><label for="new_tracker_id"><%=l(:field_tracker)%> :</label> |
|
21 | 26 | <%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p> |
@@ -56,7 +56,7 | |||
|
56 | 56 | </div> |
|
57 | 57 | |
|
58 | 58 | <% content_for :sidebar do %> |
|
59 | <% if authorize_for('projects', 'add_issue') %> | |
|
59 | <% if authorize_for('projects', 'add_issue') && @project.trackers.any? %> | |
|
60 | 60 | <h3><%= l(:label_issue_new) %></h3> |
|
61 | 61 | <%= l(:label_tracker) %>: <%= new_issue_selector %> |
|
62 | 62 | <% end %> |
@@ -22,7 +22,7 require 'projects_controller' | |||
|
22 | 22 | class ProjectsController; def rescue_action(e) raise e end; end |
|
23 | 23 | |
|
24 | 24 | class ProjectsControllerTest < Test::Unit::TestCase |
|
25 | fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :issue_statuses, :enabled_modules, :enumerations | |
|
25 | fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations | |
|
26 | 26 | |
|
27 | 27 | def setup |
|
28 | 28 | @controller = ProjectsController.new |
@@ -18,7 +18,7 | |||
|
18 | 18 | require File.dirname(__FILE__) + '/../test_helper' |
|
19 | 19 | |
|
20 | 20 | class IssueTest < Test::Unit::TestCase |
|
21 | fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries | |
|
21 | fixtures :projects, :users, :members, :trackers, :projects_trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries | |
|
22 | 22 | |
|
23 | 23 | def test_category_based_assignment |
|
24 | 24 | issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1) |
General Comments 0
You need to be logged in to leave comments.
Login now