[#45339] Modify the backend to support saving the non working days
https://community.openproject.org/work_packages/45339pull/11829/head
parent
8bcbf705b2
commit
685b8226de
@ -0,0 +1,98 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2022 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See COPYRIGHT and LICENSE files for more details. |
||||
#++ |
||||
|
||||
class Settings::WorkingDaysUpdateService < Settings::UpdateService |
||||
def call(params) |
||||
params = params.to_h.deep_symbolize_keys |
||||
self.non_working_days_params = params.delete(:non_working_days) || [] |
||||
super |
||||
end |
||||
|
||||
def validate_params(params) |
||||
contract = Settings::WorkingDaysParamsContract.new(model, user, params:) |
||||
ServiceResult.new success: contract.valid?, |
||||
errors: contract.errors, |
||||
result: model |
||||
end |
||||
|
||||
def persist(call) |
||||
results = call |
||||
ActiveRecord::Base.transaction do |
||||
# The order of merging the service is important to preserve |
||||
# the errors model's base object, which is a NonWorkingDay |
||||
results = persist_non_working_days |
||||
results.merge!(super) if results.success? |
||||
|
||||
raise ActiveRecord::Rollback if results.failure? |
||||
end |
||||
|
||||
results |
||||
end |
||||
|
||||
private |
||||
|
||||
attr_accessor :non_working_days_params |
||||
|
||||
def persist_non_working_days |
||||
# We don't support update for now |
||||
to_create, to_delete = attributes_to_create_and_delete |
||||
results = destroy_records(to_delete) |
||||
create_results = create_records(to_create) |
||||
results.merge!(create_results) |
||||
results.result = Array(results.result) + Array(create_results.result) |
||||
results |
||||
end |
||||
|
||||
def attributes_to_create_and_delete |
||||
non_working_days_params.reduce([[], []]) do |results, nwd| |
||||
results.first << nwd if !nwd[:id] |
||||
results.last << nwd[:id] if nwd[:_destroy] && nwd[:id] |
||||
results |
||||
end |
||||
end |
||||
|
||||
def create_records(attributes) |
||||
wrap_result(attributes.map { |attrs| NonWorkingDay.create(attrs) }) |
||||
end |
||||
|
||||
def destroy_records(ids) |
||||
wrap_result NonWorkingDay.where(id: ids).destroy_all |
||||
end |
||||
|
||||
def wrap_result(result) |
||||
model = NonWorkingDay.new |
||||
errors = model.errors.tap do |err| |
||||
result.each do |r| |
||||
err.merge!(r.errors) |
||||
end |
||||
end |
||||
success = model.errors.empty? |
||||
|
||||
ServiceResult.new(success:, errors:, result:) |
||||
end |
||||
end |
@ -0,0 +1,117 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2022 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See COPYRIGHT and LICENSE files for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe Admin::Settings::WorkingDaysSettingsController do |
||||
shared_let(:user) { create(:admin) } |
||||
|
||||
current_user { user } |
||||
|
||||
describe 'show' do |
||||
describe 'permissions' do |
||||
let(:fetch) { get 'show' } |
||||
|
||||
it_behaves_like 'a controller action with require_admin' |
||||
end |
||||
|
||||
it 'contains check boxes for the working days' do |
||||
get 'show' |
||||
|
||||
expect(response).to be_successful |
||||
expect(response).to render_template 'admin/settings/working_days_settings/show' |
||||
end |
||||
end |
||||
|
||||
describe 'update' do |
||||
let(:working_days) { [*'1'..'7'] } |
||||
let(:non_working_days) { {} } |
||||
let(:params) do |
||||
{ settings: { working_days:, non_working_days: } } |
||||
end |
||||
|
||||
subject { patch 'update', params: } |
||||
|
||||
it 'succeeds' do |
||||
subject |
||||
|
||||
expect(response).to redirect_to action: 'show' |
||||
expect(flash[:notice]).to eq I18n.t(:notice_successful_update) |
||||
end |
||||
|
||||
context 'with non_working_days' do |
||||
let(:non_working_days) do |
||||
{ '0' => { 'name' => 'Christmas Eve', 'date' => '2022-12-24' } } |
||||
end |
||||
|
||||
it 'succeeds' do |
||||
subject |
||||
|
||||
expect(response).to redirect_to action: 'show' |
||||
expect(flash[:notice]).to eq I18n.t(:notice_successful_update) |
||||
end |
||||
|
||||
it 'creates the non_working_days' do |
||||
expect { subject }.to change(NonWorkingDay, :count).by(1) |
||||
expect(NonWorkingDay.first).to have_attributes(name: 'Christmas Eve', date: Date.parse('2022-12-24')) |
||||
end |
||||
end |
||||
|
||||
context 'when fails with a duplicate entry' do |
||||
let(:nwd_to_delete) { create(:non_working_day, name: 'NWD to delete') } |
||||
let(:non_working_days) do |
||||
{ |
||||
'0' => { 'name' => 'Christmas Eve', 'date' => '2022-12-24' }, |
||||
'1' => { 'name' => 'Christmas Eve2', 'date' => '2022-12-24' }, |
||||
'2' => { 'id' => nwd_to_delete.id, '_destroy' => true } |
||||
} |
||||
end |
||||
|
||||
it 'displays the error message' do |
||||
subject |
||||
|
||||
expect(response).to render_template :show |
||||
expect(flash[:error]).to eq 'A non-working day already exists for 2022-12-24.' |
||||
end |
||||
|
||||
it 'sets the @modified_non_working_days variable' do |
||||
subject |
||||
expect(assigns(:modified_non_working_days)).to contain_exactly( |
||||
have_attributes(name: 'Christmas Eve', date: Date.parse('2022-12-24')), |
||||
have_attributes(name: 'Christmas Eve2', date: Date.parse('2022-12-24')), |
||||
have_attributes(nwd_to_delete.slice(:id, :name, :date)) |
||||
) |
||||
end |
||||
|
||||
it 'does not destroys other records' do |
||||
subject |
||||
expect { nwd_to_delete.reload }.not_to raise_error |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,56 @@ |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2010-2022 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See COPYRIGHT and LICENSE files for more details. |
||||
|
||||
require 'spec_helper' |
||||
|
||||
RSpec.shared_examples 'successful call' do |
||||
it 'is successful' do |
||||
expect(subject) |
||||
.to be_success |
||||
end |
||||
|
||||
it 'sets the setting value' do |
||||
subject |
||||
|
||||
expect(Setting) |
||||
.to have_received(:[]=) |
||||
.with(setting_name, new_setting_value) |
||||
end |
||||
end |
||||
|
||||
RSpec.shared_examples 'unsuccessful call' do |
||||
it 'is not successful' do |
||||
expect(subject) |
||||
.not_to be_success |
||||
end |
||||
|
||||
it 'does not set the setting value' do |
||||
subject |
||||
|
||||
expect(Setting) |
||||
.not_to have_received(:[]=) |
||||
end |
||||
end |
@ -0,0 +1,169 @@ |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2010-2022 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See COPYRIGHT and LICENSE files for more details. |
||||
|
||||
require 'spec_helper' |
||||
require_relative 'shared/shared_call_examples' |
||||
|
||||
describe Settings::WorkingDaysUpdateService do |
||||
let(:instance) do |
||||
described_class.new(user:) |
||||
end |
||||
let(:user) { build_stubbed(:user) } |
||||
let(:contract) do |
||||
instance_double(Settings::UpdateContract, |
||||
validate: contract_success, |
||||
errors: instance_double(ActiveModel::Error)) |
||||
end |
||||
let(:contract_success) { true } |
||||
let(:params_contract) do |
||||
instance_double(Settings::WorkingDaysParamsContract, |
||||
valid?: params_contract_success, |
||||
errors: instance_double(ActiveModel::Error)) |
||||
end |
||||
let(:params_contract_success) { true } |
||||
let(:setting_name) { :a_setting_name } |
||||
let(:new_setting_value) { 'a_new_setting_value' } |
||||
let(:previous_setting_value) { 'the_previous_setting_value' } |
||||
let(:setting_params) { { setting_name => new_setting_value } } |
||||
let(:non_working_days_params) { {} } |
||||
let(:params) { setting_params.merge(non_working_days: non_working_days_params) } |
||||
|
||||
before do |
||||
# stub a setting definition |
||||
allow(Setting) |
||||
.to receive(:[]) |
||||
.and_call_original |
||||
allow(Setting) |
||||
.to receive(:[]).with(setting_name) |
||||
.and_return(previous_setting_value) |
||||
allow(Setting) |
||||
.to receive(:[]=) |
||||
|
||||
# stub contract |
||||
allow(Settings::UpdateContract) |
||||
.to receive(:new) |
||||
.and_return(contract) |
||||
allow(Settings::WorkingDaysParamsContract) |
||||
.to receive(:new) |
||||
.and_return(params_contract) |
||||
end |
||||
|
||||
describe '#call' do |
||||
subject { instance.call(params) } |
||||
|
||||
shared_examples 'unsuccessful working days settings call' do |
||||
include_examples 'unsuccessful call' |
||||
|
||||
it 'does not persists the non working days' do |
||||
expect { subject }.not_to change(NonWorkingDay, :count) |
||||
end |
||||
end |
||||
|
||||
include_examples 'successful call' |
||||
|
||||
context 'when non working days are present' do |
||||
let!(:existing_nwd) { create(:non_working_day, name: 'Existing NWD') } |
||||
let!(:nwd_to_delete) { create(:non_working_day, name: 'NWD to delete') } |
||||
let(:non_working_days_params) do |
||||
[ |
||||
{ 'name' => 'Christmas Eve', 'date' => '2022-12-24' }, |
||||
{ 'name' => 'NYE', 'date' => '2022-12-31' }, |
||||
{ 'id' => existing_nwd.id }, |
||||
{ 'id' => nwd_to_delete.id, '_destroy' => true } |
||||
] |
||||
end |
||||
|
||||
include_examples 'successful call' |
||||
|
||||
it 'persists (create/delete) the non working days' do |
||||
expect { subject }.to change(NonWorkingDay, :count).by(1) |
||||
|
||||
expect { nwd_to_delete.reload }.to raise_error(ActiveRecord::RecordNotFound) |
||||
|
||||
expect(NonWorkingDay.all).to contain_exactly( |
||||
have_attributes(name: 'Christmas Eve', date: Date.parse('2022-12-24')), |
||||
have_attributes(name: 'NYE', date: Date.parse('2022-12-31')), |
||||
have_attributes(existing_nwd.slice(:id, :name, :date)) |
||||
) |
||||
end |
||||
|
||||
context 'when there are duplicates' do |
||||
context 'with both within the params' do |
||||
let(:non_working_days_params) do |
||||
[ |
||||
{ 'name' => 'Christmas Eve', 'date' => '2022-12-24' }, |
||||
{ 'name' => 'Christmas Eve', 'date' => '2022-12-24' } |
||||
] |
||||
end |
||||
|
||||
include_examples 'unsuccessful working days settings call' |
||||
end |
||||
|
||||
context 'with one saved in the database' do |
||||
let(:non_working_days_params) do |
||||
[existing_nwd.slice(:name, :date)] |
||||
end |
||||
|
||||
include_examples 'unsuccessful working days settings call' |
||||
|
||||
context 'when deleting and re-creating the duplicate non-working day' do |
||||
let(:non_working_days_params) do |
||||
[ |
||||
existing_nwd.slice(:id, :name, :date).merge('_destroy' => true), |
||||
existing_nwd.slice(:name, :date) |
||||
] |
||||
end |
||||
|
||||
include_examples 'successful call' |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
context 'when the params contract is not successfully validated' do |
||||
let(:params_contract_success) { false } |
||||
|
||||
include_examples 'unsuccessful working days settings call' |
||||
end |
||||
|
||||
context 'when the contract is not successfully validated' do |
||||
let(:contract_success) { false } |
||||
|
||||
include_examples 'unsuccessful working days settings call' |
||||
|
||||
context 'when non working days are present' do |
||||
let(:non_working_days_params) do |
||||
[ |
||||
{ 'name' => 'Christmas Eve', 'date' => '2022-12-24' }, |
||||
{ 'name' => 'NYE', 'date' => '2022-12-31' } |
||||
] |
||||
end |
||||
|
||||
include_examples 'unsuccessful working days settings call' |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue