mirror of
https://github.com/redmine/redmine.git
synced 2025-11-08 06:15:59 +01:00
Allow project bulk edit of time entries (#26534).
Patch by Marius BALTEANU. git-svn-id: http://svn.redmine.org/redmine/trunk@17482 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -165,8 +165,18 @@ class TimelogController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def bulk_edit
|
def bulk_edit
|
||||||
@available_activities = @projects.map(&:activities).reduce(:&)
|
@target_projects = Project.allowed_to(:log_time).to_a
|
||||||
@custom_fields = TimeEntry.first.available_custom_fields.select {|field| field.format.bulk_edit_supported}
|
@custom_fields = TimeEntry.first.available_custom_fields.select {|field| field.format.bulk_edit_supported}
|
||||||
|
if params[:time_entry]
|
||||||
|
@target_project = @target_projects.detect {|p| p.id.to_s == params[:time_entry][:project_id].to_s}
|
||||||
|
end
|
||||||
|
if @target_project
|
||||||
|
@available_activities = @target_project.activities
|
||||||
|
else
|
||||||
|
@available_activities = @projects.map(&:activities).reduce(:&)
|
||||||
|
end
|
||||||
|
@time_entry_params = params[:time_entry] || {}
|
||||||
|
@time_entry_params[:custom_field_values] ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def bulk_update
|
def bulk_update
|
||||||
|
|||||||
@@ -1562,6 +1562,20 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns an array of error messages for bulk edited items (issues, time entries)
|
||||||
|
def bulk_edit_error_messages(items)
|
||||||
|
messages = {}
|
||||||
|
items.each do |item|
|
||||||
|
item.errors.full_messages.each do |message|
|
||||||
|
messages[message] ||= []
|
||||||
|
messages[message] << item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
messages.map { |message, items|
|
||||||
|
"#{message}: " + items.map {|i| "##{i.id}"}.join(', ')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def wiki_helper
|
def wiki_helper
|
||||||
|
|||||||
@@ -166,20 +166,6 @@ module IssuesHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of error messages for bulk edited issues
|
|
||||||
def bulk_edit_error_messages(issues)
|
|
||||||
messages = {}
|
|
||||||
issues.each do |issue|
|
|
||||||
issue.errors.full_messages.each do |message|
|
|
||||||
messages[message] ||= []
|
|
||||||
messages[message] << issue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
messages.map { |message, issues|
|
|
||||||
"#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a link for adding a new subtask to the given issue
|
# Returns a link for adding a new subtask to the given issue
|
||||||
def link_to_new_subtask(issue)
|
def link_to_new_subtask(issue)
|
||||||
attrs = {
|
attrs = {
|
||||||
|
|||||||
@@ -27,35 +27,43 @@
|
|||||||
<%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id, :id => nil)}.join.html_safe %>
|
<%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id, :id => nil)}.join.html_safe %>
|
||||||
<div class="box tabular">
|
<div class="box tabular">
|
||||||
<div>
|
<div>
|
||||||
|
<p>
|
||||||
|
<label><%= l(:field_project) %></label>
|
||||||
|
<%= select_tag('time_entry[project_id]', project_tree_options_for_select(@target_projects,
|
||||||
|
:include_blank => l(:label_no_change_option), :selected => @target_project),
|
||||||
|
:onchange => "updateBulkEditFrom('#{escape_javascript url_for(:action => 'bulk_edit', :format => 'js')}')" ) %>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="time_entry_issue_id"><%= l(:field_issue) %></label>
|
<label for="time_entry_issue_id"><%= l(:field_issue) %></label>
|
||||||
<%= text_field :time_entry, :issue_id, :size => 6 %>
|
<%= text_field :time_entry, :issue_id, :size => 6 %>
|
||||||
|
<label class="inline"><%= check_box_tag 'time_entry[issue_id]', 'none', (@time_entry_params[:issue_id] == 'none'), :id => nil, :data => {:disables => '#time_entry_issue_id'} %><%= l(:button_clear) %></label>
|
||||||
|
<span id="time_entry_issue"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="time_entry_spent_on"><%= l(:field_spent_on) %></label>
|
<label for="time_entry_spent_on"><%= l(:field_spent_on) %></label>
|
||||||
<%= date_field :time_entry, :spent_on, :size => 10 %><%= calendar_for('time_entry_spent_on') %>
|
<%= date_field :time_entry, :spent_on, :size => 10, :value => @time_entry_params[:spent_on] %><%= calendar_for('time_entry_spent_on') %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="time_entry_hours"><%= l(:field_hours) %></label>
|
<label for="time_entry_hours"><%= l(:field_hours) %></label>
|
||||||
<%= text_field :time_entry, :hours, :size => 6 %>
|
<%= text_field :time_entry, :hours, :size => 6, :value => @time_entry_params[:hours] %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if @available_activities.any? %>
|
<% if @available_activities.any? %>
|
||||||
<p>
|
<p>
|
||||||
<label for="time_entry_activity_id"><%= l(:field_activity) %></label>
|
<label for="time_entry_activity_id"><%= l(:field_activity) %></label>
|
||||||
<%= select_tag('time_entry[activity_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_activities, :id, :name)) %>
|
<%= select_tag('time_entry[activity_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_activities, :id, :name, @time_entry_params[:activity_id])) %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="time_entry_comments"><%= l(:field_comments) %></label>
|
<label for="time_entry_comments"><%= l(:field_comments) %></label>
|
||||||
<%= text_field(:time_entry, :comments, :size => 100) %>
|
<%= text_field(:time_entry, :comments, :size => 100, :value => @time_entry_params[:comments]) %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% @custom_fields.each do |custom_field| %>
|
<% @custom_fields.each do |custom_field| %>
|
||||||
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('time_entry', custom_field, @time_entries) %></p>
|
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('time_entry', custom_field, @time_entries, @time_entry_params[:custom_field_values][custom_field.id.to_s]) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= call_hook(:view_time_entries_bulk_edit_details_bottom, { :time_entries => @time_entries }) %>
|
<%= call_hook(:view_time_entries_bulk_edit_details_bottom, { :time_entries => @time_entries }) %>
|
||||||
@@ -64,3 +72,47 @@
|
|||||||
|
|
||||||
<p><%= submit_tag l(:button_submit) %></p>
|
<p><%= submit_tag l(:button_submit) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= javascript_tag do %>
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#time_entry_project_id').change(function(){
|
||||||
|
<!-- $('#time_entry_issue_id').val(''); -->
|
||||||
|
$('#time_entry_issue').text('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
<% if @project || @target_project %>
|
||||||
|
observeAutocompleteField('time_entry_issue_id',
|
||||||
|
function(request, callback) {
|
||||||
|
var url = '<%= j auto_complete_issues_path %>';
|
||||||
|
var data = {
|
||||||
|
term: request.term
|
||||||
|
};
|
||||||
|
var project_id;
|
||||||
|
<% if @project %>
|
||||||
|
project_id = '<%= @project.id %>';
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
current_project_id = $('#time_entry_project_id').val();
|
||||||
|
if(current_project_id === ''){
|
||||||
|
data['project_id'] = project_id;
|
||||||
|
} else {
|
||||||
|
data['project_id'] = current_project_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.get(url, data, null, 'json')
|
||||||
|
.done(function(data){
|
||||||
|
callback(data);
|
||||||
|
})
|
||||||
|
.fail(function(jqXHR, status, error){
|
||||||
|
callback([]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: function(event, ui, data) {
|
||||||
|
$('#time_entry_issue').text(ui.item.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
1
app/views/timelog/bulk_edit.js.erb
Normal file
1
app/views/timelog/bulk_edit.js.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$('#content').html('<%= escape_javascript(render :template => 'timelog/bulk_edit', :formats => [:html]) %>');
|
||||||
@@ -156,7 +156,7 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
|
match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
|
||||||
resources :wiki, :except => [:index, :create], :as => 'wiki_page' do
|
resources :wiki, :except => [:index, :create], :as => 'wiki_page' do
|
||||||
member do
|
member do
|
||||||
@@ -232,6 +232,8 @@ Rails.application.routes.draw do
|
|||||||
match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
|
match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
|
||||||
# Used to update the new time entry form
|
# Used to update the new time entry form
|
||||||
post '/time_entries/new', :to => 'timelog#new'
|
post '/time_entries/new', :to => 'timelog#new'
|
||||||
|
# Used to update the bulk edit time entry form
|
||||||
|
post '/time_entries/bulk_edit', :to => 'timelog#bulk_edit'
|
||||||
|
|
||||||
get 'projects/:id/activity', :to => 'activities#index', :as => :project_activity
|
get 'projects/:id/activity', :to => 'activities#index', :as => :project_activity
|
||||||
get 'activity', :to => 'activities#index'
|
get 'activity', :to => 'activities#index'
|
||||||
|
|||||||
@@ -545,6 +545,11 @@ class TimelogControllerTest < Redmine::ControllerTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
|
assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
|
||||||
|
assert_select 'select[name=?]', 'time_entry[project_id]'
|
||||||
|
|
||||||
|
# Clear issue checkbox
|
||||||
|
assert_select 'input[name=?][value=?]', 'time_entry[issue_id]', 'none'
|
||||||
|
|
||||||
# System wide custom field
|
# System wide custom field
|
||||||
assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
|
assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
|
||||||
|
|
||||||
@@ -563,6 +568,34 @@ class TimelogControllerTest < Redmine::ControllerTest
|
|||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_get_bulk_edit_on_different_projects_should_propose_only_common_activites
|
||||||
|
project = Project.find(3)
|
||||||
|
TimeEntryActivity.create!(:name => 'QA', :project => project, :parent => TimeEntryActivity.find_by_name('QA'), :active => false)
|
||||||
|
@request.session[:user_id] = 1
|
||||||
|
|
||||||
|
get :bulk_edit, :params => {:ids => [1, 2, 4]}
|
||||||
|
assert_response :success
|
||||||
|
assert_select 'select[id=?]', 'time_entry_activity_id' do
|
||||||
|
assert_select 'option', 3
|
||||||
|
assert_select 'option[value=?]', '11', 0, :text => 'QA'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_get_bulk_edit_on_same_project_should_propose_project_activities
|
||||||
|
project = Project.find(1)
|
||||||
|
override_activity = TimeEntryActivity.create!({:name => "QA override", :parent => TimeEntryActivity.find_by_name("QA"), :project => project})
|
||||||
|
|
||||||
|
@request.session[:user_id] = 1
|
||||||
|
|
||||||
|
get :bulk_edit, :params => {:ids => [1, 2]}
|
||||||
|
assert_response :success
|
||||||
|
|
||||||
|
assert_select 'select[id=?]', 'time_entry_activity_id' do
|
||||||
|
assert_select 'option', 4
|
||||||
|
assert_select 'option[value=?]', override_activity.id.to_s, :text => 'QA override'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_bulk_edit_with_edit_own_time_entries_permission
|
def test_bulk_edit_with_edit_own_time_entries_permission
|
||||||
@request.session[:user_id] = 2
|
@request.session[:user_id] = 2
|
||||||
Role.find_by_name('Manager').remove_permission! :edit_time_entries
|
Role.find_by_name('Manager').remove_permission! :edit_time_entries
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class RoutingTimelogsTest < Redmine::RoutingTest
|
|||||||
|
|
||||||
def test_timelogs_bulk_edit
|
def test_timelogs_bulk_edit
|
||||||
should_route 'GET /time_entries/bulk_edit' => 'timelog#bulk_edit'
|
should_route 'GET /time_entries/bulk_edit' => 'timelog#bulk_edit'
|
||||||
|
should_route 'POST /time_entries/bulk_edit' => 'timelog#bulk_edit'
|
||||||
should_route 'POST /time_entries/bulk_update' => 'timelog#bulk_update'
|
should_route 'POST /time_entries/bulk_update' => 'timelog#bulk_update'
|
||||||
should_route 'DELETE /time_entries/destroy' => 'timelog#destroy'
|
should_route 'DELETE /time_entries/destroy' => 'timelog#destroy'
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user