From 51428634071e41696c7fcde7cdf7eac7fd2020b2 Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Thu, 9 Apr 2026 08:28:19 +0900 Subject: [PATCH] Add default due date for new issues with configurable offset from today --- app/controllers/issues_controller.rb | 2 + app/controllers/settings_controller.rb | 6 ++- app/models/mail_handler.rb | 2 + app/models/setting.rb | 27 +++++++++++++ app/views/settings/_issues.html.erb | 27 +++++++++++++ config/locales/en.yml | 2 + config/settings.yml | 2 + test/functional/issues_controller_test.rb | 43 +++++++++++++++++++++ test/functional/settings_controller_test.rb | 14 +++++++ test/unit/mail_handler_test.rb | 8 ++++ test/unit/setting_test.rb | 14 +++++++ 11 files changed, 146 insertions(+), 1 deletion(-) diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index a9c3f6183..ef4b227e1 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -618,6 +618,8 @@ class IssuesController < ApplicationController end @issue.author ||= User.current @issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date? + offset = Setting.default_issue_due_date_offset_in_days + @issue.due_date ||= User.current.today + offset if offset attrs = (params[:issue] || {}).deep_dup if action_name == 'new' && params[:was_default_status] == attrs[:status_id] diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 0caf55cd2..433a1120b 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -36,7 +36,11 @@ class SettingsController < ApplicationController def edit @notifiables = Redmine::Notifiable.all if request.post? - errors = Setting.set_all_from_params(params[:settings].to_unsafe_hash) + settings = params[:settings].to_unsafe_hash + if settings['default_issue_due_date_offset_enabled'] == '0' + settings['default_issue_due_date_offset'] = '' + end + errors = Setting.set_all_from_params(settings) if errors.blank? flash[:notice] = l(:notice_successful_update) redirect_to settings_path(:tab => params[:tab]) diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 5d246a572..1d781c4ae 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -214,6 +214,8 @@ class MailHandler < ActionMailer::Base end issue.description = cleaned_up_text_body issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date? + offset = Setting.default_issue_due_date_offset_in_days + issue.due_date ||= User.current.today + offset if offset if handler_options[:issue][:is_private] == '1' issue.is_private = true end diff --git a/app/models/setting.rb b/app/models/setting.rb index e955a7335..9f7e010ae 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -192,6 +192,22 @@ class Setting < ApplicationRecord messages << [:mail_from, l('activerecord.errors.messages.invalid')] end end + if settings.key?(:default_issue_due_date_offset) + value = settings[:default_issue_due_date_offset].to_s.strip + unless value.blank? + begin + offset = Integer(value, 10) + if offset < 0 + messages << [ + :default_issue_due_date_offset, + l('activerecord.errors.messages.greater_than_or_equal_to', :count => 0) + ] + end + rescue ArgumentError + messages << [:default_issue_due_date_offset, l('activerecord.errors.messages.not_a_number')] + end + end + end messages end @@ -238,6 +254,17 @@ class Setting < ApplicationRecord params end + def self.default_issue_due_date_offset_from_params(params) + params.to_s.strip + end + + def self.default_issue_due_date_offset_in_days + value = default_issue_due_date_offset.to_s.strip + return nil if value.blank? || !value.match?(/\A\d+\z/) + + value.to_i + end + def self.twofa_required? twofa == '2' end diff --git a/app/views/settings/_issues.html.erb b/app/views/settings/_issues.html.erb index 3a3a36d1b..d2265dd34 100644 --- a/app/views/settings/_issues.html.erb +++ b/app/views/settings/_issues.html.erb @@ -15,6 +15,33 @@

<%= setting_check_box :default_issue_start_date_to_creation_date %>

+<% + default_due_date_offset_enabled = + if params[:settings] + params[:settings][:default_issue_due_date_offset_enabled].to_s != '0' + else + Setting.default_issue_due_date_offset.present? + end +%> +

+ + <%= hidden_field_tag 'settings[default_issue_due_date_offset_enabled]', 0, :id => nil %> + <%= check_box_tag 'settings[default_issue_due_date_offset_enabled]', 1, + default_due_date_offset_enabled, + :id => 'settings_default_issue_due_date_offset_enabled', + :data => {:enables => '#settings_default_issue_due_date_offset'} %> +

+

+ + <%= setting_text_field :default_issue_due_date_offset, + :label => false, + :size => 6, + :disabled => !default_due_date_offset_enabled %> + <%= l(:text_default_issue_due_date_offset_from_today) %> +

+

<%= setting_check_box :display_subprojects_issues %>

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

diff --git a/config/locales/en.yml b/config/locales/en.yml index 8315e12ce..94784905a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -498,6 +498,8 @@ en: setting_gantt_months_limit: Maximum number of months displayed on the gantt chart setting_issue_group_assignment: Allow issue assignment to groups setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + text_enable_default_issue_due_date_offset: Enable default due date for new issues + text_default_issue_due_date_offset_from_today: days from today setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed setting_unsubscribe: Allow users to delete their own account setting_session_lifetime: Session maximum lifetime diff --git a/config/settings.yml b/config/settings.yml index 982bc5f1f..e5e77567d 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -198,6 +198,8 @@ issue_group_assignment: default: 0 default_issue_start_date_to_creation_date: default: 0 +default_issue_due_date_offset: + default: '' notified_events: serialized: true default: diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 3ea54226d..b39a6cc7d 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -3860,6 +3860,21 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_get_new_with_default_due_date_offset + with_settings :default_issue_due_date_offset => '7' do + @request.session[:user_id] = 2 + get( + :new, + :params => { + :project_id => 1, + :tracker_id => 1 + } + ) + assert_response :success + assert_select 'input[name=?][value=?]', 'issue[due_date]', (Date.today + 7.days).to_s + end + end + def test_get_new_form_should_allow_attachment_upload @request.session[:user_id] = 2 get( @@ -4392,6 +4407,34 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_post_create_without_due_date_and_default_due_date_offset + with_settings :default_issue_due_date_offset => '5' do + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post( + :create, + :params => { + :project_id => 1, + :issue => { + :tracker_id => 3, + :status_id => 2, + :subject => 'This is the test_new issue with default due date', + :description => 'This is the description', + :priority_id => 5, + :estimated_hours => '', + :custom_field_values => { + '2' => 'Value for field 2' + } + } + } + ) + end + issue = Issue.find_by_subject('This is the test_new issue with default due date') + assert_not_nil issue + assert_equal Date.today + 5, issue.due_date + end + end + def test_post_create_and_continue @request.session[:user_id] = 2 assert_difference 'Issue.count' do diff --git a/test/functional/settings_controller_test.rb b/test/functional/settings_controller_test.rb index ef17883ec..a936ff0ab 100644 --- a/test/functional/settings_controller_test.rb +++ b/test/functional/settings_controller_test.rb @@ -141,6 +141,20 @@ class SettingsControllerTest < Redmine::ControllerTest ) end + def test_post_edit_should_clear_default_due_date_offset_when_disabled + with_settings :default_issue_due_date_offset => '5' do + post :edit, :params => { + :tab => 'issues', + :settings => { + :default_issue_due_date_offset_enabled => '0', + :default_issue_due_date_offset => '7' + } + } + assert_redirected_to '/settings?tab=issues' + assert_equal '', Setting.default_issue_due_date_offset + end + end + def test_post_edit_with_invalid_setting_should_not_error post :edit, :params => { :settings => { diff --git a/test/unit/mail_handler_test.rb b/test/unit/mail_handler_test.rb index 373819be9..202e0ab11 100644 --- a/test/unit/mail_handler_test.rb +++ b/test/unit/mail_handler_test.rb @@ -291,6 +291,14 @@ class MailHandlerTest < ActiveSupport::TestCase end end + def test_add_issue_should_set_default_due_date + with_settings :default_issue_due_date_offset => '5' do + issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'}) + assert issue.is_a?(Issue) + assert_equal Date.today + 5, issue.due_date + end + end + def test_add_issue_should_add_cc_as_watchers user = User.find_by_mail('dlopper@somenet.foo') issue = submit_email('ticket_with_cc.eml', :issue => {:project => 'ecookbook'}) diff --git a/test/unit/setting_test.rb b/test/unit/setting_test.rb index 79b44fa91..492d11925 100644 --- a/test/unit/setting_test.rb +++ b/test/unit/setting_test.rb @@ -135,6 +135,20 @@ class SettingTest < ActiveSupport::TestCase end end + def test_default_issue_due_date_offset_should_validate_values + with_locale 'en' do + assert_nil Setting.set_all_from_params(:default_issue_due_date_offset => '') + assert_nil Setting.set_all_from_params(:default_issue_due_date_offset => '0') + assert_nil Setting.set_all_from_params(:default_issue_due_date_offset => '5') + + errors = Setting.set_all_from_params(:default_issue_due_date_offset => 'foo') + assert_includes errors, [:default_issue_due_date_offset, 'is not a number'] + + errors = Setting.set_all_from_params(:default_issue_due_date_offset => '-1') + assert_includes errors, [:default_issue_due_date_offset, 'must be greater than or equal to 0'] + end + end + def test_default_text_formatting_for_new_installations_is_common_mark assert_equal 'common_mark', Setting.text_formatting end -- 2.50.1