mirror of
https://github.com/redmine/redmine.git
synced 2025-11-13 08:46:01 +01:00
Add administration setting to allow time logging on closed issues. By default, the setting is enabled (#13596).
git-svn-id: https://svn.redmine.org/redmine/trunk@23586 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -33,7 +33,7 @@ class ContextMenusController < ApplicationController
|
|||||||
|
|
||||||
@can = {
|
@can = {
|
||||||
:edit => @issues.all?(&:attributes_editable?),
|
:edit => @issues.all?(&:attributes_editable?),
|
||||||
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),
|
:log_time => @issue&.time_loggable?,
|
||||||
:copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
|
:copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
|
||||||
:add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
|
:add_watchers => User.current.allowed_to?(:add_issue_watchers, @projects),
|
||||||
:delete => @issues.all?(&:deletable?),
|
:delete => @issues.all?(&:deletable?),
|
||||||
|
|||||||
@@ -188,6 +188,11 @@ class Issue < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns true if user or current user is allowed to log time on the issue
|
||||||
|
def time_loggable?(user=User.current)
|
||||||
|
user.allowed_to?(:log_time, project) && (!Setting.timelog_accept_future_dates? || !closed?)
|
||||||
|
end
|
||||||
|
|
||||||
# Returns true if user or current user is allowed to edit or add notes to the issue
|
# Returns true if user or current user is allowed to edit or add notes to the issue
|
||||||
def editable?(user=User.current)
|
def editable?(user=User.current)
|
||||||
attributes_editable?(user) || notes_addable?(user)
|
attributes_editable?(user) || notes_addable?(user)
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ class TimeEntry < ApplicationRecord
|
|||||||
if spent_on && spent_on_changed? && user
|
if spent_on && spent_on_changed? && user
|
||||||
errors.add :base, I18n.t(:error_spent_on_future_date) if !Setting.timelog_accept_future_dates? && (spent_on > user.today)
|
errors.add :base, I18n.t(:error_spent_on_future_date) if !Setting.timelog_accept_future_dates? && (spent_on > user.today)
|
||||||
end
|
end
|
||||||
|
if !Setting.timelog_accept_closed_issues? && issue&.closed? && issue&.was_closed?
|
||||||
|
errors.add :base, I18n.t(:error_spent_on_closed_issue)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def hours=(h)
|
def hours=(h)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
:onclick => 'showAndScrollTo("update", "issue_notes"); return false;',
|
:onclick => 'showAndScrollTo("update", "issue_notes"); return false;',
|
||||||
:class => 'icon icon-edit ', :accesskey => accesskey(:edit) if @issue.editable? %>
|
:class => 'icon icon-edit ', :accesskey => accesskey(:edit) if @issue.editable? %>
|
||||||
<%= link_to sprite_icon('time-add', l(:button_log_time)), new_issue_time_entry_path(@issue),
|
<%= link_to sprite_icon('time-add', l(:button_log_time)), new_issue_time_entry_path(@issue),
|
||||||
:class => 'icon icon-time-add ' if User.current.allowed_to?(:log_time, @project) %>
|
:class => 'icon icon-time-add ' if @issue.time_loggable? %>
|
||||||
<%= watcher_link(@issue, User.current) %>
|
<%= watcher_link(@issue, User.current) %>
|
||||||
<%= link_to sprite_icon('copy', l(:button_copy)), project_copy_issue_path(@project, @issue),
|
<%= link_to sprite_icon('copy', l(:button_copy)), project_copy_issue_path(@project, @issue),
|
||||||
:class => 'icon icon-copy ' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %>
|
:class => 'icon icon-copy ' if User.current.allowed_to?(:copy_issues, @project) && Issue.allowed_target_projects.any? %>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if User.current.allowed_to?(:log_time, @issue.project) %>
|
<% if @issue.time_loggable? %>
|
||||||
<fieldset class="tabular" id="log_time"><legend><%= l(:button_log_time) %></legend>
|
<fieldset class="tabular" id="log_time"><legend><%= l(:button_log_time) %></legend>
|
||||||
<%= labelled_fields_for :time_entry, @time_entry do |time_entry| %>
|
<%= labelled_fields_for :time_entry, @time_entry do |time_entry| %>
|
||||||
<div class="splitcontent">
|
<div class="splitcontent">
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
<p><%= setting_check_box :timelog_accept_0_hours %></p>
|
<p><%= setting_check_box :timelog_accept_0_hours %></p>
|
||||||
|
|
||||||
<p><%= setting_check_box :timelog_accept_future_dates %></p>
|
<p><%= setting_check_box :timelog_accept_future_dates %></p>
|
||||||
|
|
||||||
|
<p><%= setting_check_box :timelog_accept_closed_issues %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset class="box">
|
<fieldset class="box">
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ en:
|
|||||||
error_exceeds_maximum_hours_per_day: "Cannot log more than %{max_hours} hours on the same day (%{logged_hours} hours have already been logged)"
|
error_exceeds_maximum_hours_per_day: "Cannot log more than %{max_hours} hours on the same day (%{logged_hours} hours have already been logged)"
|
||||||
error_can_not_delete_auth_source: "This authentication mode is in use and cannot be deleted."
|
error_can_not_delete_auth_source: "This authentication mode is in use and cannot be deleted."
|
||||||
error_spent_on_future_date: "Cannot log time on a future date"
|
error_spent_on_future_date: "Cannot log time on a future date"
|
||||||
|
error_spent_on_closed_issue: "Cannot log time on a closed issue"
|
||||||
error_not_allowed_to_log_time_for_other_users: "You are not allowed to log time for other users"
|
error_not_allowed_to_log_time_for_other_users: "You are not allowed to log time for other users"
|
||||||
error_can_not_execute_macro_html: "Error executing the <strong>%{name}</strong> macro (%{error})"
|
error_can_not_execute_macro_html: "Error executing the <strong>%{name}</strong> macro (%{error})"
|
||||||
error_macro_does_not_accept_block: "This macro does not accept a block of text"
|
error_macro_does_not_accept_block: "This macro does not accept a block of text"
|
||||||
@@ -519,6 +520,7 @@ en:
|
|||||||
setting_timelog_accept_0_hours: Accept time logs with 0 hours
|
setting_timelog_accept_0_hours: Accept time logs with 0 hours
|
||||||
setting_timelog_max_hours_per_day: Maximum hours that can be logged per day and user
|
setting_timelog_max_hours_per_day: Maximum hours that can be logged per day and user
|
||||||
setting_timelog_accept_future_dates: Accept time logs on future dates
|
setting_timelog_accept_future_dates: Accept time logs on future dates
|
||||||
|
setting_timelog_accept_closed_issues: Accept time logs on closed issues
|
||||||
setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications subject
|
setting_show_status_changes_in_mail_subject: Show status changes in issue mail notifications subject
|
||||||
setting_project_list_defaults: Projects list defaults
|
setting_project_list_defaults: Projects list defaults
|
||||||
setting_twofa: Two-factor authentication
|
setting_twofa: Two-factor authentication
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ timelog_max_hours_per_day:
|
|||||||
default: 999
|
default: 999
|
||||||
timelog_accept_future_dates:
|
timelog_accept_future_dates:
|
||||||
default: 1
|
default: 1
|
||||||
|
timelog_accept_closed_issues:
|
||||||
|
default: 1
|
||||||
show_status_changes_in_mail_subject:
|
show_status_changes_in_mail_subject:
|
||||||
default: 1
|
default: 1
|
||||||
wiki_tablesort_enabled:
|
wiki_tablesort_enabled:
|
||||||
|
|||||||
@@ -5917,6 +5917,16 @@ class IssuesControllerTest < Redmine::ControllerTest
|
|||||||
assert_select 'input[name=?]', 'time_entry[hours]', 0
|
assert_select 'input[name=?]', 'time_entry[hours]', 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_get_edit_should_not_display_the_time_entry_form_on_closed_issue
|
||||||
|
with_settings :timelog_accept_closed_issues => '0' do
|
||||||
|
@request.session[:user_id] = 2
|
||||||
|
issue = Issue.find(1)
|
||||||
|
issue.update :status => IssueStatus.find(5)
|
||||||
|
get(:edit, :params => {:id => 1})
|
||||||
|
assert_select 'input[name=?]', 'time_entry[hours]', 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_get_edit_with_params
|
def test_get_edit_with_params
|
||||||
@request.session[:user_id] = 2
|
@request.session[:user_id] = 2
|
||||||
get(
|
get(
|
||||||
@@ -6373,6 +6383,57 @@ class IssuesControllerTest < Redmine::ControllerTest
|
|||||||
assert mail.subject.include?("(#{IssueStatus.find(2).name})")
|
assert mail.subject.include?("(#{IssueStatus.find(2).name})")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_update_should_accept_time_entry_when_closing_issue
|
||||||
|
with_settings :timelog_accept_closed_issues => '0' do
|
||||||
|
issue = Issue.find(1)
|
||||||
|
assert_equal 1, issue.status_id
|
||||||
|
@request.session[:user_id] = 2
|
||||||
|
assert_difference('TimeEntry.count', 1) do
|
||||||
|
put(
|
||||||
|
:update,
|
||||||
|
:params => {
|
||||||
|
:id => 1,
|
||||||
|
:issue => {
|
||||||
|
:status_id => 5,
|
||||||
|
},
|
||||||
|
:time_entry => {
|
||||||
|
:hours => '2',
|
||||||
|
:comments => '',
|
||||||
|
:activity_id => TimeEntryActivity.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
assert_redirected_to :action => 'show', :id => '1'
|
||||||
|
issue.reload
|
||||||
|
assert issue.closed?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_update_should_not_accept_time_entry_on_closed_issue
|
||||||
|
with_settings :timelog_accept_closed_issues => '0' do
|
||||||
|
issue = Issue.find(1)
|
||||||
|
issue.update :status => IssueStatus.find(5)
|
||||||
|
@request.session[:user_id] = 2
|
||||||
|
assert_no_difference('TimeEntry.count') do
|
||||||
|
put(
|
||||||
|
:update,
|
||||||
|
:params => {
|
||||||
|
:id => 1,
|
||||||
|
:issue => {
|
||||||
|
},
|
||||||
|
:time_entry => {
|
||||||
|
:hours => '2',
|
||||||
|
:comments => '',
|
||||||
|
:activity_id => TimeEntryActivity.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_put_update_with_note_only
|
def test_put_update_with_note_only
|
||||||
notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
|
notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,18 @@ class TimeEntryTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_should_not_accept_closed_issue
|
||||||
|
with_settings :timelog_accept_closed_issues => '0' do
|
||||||
|
project = Project.find(1)
|
||||||
|
entry = TimeEntry.generate project: project
|
||||||
|
issue = project.issues.to_a.detect(&:closed?)
|
||||||
|
entry.issue = issue
|
||||||
|
assert !entry.save
|
||||||
|
assert entry.errors[:base].present?
|
||||||
|
assert_equal 'Cannot log time on a closed issue', entry.errors[:base].first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_should_require_spent_on
|
def test_should_require_spent_on
|
||||||
with_settings :timelog_accept_future_dates => '0' do
|
with_settings :timelog_accept_future_dates => '0' do
|
||||||
entry = TimeEntry.find(1)
|
entry = TimeEntry.find(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user