Feature #1767 » 0004-Make-spent-time-project-custom-fields-configurable-s.patch
app/controllers/custom_fields_controller.rb | ||
---|---|---|
31 | 31 |
format.html do |
32 | 32 |
@custom_fields_by_type = CustomField.all.group_by {|f| f.class.name} |
33 | 33 |
@custom_fields_projects_count = |
34 |
IssueCustomField.where(is_for_all: false).joins(:projects).group(:custom_field_id).count |
|
34 |
[IssueCustomField, TimeEntryCustomField, ProjectCustomField].each_with_object({}) do |klass, _| |
|
35 |
_.merge!( |
|
36 |
klass.where(is_for_all: false).joins(:projects).group(:custom_field_id).count |
|
37 |
) |
|
38 |
end |
|
35 | 39 |
end |
36 | 40 |
format.api do |
37 | 41 |
@custom_fields = CustomField.all |
app/controllers/project_enumerations_controller.rb | ||
---|---|---|
22 | 22 |
before_action :authorize |
23 | 23 | |
24 | 24 |
def update |
25 |
if @project.update_or_create_time_entry_activities(update_params) |
|
26 |
flash[:notice] = l(:notice_successful_update) |
|
25 |
Project.transaction do |
|
26 |
if params[:project] |
|
27 |
@project.safe_attributes = params[:project] |
|
28 |
raise ActiveRecord::Rollback unless @project.save |
|
29 |
end |
|
30 |
if @project.update_or_create_time_entry_activities(update_params) |
|
31 |
flash[:notice] = l(:notice_successful_update) |
|
32 |
else |
|
33 |
raise ActiveRecord::Rollback |
|
34 |
end |
|
27 | 35 |
end |
28 | 36 | |
29 | 37 |
redirect_to settings_project_path(@project, :tab => 'activities') |
app/controllers/projects_controller.rb | ||
---|---|---|
95 | 95 | |
96 | 96 |
def new |
97 | 97 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
98 |
@project_custom_fields = ProjectCustomField.sorted.to_a |
|
98 | 99 |
@trackers = Tracker.sorted.to_a |
99 | 100 |
@project = Project.new |
100 | 101 |
@project.safe_attributes = params[:project] |
... | ... | |
102 | 103 | |
103 | 104 |
def create |
104 | 105 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
106 |
@project_custom_fields = ProjectCustomField.sorted.to_a |
|
105 | 107 |
@trackers = Tracker.sorted.to_a |
106 | 108 |
@project = Project.new |
107 | 109 |
@project.safe_attributes = params[:project] |
... | ... | |
139 | 141 | |
140 | 142 |
def copy |
141 | 143 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
144 |
@project_custom_fields = ProjectCustomField.sorted.to_a |
|
145 |
@time_entry_custom_fields = TimeEntryCustomField.sorted.to_a |
|
142 | 146 |
@trackers = Tracker.sorted.to_a |
143 | 147 |
@source_project = Project.find(params[:id]) |
144 | 148 |
if request.get? |
... | ... | |
198 | 202 | |
199 | 203 |
def settings |
200 | 204 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
205 |
@project_custom_fields = ProjectCustomField.sorted.to_a |
|
206 |
@time_entry_custom_fields = TimeEntryCustomField.sorted.to_a |
|
201 | 207 |
@issue_category ||= IssueCategory.new |
202 | 208 |
@member ||= @project.members.new |
203 | 209 |
@trackers = Tracker.sorted.to_a |
app/models/custom_field.rb | ||
---|---|---|
117 | 117 |
end |
118 | 118 |
if self.is_a?(IssueCustomField) |
119 | 119 |
self.tracker_ids = custom_field.tracker_ids.dup |
120 |
end |
|
121 |
if %w(IssueCustomField TimeEntryCustomField ProjectCustomField).include?(self.class.name) |
|
120 | 122 |
self.project_ids = custom_field.project_ids.dup |
121 | 123 |
end |
122 | 124 |
self |
app/models/project.rb | ||
---|---|---|
58 | 58 |
:class_name => 'IssueCustomField', |
59 | 59 |
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", |
60 | 60 |
:association_foreign_key => 'custom_field_id' |
61 |
has_and_belongs_to_many :project_custom_fields, |
|
62 |
lambda {order(:position)}, |
|
63 |
:class_name => 'ProjectCustomField', |
|
64 |
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", |
|
65 |
:association_foreign_key => 'custom_field_id' |
|
66 |
has_and_belongs_to_many :time_entry_custom_fields, |
|
67 |
lambda {order(:position)}, |
|
68 |
:class_name => 'TimeEntryCustomField', |
|
69 |
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", |
|
70 |
:association_foreign_key => 'custom_field_id' |
|
61 | 71 |
# Default Custom Query |
62 | 72 |
belongs_to :default_issue_query, :class_name => 'IssueQuery' |
63 | 73 | |
... | ... | |
366 | 376 |
@rolled_up_statuses = nil |
367 | 377 |
@rolled_up_custom_fields = nil |
368 | 378 |
@all_issue_custom_fields = nil |
379 |
@all_project_custom_fields = nil |
|
369 | 380 |
@all_time_entry_custom_fields = nil |
370 | 381 |
@to_param = nil |
371 | 382 |
@allowed_parents = nil |
... | ... | |
643 | 654 |
end |
644 | 655 |
end |
645 | 656 | |
657 |
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields |
|
658 |
def available_custom_fields |
|
659 |
all_project_custom_fields |
|
660 |
end |
|
661 | ||
662 |
def all_project_custom_fields |
|
663 |
@all_project_custom_fields ||= |
|
664 |
if new_record? |
|
665 |
ProjectCustomField.sorted. |
|
666 |
where("is_for_all = ? OR id IN (?)", true, project_custom_field_ids) |
|
667 |
else |
|
668 |
ProjectCustomField.sorted. |
|
669 |
where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" + |
|
670 |
" FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" + |
|
671 |
" WHERE cfp.project_id = ?)", true, id) |
|
672 |
end |
|
673 |
end |
|
674 | ||
675 |
def all_time_entry_custom_fields |
|
676 |
@all_time_entry_custom_fields ||= |
|
677 |
if new_record? |
|
678 |
TimeEntryCustomField.sorted. |
|
679 |
where("is_for_all = ? OR id IN (?)", true, time_entry_custom_field_ids) |
|
680 |
else |
|
681 |
TimeEntryCustomField.sorted. |
|
682 |
where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" + |
|
683 |
" FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" + |
|
684 |
" WHERE cfp.project_id = ?)", true, id) |
|
685 |
end |
|
686 |
end |
|
687 | ||
646 | 688 |
# Returns a scope of all custom fields enabled for issues of the project |
647 | 689 |
# and its subprojects |
648 | 690 |
def rolled_up_custom_fields |
... | ... | |
824 | 866 |
'custom_fields', |
825 | 867 |
'tracker_ids', |
826 | 868 |
'issue_custom_field_ids', |
869 |
'project_custom_field_ids', |
|
870 |
'time_entry_custom_field_ids', |
|
827 | 871 |
'parent_id', |
828 | 872 |
'default_version_id', |
829 | 873 |
'default_issue_query_id', |
... | ... | |
869 | 913 |
end |
870 | 914 |
end |
871 | 915 | |
916 |
if project_custom_field_ids = attrs.delete('project_custom_field_ids') |
|
917 |
super({'project_custom_field_ids' => project_custom_field_ids}, user) |
|
918 |
end |
|
919 | ||
872 | 920 |
# Reject custom fields values not visible by the user |
873 | 921 |
if attrs['custom_field_values'].present? |
874 | 922 |
editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s} |
... | ... | |
944 | 992 |
copy.trackers = project.trackers |
945 | 993 |
copy.custom_values = project.custom_values.collect {|v| v.clone} |
946 | 994 |
copy.issue_custom_fields = project.issue_custom_fields |
995 |
copy.project_custom_fields = project.project_custom_fields |
|
996 |
copy.time_entry_custom_fields = project.time_entry_custom_fields |
|
947 | 997 |
copy |
948 | 998 |
end |
949 | 999 |
app/models/project_custom_field.rb | ||
---|---|---|
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 | 20 |
class ProjectCustomField < CustomField |
21 |
has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id", :autosave => true |
|
22 | ||
23 |
safe_attributes 'project_ids' |
|
24 | ||
21 | 25 |
def type_name |
22 | 26 |
:label_project_plural |
23 | 27 |
end |
... | ... | |
28 | 32 | |
29 | 33 |
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil) |
30 | 34 |
project_key ||= "#{Project.table_name}.id" |
31 |
super(project_key, user, id_column) |
|
35 |
id_column ||= id |
|
36 |
sql = super(project_key, user, id_column) |
|
37 |
project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{self.class.connection.quoted_true} AND ifa.id = #{id_column})" + |
|
38 |
" OR #{Project.table_name}.id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id_column})" |
|
39 | ||
40 |
"((#{sql}) AND (#{project_condition}))" |
|
32 | 41 |
end |
33 | 42 |
end |
app/models/time_entry.rb | ||
---|---|---|
228 | 228 |
end |
229 | 229 |
end |
230 | 230 | |
231 |
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields |
|
232 |
def available_custom_fields |
|
233 |
p = issue&.project || project |
|
234 |
p ? p.all_time_entry_custom_fields : super |
|
235 |
end |
|
236 | ||
231 | 237 |
def assignable_users |
232 | 238 |
users = [] |
233 | 239 |
if project |
app/models/time_entry_custom_field.rb | ||
---|---|---|
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 | 20 |
class TimeEntryCustomField < CustomField |
21 |
has_and_belongs_to_many :projects, :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :foreign_key => "custom_field_id", :autosave => true |
|
22 | ||
23 |
safe_attributes 'project_ids' |
|
24 | ||
21 | 25 |
def type_name |
22 | 26 |
:label_spent_time |
23 | 27 |
end |
... | ... | |
26 | 30 |
super || (roles & user.roles_for_project(project)).present? |
27 | 31 |
end |
28 | 32 | |
33 |
def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil) |
|
34 |
id_column ||= id |
|
35 |
sql = super(project_key, user, id_column) |
|
36 |
project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{self.class.connection.quoted_true} AND ifa.id = #{id_column})" + |
|
37 |
" OR #{TimeEntry.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id_column})" |
|
38 | ||
39 |
"((#{sql}) AND (#{project_condition}))" |
|
40 |
end |
|
41 | ||
29 | 42 |
def validate_custom_field |
30 | 43 |
super |
31 | 44 |
errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) unless visible? || roles.present? |
app/views/custom_fields/_form.html.erb | ||
---|---|---|
59 | 59 | |
60 | 60 |
<% if @custom_field.is_a?(IssueCustomField) %> |
61 | 61 |
<%= render :partial => 'visibility_by_tracker_selector', :locals => { :f => f } %> |
62 |
<% end %> |
|
62 | 63 | |
64 |
<% if %w(IssueCustomField TimeEntryCustomField ProjectCustomField).include?(@custom_field.class.name) %> |
|
63 | 65 |
<%= render :partial => 'visibility_by_project_selector', :locals => { :f => f } %> |
64 | 66 |
<% end %> |
65 | 67 |
</div> |
app/views/custom_fields/_index.html.erb | ||
---|---|---|
3 | 3 |
<th><%=l(:field_name)%></th> |
4 | 4 |
<th><%=l(:field_field_format)%></th> |
5 | 5 |
<th><%=l(:field_is_required)%></th> |
6 |
<% if tab[:name] == 'IssueCustomField' %>
|
|
6 |
<% if %w(IssueCustomField TimeEntryCustomField ProjectCustomField).include?(tab[:name]) %>
|
|
7 | 7 |
<th><%=l(:field_is_for_all)%></th> |
8 | 8 |
<th><%=l(:label_used_by)%></th> |
9 | 9 |
<% end %> |
... | ... | |
16 | 16 |
<td class="name"><%= link_to custom_field.name, edit_custom_field_path(custom_field) %></td> |
17 | 17 |
<td><%= l(custom_field.format.label) %></td> |
18 | 18 |
<td><%= checked_image custom_field.is_required? %></td> |
19 |
<% if tab[:name] == 'IssueCustomField' %>
|
|
19 |
<% if %w(IssueCustomField TimeEntryCustomField ProjectCustomField).include?(tab[:name]) %>
|
|
20 | 20 |
<td><%= checked_image custom_field.is_for_all? %></td> |
21 |
<td><%= l(:label_x_projects, :count => @custom_fields_projects_count[custom_field.id] || 0) if custom_field.is_a? IssueCustomField and !custom_field.is_for_all? %></td>
|
|
21 |
<td><%= l(:label_x_projects, :count => @custom_fields_projects_count[custom_field.id] || 0) if [IssueCustomField, TimeEntryCustomField, ProjectCustomField].include?(custom_field.class) and !custom_field.is_for_all? %></td>
|
|
22 | 22 |
<% end %> |
23 | 23 |
<td class="buttons"> |
24 | 24 |
<%= reorder_handle(custom_field, :url => custom_field_path(custom_field), :param => 'custom_field') %> |
app/views/projects/_form.html.erb | ||
---|---|---|
31 | 31 |
<%= call_hook(:view_projects_form, :project => @project, :form => f) %> |
32 | 32 |
</div> |
33 | 33 | |
34 |
<% unless @project_custom_fields.empty? %> |
|
35 |
<fieldset class="box tabular" id="project_project_custom_fields"><legend><%= toggle_checkboxes_link('#project_project_custom_fields input[type=checkbox]:enabled') %><%=l(:label_custom_field_plural)%></legend> |
|
36 |
<% if User.current.admin? %> |
|
37 |
<div class="contextual"><%= link_to l(:label_administration), custom_fields_path(tab: ProjectCustomField.name), class: "icon icon-settings" %></div> |
|
38 |
<% end %> |
|
39 |
<% all_project_custom_fields = @project.all_project_custom_fields %> |
|
40 |
<% @project_custom_fields.each do |custom_field| %> |
|
41 |
<label class="floating"> |
|
42 |
<%= check_box_tag 'project[project_custom_field_ids][]', custom_field.id, all_project_custom_fields.include?(custom_field), |
|
43 |
disabled: (custom_field.is_for_all? ? "disabled" : nil), |
|
44 |
id: nil %> |
|
45 |
<%= custom_field_name_tag(custom_field) %> |
|
46 |
</label> |
|
47 |
<% end %> |
|
48 |
<%= hidden_field_tag 'project[project_custom_field_ids][]', '', id: nil %> |
|
49 |
</fieldset> |
|
50 |
<%= javascript_tag do %> |
|
51 |
$(document).ready(function(){ |
|
52 |
var custom_field_toggle_disabled = function(custom_field_id){ |
|
53 |
var custom_field_value = $('#project_custom_field_values_' + $(custom_field_id).attr('value')); |
|
54 |
custom_field_value.prop('disabled', !$(custom_field_id).prop('checked')); |
|
55 |
}; |
|
56 |
$('#project_project_custom_fields input[type=checkbox]').change(function(){ |
|
57 |
custom_field_toggle_disabled(this); |
|
58 |
}); |
|
59 |
$('#project_project_custom_fields input[type=checkbox]').each(function(){ |
|
60 |
custom_field_toggle_disabled(this); |
|
61 |
}); |
|
62 |
}); |
|
63 |
<% end %> |
|
64 |
<% end %> |
|
65 | ||
34 | 66 |
<% if @project.safe_attribute?('enabled_module_names') %> |
35 | 67 |
<fieldset class="box tabular" id="project_modules"><legend><%= toggle_checkboxes_link('#project_modules input[type="checkbox"]') %><%= l(:label_module_plural) %></legend> |
36 | 68 |
<% Redmine::AccessControl.available_project_modules.each do |m| %> |
app/views/projects/copy.html.erb | ||
---|---|---|
26 | 26 |
<%= hidden_field_tag 'project[issue_custom_field_ids][]', issue_custom_field_id %> |
27 | 27 |
<% end %> |
28 | 28 | |
29 |
<% @project.time_entry_custom_field_ids.each do |time_entry_custom_field_id| %> |
|
30 |
<%= hidden_field_tag 'project[time_entry_custom_field_ids][]', time_entry_custom_field_id %> |
|
31 |
<% end %> |
|
32 | ||
29 | 33 |
<%= submit_tag l(:button_copy) %> |
30 | 34 |
<% end %> |
app/views/projects/settings/_activities.html.erb | ||
---|---|---|
41 | 41 |
<% end %> |
42 | 42 |
</table> |
43 | 43 | |
44 |
<% unless @time_entry_custom_fields.empty? %> |
|
45 |
<fieldset class="box tabular" id="project_time_entry_custom_fields"><legend><%= toggle_checkboxes_link('#project_time_entry_custom_fields input[type=checkbox]:enabled') %><%=l(:label_custom_field_plural)%></legend> |
|
46 |
<% if User.current.admin? %> |
|
47 |
<div class="contextual"><%= link_to l(:label_administration), custom_fields_path(tab: TimeEntryCustomField.name), class: "icon icon-settings" %></div> |
|
48 |
<% end %> |
|
49 |
<% all_time_entry_custom_fields = @project.all_time_entry_custom_fields %> |
|
50 |
<% @time_entry_custom_fields.each do |custom_field| %> |
|
51 |
<label class="floating"> |
|
52 |
<%= check_box_tag 'project[time_entry_custom_field_ids][]', custom_field.id, all_time_entry_custom_fields.include?(custom_field), |
|
53 |
disabled: (custom_field.is_for_all? ? "disabled" : nil), |
|
54 |
id: nil %> |
|
55 |
<%= custom_field_name_tag(custom_field) %> |
|
56 |
</label> |
|
57 |
<% end %> |
|
58 |
<%= hidden_field_tag 'project[time_entry_custom_field_ids][]', '', id: nil %> |
|
59 |
</fieldset> |
|
60 |
<% end %> |
|
61 | ||
44 | 62 |
<%= submit_tag l(:button_save) %> |
45 | 63 |
<% end %> |