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 %> |