Project

General

Profile

Feature #22802 » manual_set_issue_position_in_version_redmine_4.1.1-2.patch

max13fr -, 2020-06-17 13:39

View differences:

app/controllers/enumerations_controller.rb
25 25
  before_action :require_admin_or_api_request, :only => :index
26 26
  before_action :build_new_enumeration, :only => [:new, :create]
27 27
  before_action :find_enumeration, :only => [:edit, :update, :destroy]
28
  accept_api_auth :index
28
  accept_api_auth :index, :update
29 29

  
30 30
  helper :custom_fields
31 31

  
app/controllers/issues_controller.rb
184 184

  
185 185
      respond_to do |format|
186 186
        format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) }
187
        format.js { head 200 }
187 188
        format.api  { render_api_ok }
188 189
      end
189 190
    else
190 191
      respond_to do |format|
191 192
        format.html { render :action => 'edit' }
193
        format.js { head 422 }
192 194
        format.api  { render_validation_errors(@issue) }
193 195
      end
194 196
    end
app/controllers/versions_controller.rb
52 52
            includes(:project, :tracker).
53 53
            preload(:status, :priority, :fixed_version).
54 54
            where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)).
55
            order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
55
            #order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
56
            order(order_issues_by)
56 57
          @issues_by_version = issues.group_by(&:fixed_version)
57 58
        end
58 59
        @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
......
69 70
        @issues = @version.fixed_issues.visible.
70 71
          includes(:status, :tracker, :priority).
71 72
          preload(:project).
72
          reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
73
          #reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
74
          reorder(order_issues_by).
73 75
          to_a
74 76
      }
75 77
      format.api
......
173 175
    end
174 176
  end
175 177

  
178
  def order_issues_by
179
    if Setting.manual_issue_position_in_versions == '1'
180
       return "COALESCE(#{Issue.table_name}.position, 999999)"
181
    else
182
       return "#{Tracker.table_name}.position, #{Issue.table_name}.id"
183
    end
184
  end
185

  
176 186
  private
177 187

  
178 188
  def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
app/models/issue.rb
54 54
  acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status),
55 55
                            :author_key => :author_id
56 56

  
57
  acts_as_positioned :scope => [:fixed_version_id]
58

  
57 59
  DONE_RATIO_OPTIONS = %w(issue_field issue_status)
58 60

  
59 61
  attr_writer :deleted_attachment_ids
......
494 496
      (issue.new_record? || issue.attributes_editable?(user)) &&
495 497
        user.allowed_to?(:manage_subtasks, issue.project)
496 498
    })
499
  safe_attributes(
500
    'position',
501
    :if => lambda {|issue, user| user.allowed_to?(:change_issue_position_in_version, issue.project)}
502
  )
497 503
  safe_attributes(
498 504
    'deleted_attachment_ids',
499 505
    :if => lambda {|issue, user| issue.attachments_deletable?(user)})
......
832 838

  
833 839
  # Returns the names of attributes that are journalized when updating the issue
834 840
  def journalized_attribute_names
835
    names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)
841
    names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on)
836 842
    if tracker
837 843
      names -= tracker.disabled_core_fields
838 844
    end
app/views/settings/_issues.html.erb
15 15

  
16 16
<p><%= setting_check_box :display_subprojects_issues %></p>
17 17

  
18
<p><%= setting_check_box :manual_issue_position_in_versions %></p>
19

  
18 20
<p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
19 21

  
20 22
<p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p>
app/views/versions/index.html.erb
32 32
      <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
33 33
        <table class="list related-issues">
34 34
        <caption><%= l(:label_related_issues) %></caption>
35
        <% issues.each do |issue| -%>
36
          <tr class="hascontextmenu <%= issue.css_classes %>">
37
            <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
38
            <td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
39
            <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
40
            <td class="buttons"><%= link_to_context_menu %></td>
41
          </tr>
42
        <% end -%>
35
          <tbody>
36
            <% issues.each do |issue| -%>
37
              <tr class="hascontextmenu <%= issue.css_classes %>">
38
                <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
39
                <td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
40
                <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
41
                <td class="buttons"><%= link_to_context_menu %></td>
42
                <% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, version.project) %>
43
                  <td class="sortable"><%= reorder_handle(issue) %></td>
44
                <% end%>
45
              </tr>
46
            <% end -%>
47
          </tbody>
43 48
        </table>
44 49
      <% end %>
45 50
    <% end %>
......
108 113
<% html_title(l(:label_roadmap)) %>
109 114

  
110 115
<%= context_menu %>
116

  
117
<% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, @project) %>
118
  <%= javascript_tag do %>
119
    $(function() { $("table.related-issues tbody").positionedItems(); });
120
  <% end %>
121
<% end %>
app/views/versions/show.html.erb
41 41
<% if @issues.present? %>
42 42
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
43 43
  <table class="list related-issues">
44
  <caption><%= l(:label_related_issues) %></caption>
45
  <%- @issues.each do |issue| -%>
46
    <tr class="hascontextmenu <%= issue.css_classes %>">
47
      <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
48
      <td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
49
      <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
50
      <td class="buttons"><%= link_to_context_menu %></td>
51
    </tr>
52
  <% end %>
44
    <caption><%= l(:label_related_issues) %></caption>
45
    <tbody>
46
      <%- @issues.each do |issue| -%>
47
        <tr class="hascontextmenu <%= issue.css_classes %>">
48
          <td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
49
          <td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
50
          <td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
51
          <td class="buttons"><%= link_to_context_menu %></td>
52
          <% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %>
53
            <td class="sortable"><%= reorder_handle(issue) %></td>
54
          <% end%>
55
        </tr>
56
      <% end %>
57
    </tbody>
53 58
  </table>
54 59
<% end %>
55 60
<%= context_menu %>
......
59 64
<%= call_hook :view_versions_show_bottom, :version => @version %>
60 65

  
61 66
<% html_title @version.name %>
67

  
68
<% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %>
69
  <%= javascript_tag do %>
70
    $(function() { $("table.related-issues tbody").positionedItems(); });
71
  <% end %>
72
<% end %>
config/locales/en.yml
485 485
  setting_timelog_accept_future_dates: Accept time logs on future dates
486 486
  setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications subject
487 487
  setting_project_list_defaults: Projects list defaults
488
  setting_manual_issue_position_in_versions: Enable manually set issue position in versions
488 489

  
489 490
  permission_add_project: Create project
490 491
  permission_add_subprojects: Create subprojects
......
553 554
  permission_manage_related_issues: Manage related issues
554 555
  permission_import_issues: Import issues
555 556
  permission_log_time_for_other_users: Log spent time for other users
557
  permission_change_issue_position_in_version: Change issue position in version
556 558

  
557 559
  project_module_issue_tracking: Issue tracking
558 560
  project_module_time_tracking: Time tracking
config/locales/fr.yml
482 482
  setting_time_entry_list_defaults: Affichage par défaut de la liste des temps passés
483 483
  setting_timelog_accept_0_hours: Autoriser la saisie de temps avec 0 heure
484 484
  setting_timelog_max_hours_per_day: Maximum d'heures pouvant être saisies par un utilisateur sur un jour
485
  setting_manual_issue_position_in_versions: Activer le positionnement manuel des tickets dans les versions
485 486

  
486 487
  permission_add_project: Créer un projet
487 488
  permission_add_subprojects: Créer des sous-projets
......
548 549
  permission_manage_subtasks: Gérer les sous-tâches
549 550
  permission_manage_related_issues: Gérer les demandes associées
550 551
  permission_import_issues: Importer des demandes
552
  permission_change_issue_position_in_version: Changer la position d'un ticket dans une version
551 553

  
552 554
  project_module_issue_tracking: Suivi des demandes
553 555
  project_module_time_tracking: Suivi du temps passé
config/settings.yml
177 177
  default: 'derived'
178 178
link_copied_issue:
179 179
  default: 'ask'
180
manual_issue_position_in_versions:
181
  default: 1
180 182
close_duplicate_issues:
181 183
  default: 1
182 184
issue_group_assignment:
db/migrate/20200315154300_add_issue_position.rb
1
class AddIssuePosition < ActiveRecord::Migration[5.2]
2
  def self.up
3
    add_column :issues, :position, :integer
4
  end
5

  
6
  def self.down
7
    remove_column :issues, :position
8
  end
9
end
lib/redmine.rb
116 116
    map.permission :view_private_notes, {}, :read => true, :require => :member
117 117
    map.permission :set_notes_private, {}, :require => :member
118 118
    map.permission :delete_issues, {:issues => :destroy}, :require => :member
119
    map.permission :change_issue_position_in_version, {}
119 120
    # Watchers
120 121
    map.permission :view_issue_watchers, {}, :read => true
121 122
    map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
public/stylesheets/application.css
607 607
div#roadmap .related-issues td.checkbox { display: none; }
608 608
div#roadmap .related-issues td.assigned_to { width:1px; white-space:nowrap; padding: 0; }
609 609
div#roadmap .related-issues td.assigned_to img { padding-left: 4px; padding-right: 4px;}
610
div#roadmap .related-issues td.sortable { text-align: right; }
610 611
div#roadmap .wiki h1:first-child { display: none; }
611 612
div#roadmap .wiki h1 { font-size: 120%; }
612 613
div#roadmap .wiki h2 { font-size: 110%; }
app/models/issue_query.rb
39 39
    QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date", :groupable => true),
40 40
    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true),
41 41
    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
42
    QueryColumn.new(:position, :sortable => "#{Issue.table_name}.position"),
42 43
    QueryColumn.new(
43 44
      :total_estimated_hours,
44 45
      :sortable => -> {
......
157 158
    add_available_filter "start_date", :type => :date
158 159
    add_available_filter "due_date", :type => :date
159 160
    add_available_filter "estimated_hours", :type => :float
161
    add_available_filter "position", :type => :integer
160 162

  
161 163
    if User.current.allowed_to?(:view_time_entries, project, :global => true)
162 164
      add_available_filter "spent_time", :type => :float, :label => :label_spent_time
(6-6/9)