From bb107a7223e1508e8bc4629892446a42ada714df Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:51:54 +0200 Subject: [PATCH] [#43819] Basic administration for non working days (excluding danger zone) https://community.openproject.org/work_packages/43819 WorkingDays as Setting --- .rubocop.yml | 4 +- .../settings/working_days_params_contract.rb | 47 ++++++++++ .../working_days_settings_controller.rb | 25 ++++++ app/controllers/admin/settings_controller.rb | 6 +- app/helpers/settings_helper.rb | 3 +- app/models/day.rb | 18 ++-- app/models/week_day.rb | 28 +++++- app/services/settings/update_service.rb | 22 +++-- .../work_packages/shared/working_days.rb | 12 +-- .../working_days_settings/show.html.erb | 55 ++++++++++++ config/constants/settings/definitions.rb | 5 ++ config/initializers/menus.rb | 6 ++ config/locales/en.yml | 12 ++- config/routes.rb | 21 ++--- db/migrate/20220909153412_drop_week_days.rb | 21 +++++ lib/api/v3/days/week_api.rb | 12 ++- lib/api/v3/days/week_day_representer.rb | 4 + .../spec/features/calendar_dates_spec.rb | 10 +-- .../spec/features/team_planner_dates_spec.rb | 10 +-- .../working_days_params_contract_spec.rb | 48 +++++++++++ .../work_packages/base_contract_spec.rb | 6 +- spec/factories/week_day_factory.rb | 37 +------- spec/features/admin/working_days_spec.rb | 86 +++++++++++++++++++ .../menu_items/admin_menu_item_spec.rb | 8 +- .../datepicker/datepicker_logic_spec.rb | 2 +- .../work_packages/details/workdays_spec.rb | 3 +- .../timeline/timeline_dates_spec.rb | 7 +- .../days/day_collection_representer_spec.rb | 1 - spec/lib/api/v3/days/day_representer_spec.rb | 2 +- .../week_day_collection_representer_spec.rb | 2 +- .../api/v3/days/week_day_representer_spec.rb | 6 +- spec/models/day_spec.rb | 22 ++--- spec/models/setting_spec.rb | 1 + spec/models/week_day_spec.rb | 8 +- spec/requests/api/v3/days/day_spec.rb | 2 +- .../api/v3/days/week_show_resource_spec.rb | 3 +- spec/requests/api/v3/days/week_spec.rb | 1 - .../set_attributes_service_spec.rb | 4 +- .../set_schedule_service_working_days_spec.rb | 8 +- .../shared/shared_examples_days.rb | 4 +- .../work_packages/shared/working_days_spec.rb | 6 +- spec/support/settings.rb | 78 +++++++++++++++++ .../chart_representer_spec.rb | 2 +- .../schedule_helpers/chart_spec.rb | 2 +- .../apply_working_days_change_job_spec.rb | 23 +---- 45 files changed, 527 insertions(+), 166 deletions(-) create mode 100644 app/contracts/settings/working_days_params_contract.rb create mode 100644 app/controllers/admin/settings/working_days_settings_controller.rb create mode 100644 app/views/admin/settings/working_days_settings/show.html.erb create mode 100644 db/migrate/20220909153412_drop_week_days.rb create mode 100644 spec/contracts/settings/working_days_params_contract_spec.rb create mode 100644 spec/features/admin/working_days_spec.rb create mode 100644 spec/support/settings.rb diff --git a/.rubocop.yml b/.rubocop.yml index 2c80d271f6..53cd5233dd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -79,7 +79,6 @@ Lint/Void: Lint/AmbiguousBlockAssociation: AllowedMethods: [change] - Metrics/ClassLength: Enabled: false @@ -282,6 +281,9 @@ Style/EvenOdd: Style/FormatString: Enabled: false +Style/FormatStringToken: + AllowedMethods: [redirect] + Style/GlobalVars: Enabled: false diff --git a/app/contracts/settings/working_days_params_contract.rb b/app/contracts/settings/working_days_params_contract.rb new file mode 100644 index 0000000000..635e08a795 --- /dev/null +++ b/app/contracts/settings/working_days_params_contract.rb @@ -0,0 +1,47 @@ +#-- 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. +#++ + +module Settings + class WorkingDaysParamsContract < ::ParamsContract + include RequiresAdminGuard + + validate :working_days_are_present + + protected + + def working_days_are_present + if working_days.empty? + errors.add :base, :working_days_are_missing + end + end + + def working_days + params[:working_days] + end + end +end diff --git a/app/controllers/admin/settings/working_days_settings_controller.rb b/app/controllers/admin/settings/working_days_settings_controller.rb new file mode 100644 index 0000000000..4a9842ced3 --- /dev/null +++ b/app/controllers/admin/settings/working_days_settings_controller.rb @@ -0,0 +1,25 @@ +module Admin::Settings + class WorkingDaysSettingsController < ::Admin::SettingsController + current_menu_item [:show] do + :working_days + end + + def default_breadcrumb + t(:label_working_days) + end + + def show_local_breadcrumb + true + end + + def settings_params + settings = super + settings[:working_days] = settings[:working_days].compact_blank.map(&:to_i).uniq + settings + end + + def contract_options + { params_contract: Settings::WorkingDaysParamsContract } + end + end +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 3b06d5d389..744648b0a7 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -51,7 +51,7 @@ module Admin return unless params[:settings] call = ::Settings::UpdateService - .new(user: current_user) + .new(user: current_user, contract_options:) .call(settings_params) call.on_success { flash[:notice] = t(:notice_successful_update) } @@ -93,5 +93,9 @@ module Admin def settings_params permitted_params.settings.to_h end + + def contract_options + {} + end end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index fe8d699ef8..9dcab181ee 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -76,8 +76,9 @@ module SettingsHelper end def setting_multiselect(setting, choices, options = {}) + direction = options.delete(:direction) || :vertical setting_label(setting, options) + - content_tag(:span, class: 'form--field-container -vertical') do + content_tag(:span, class: "form--field-container -#{direction}") do hidden = with_empty_unless_writable(setting) do hidden_field_tag("settings[#{setting}][]", '') end diff --git a/app/models/day.rb b/app/models/day.rb index 8dea0bd74b..c74a5bc3da 100644 --- a/app/models/day.rb +++ b/app/models/day.rb @@ -29,12 +29,6 @@ class Day < ApplicationRecord include Tableless - belongs_to :week_day, - inverse_of: false, - class_name: 'WeekDay', - foreign_key: :day_of_week, - primary_key: :day - has_many :non_working_days, inverse_of: false, class_name: 'NonWorkingDay', @@ -53,7 +47,6 @@ class Day < ApplicationRecord from = today.at_beginning_of_month to = today.next_month.at_end_of_month from_range(from:, to:) - .includes(:week_day) .includes(:non_working_days) .order("days.id") end @@ -68,16 +61,21 @@ class Day < ApplicationRecord to_char(dd, 'YYYYMMDD')::integer id, date_trunc('day', dd)::date date, extract(isodow from dd) day_of_week, - (COALESCE(week_days.working, TRUE) AND non_working_days.id IS NULL)::bool working + (COALESCE(POSITION(extract(isodow from dd)::text IN settings.value) > 0, TRUE) + AND non_working_days.id IS NULL)::bool working FROM generate_series( '#{from}'::timestamp, '#{to}'::timestamp, '1 day'::interval) dd - LEFT JOIN week_days - ON extract(isodow from dd) = week_days.day + LEFT JOIN settings + ON settings.name = 'working_days' LEFT JOIN non_working_days ON dd = non_working_days.date ) days SQL end + + def week_day + WeekDay.new(day: day_of_week) + end end diff --git a/app/models/week_day.rb b/app/models/week_day.rb index ebc0b43a60..03b4296efa 100644 --- a/app/models/week_day.rb +++ b/app/models/week_day.rb @@ -1,6 +1,32 @@ -class WeekDay < ApplicationRecord +class WeekDay + DAY_RANGE = Array(1..7) + + attr_accessor :day + + class << self + def find_by!(day:) + raise ActiveRecord::RecordNotFound, "Couldn't find WeekDay with day #{day}" unless day.in?(DAY_RANGE) + + new(day:) + end + + def all + DAY_RANGE.map do |day| + new(day:) + end + end + end + + def initialize(day:) + self.day = day + end + def name day_names = I18n.t('date.day_names') day_names[day % 7] end + + def working + Setting.working_days.empty? || day.in?(Setting.working_days) + end end diff --git a/app/services/settings/update_service.rb b/app/services/settings/update_service.rb index b317791b5a..8821cb0a4b 100644 --- a/app/services/settings/update_service.rb +++ b/app/services/settings/update_service.rb @@ -27,11 +27,23 @@ #++ class Settings::UpdateService < ::BaseServices::BaseContracted - def initialize(user:) + def initialize(user:, contract_options: {}) super user:, + contract_options:, contract_class: Settings::UpdateContract end + def validate_params(params) + if contract_options[:params_contract] + contract = contract_options[:params_contract].new(model, user, params:) + ServiceResult.new success: contract.valid?, + errors: contract.errors, + result: model + else + super + end + end + def after_validate(params, call) params.each do |name, value| Setting[name] = derive_value(value) @@ -44,11 +56,9 @@ class Settings::UpdateService < ::BaseServices::BaseContracted def derive_value(value) case value - when Array - # remove blank values in array settings - value.delete_if(&:blank?) - when Hash - value.delete_if { |_, v| v.blank? } + when Array, Hash + # remove blank values in array, hash settings + value.compact_blank! else value.strip end diff --git a/app/services/work_packages/shared/working_days.rb b/app/services/work_packages/shared/working_days.rb index 2d66b90d52..0d117083b3 100644 --- a/app/services/work_packages/shared/working_days.rb +++ b/app/services/work_packages/shared/working_days.rb @@ -146,13 +146,15 @@ module WorkPackages # To accomodate both versions 0-6, 1-7, an array of 8 elements is created # where array[0] = array[7] = value for Sunday # - # Because the database table for WeekDay could be empty or incomplete - # (like in tests), the initial array is built with all days considered - # working (value is `true`) + # Since Setting.working_days can be empty, the initial array is + # built with all days considered working (value is `true`) + @working_week_days = [true] * 8 - WeekDay.pluck(:day, :working).each do |day, working| - @working_week_days[day] = working + + WeekDay.all.each do |week_day| + @working_week_days[week_day.day] = week_day.working end + @working_week_days[0] = @working_week_days[7] # value for Sunday is present at index 0 AND index 7 @working_week_days end diff --git a/app/views/admin/settings/working_days_settings/show.html.erb b/app/views/admin/settings/working_days_settings/show.html.erb new file mode 100644 index 0000000000..49d3a62133 --- /dev/null +++ b/app/views/admin/settings/working_days_settings/show.html.erb @@ -0,0 +1,55 @@ +<%#-- 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. + +++#%> + +<% content_for :header_tags do %> + +<% end %> + +<%= toolbar title: t(:label_working_days) %> + +
+
+

<%= t("working_days.warning") %>

+
+
+ +<%= styled_form_tag(admin_settings_working_days_path, method: :patch) do %> +
+

+ <%= t("working_days.info").html_safe %> +

+
+ <%= setting_multiselect :working_days, + I18n.t('date.day_names').rotate.zip(WeekDay::DAY_RANGE), + direction: :horizontal, + label: false %> +
+
+ <%= styled_button_tag t(:button_save), class: '-highlight -with-icon icon-checkmark' %> +<% end %> diff --git a/config/constants/settings/definitions.rb b/config/constants/settings/definitions.rb index 938f093621..a649153815 100644 --- a/config/constants/settings/definitions.rb +++ b/config/constants/settings/definitions.rb @@ -986,6 +986,11 @@ Settings::Definition.define do add :work_package_startdate_is_adddate, default: false + add :working_days, + format: :array, + allowed: Array(1..7), + default: Array(1..5) # Sat, Sun being non-working days + add :youtube_channel, default: 'https://www.youtube.com/c/OpenProjectCommunity', writable: false diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 61d2e36cf3..3eb4b6e9bf 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -276,6 +276,12 @@ Redmine::MenuManager.map :admin_menu do |menu| if: Proc.new { User.current.admin? }, icon: 'icon2 icon-enumerations' + menu.push :working_days, + { controller: '/admin/settings/working_days_settings', action: :show }, + if: Proc.new { User.current.admin? }, + caption: :label_working_days, + icon: 'icon2 icon-calendar' + menu.push :settings, { controller: '/admin/settings/general_settings', action: :show }, if: Proc.new { User.current.admin? }, diff --git a/config/locales/en.yml b/config/locales/en.yml index 665e82d665..1bf11c6cd8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -793,6 +793,10 @@ en: attributes: permissions: dependency_missing: "need to also include '%{dependency}' as '%{permission}' is selected." + setting: + attributes: + base: + working_days_are_missing: 'At least one working day needs to be specified.' time_entry: attributes: hours: @@ -2007,6 +2011,7 @@ en: label_workflow: "Workflow" label_workflow_plural: "Workflows" label_workflow_summary: "Summary" + label_working_days: "Working days" label_x_closed_work_packages_abbr: one: "1 closed" other: "%{count} closed" @@ -2668,7 +2673,6 @@ en: status: "Entire row by Status" type: "Entire row by Type" priority: "Entire row by Priority" - text_formatting: markdown: 'Markdown' plain: 'Plain text' @@ -3099,6 +3103,12 @@ en: info: "Deleting the work package is an irreversible action." title: "Delete the work package" + working_days: + info: > + Define days considered part of the work week
+ Days that are not selected are skipped when scheduling work packages (and not included in the day count). These can be overriden at a work-package level. + warning: > + Changing which days of the week are considered working days can affect the start and finish days of all work packages in all projects in this instance. nothing_to_preview: "Nothing to preview" api_v3: diff --git a/config/routes.rb b/config/routes.rb index 6d94943e17..044f8ea5d2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,7 +71,7 @@ OpenProject::Application.routes.draw do # forward requests to the proxy if FrontendAssetHelper.assets_proxied? match '/assets/frontend/*appendix', - to: redirect(FrontendAssetHelper.cli_proxy + "/assets/frontend/%{appendix}", status: 307), + to: redirect("#{FrontendAssetHelper.cli_proxy}/assets/frontend/%{appendix}", status: 307), format: false, via: :all end @@ -143,10 +143,7 @@ OpenProject::Application.routes.draw do resources :custom_fields, except: :show do member do - match "options/:option_id", - to: "custom_fields#delete_option", - via: :delete, - as: :delete_option_of + delete "options/:option_id", to: "custom_fields#delete_option", as: :delete_option_of post :reorder_alphabetical end @@ -192,7 +189,7 @@ OpenProject::Application.routes.draw do end member do - get "settings", to: redirect('projects/%{id}/settings/general/') # rubocop:disable Style/FormatStringToken + get "settings", to: redirect('projects/%{id}/settings/general/') get :copy @@ -278,7 +275,7 @@ OpenProject::Application.routes.draw do resources :members, only: %i[index create update destroy], shallow: true do collection do - match :autocomplete_for_member, via: %i[get] + get :autocomplete_for_member end end @@ -306,7 +303,7 @@ OpenProject::Application.routes.draw do %w{diff annotate changes entry browse}.each do |action| get "(/revisions/:rev)/#{action}(/*repo_path)", format: 'html', - action: action, + action:, constraints: { rev: /[\w0-9.\-_]+/, repo_path: /.*/ }, as: "#{action}_revision" end @@ -401,6 +398,7 @@ OpenProject::Application.routes.draw do resource :mail_notifications, controller: '/admin/settings/mail_notifications_settings', only: %i[show update] resource :api, controller: '/admin/settings/api_settings', only: %i[show update] resource :work_packages, controller: '/admin/settings/work_packages_settings', only: %i[show update] + resource :working_days, controller: '/admin/settings/working_days_settings', only: %i[show update] resource :users, controller: '/admin/settings/users_settings', only: %i[show update] # Redirect /settings to general settings @@ -468,7 +466,7 @@ OpenProject::Application.routes.draw do member do get '/edit(/:tab)' => 'users#edit', as: 'edit' - match '/change_status/:change_action' => 'users#change_status_info', via: :get, as: 'change_status_info' + get '/change_status/:change_action' => 'users#change_status_info', as: 'change_status_info' post :change_status post :resend_invitation get :deletion_info @@ -535,9 +533,8 @@ OpenProject::Application.routes.draw do # alternate routes for the current user scope 'my' do - match '/deletion_info' => 'users#deletion_info', via: :get, as: 'delete_my_account_info' - match '/oauth/revoke_application/:application_id' => 'oauth/grants#revoke_application', via: :post, - as: 'revoke_my_oauth_application' + get '/deletion_info' => 'users#deletion_info', as: 'delete_my_account_info' + post '/oauth/revoke_application/:application_id' => 'oauth/grants#revoke_application', as: 'revoke_my_oauth_application' end scope controller: 'my' do diff --git a/db/migrate/20220909153412_drop_week_days.rb b/db/migrate/20220909153412_drop_week_days.rb new file mode 100644 index 0000000000..e7602fd149 --- /dev/null +++ b/db/migrate/20220909153412_drop_week_days.rb @@ -0,0 +1,21 @@ +class DropWeekDays < ActiveRecord::Migration[7.0] + def up + drop_table :week_days + end + + def down + create_table :week_days do |t| + t.integer :day, null: false + t.boolean :working, null: false, default: true + + t.timestamps + end + + execute <<-SQL.squish + ALTER TABLE week_days + ADD CONSTRAINT unique_day_number UNIQUE (day); + ALTER TABLE week_days + ADD CHECK (day >= 1 AND day <=7); + SQL + end +end diff --git a/lib/api/v3/days/week_api.rb b/lib/api/v3/days/week_api.rb index 777dad4f91..2e4e14b383 100644 --- a/lib/api/v3/days/week_api.rb +++ b/lib/api/v3/days/week_api.rb @@ -31,10 +31,14 @@ module API::V3::Days helpers ::API::Utilities::UrlPropsParsingHelper resources :week do - get &::API::V3::Utilities::Endpoints::Index.new(model: WeekDay, - render_representer: WeekDayCollectionRepresenter, - self_path: -> { api_v3_paths.days_week }) - .mount + get do + self_link = api_v3_paths.days_week + week_days = WeekDay.all + WeekDayCollectionRepresenter.new(week_days, + self_link:, + current_user:) + end + route_param :day, type: Integer, desc: 'WeekDay ID' do after_validation do @week_day = WeekDay.find_by!(day: declared_params[:day]) diff --git a/lib/api/v3/days/week_day_representer.rb b/lib/api/v3/days/week_day_representer.rb index a1ec01d0ef..f6119b3981 100644 --- a/lib/api/v3/days/week_day_representer.rb +++ b/lib/api/v3/days/week_day_representer.rb @@ -39,5 +39,9 @@ module API::V3::Days def _type 'WeekDay' end + + def json_key_part_represented + [represented.day, Setting.working_days] + end end end diff --git a/modules/calendar/spec/features/calendar_dates_spec.rb b/modules/calendar/spec/features/calendar_dates_spec.rb index bd635fb3ab..e217ce6b31 100644 --- a/modules/calendar/spec/features/calendar_dates_spec.rb +++ b/modules/calendar/spec/features/calendar_dates_spec.rb @@ -50,7 +50,7 @@ describe 'Calendar non working days', type: :feature, js: true do end context 'with week days defined' do - let!(:week_days) { create :week_days } + let(:week_days) { week_with_saturday_and_sunday_as_weekend } it 'renders sat and sun as non working' do expect(page).to have_selector('.fc-day-sat.fc-non-working-day', minimum: 1, wait: 10) @@ -76,13 +76,7 @@ describe 'Calendar non working days', type: :feature, js: true do end context 'with all days marked as weekend' do - let!(:week_days) do - days = create(:week_with_saturday_and_sunday_as_weekend) - - WeekDay.update_all(working: false) - - days - end + let(:week_days) { week_with_no_working_days } it 'renders all as non working' do expect(page).to have_selector('.fc-day-sat.fc-non-working-day', minimum: 1, wait: 10) diff --git a/modules/team_planner/spec/features/team_planner_dates_spec.rb b/modules/team_planner/spec/features/team_planner_dates_spec.rb index 88ead683b6..a1508180b3 100644 --- a/modules/team_planner/spec/features/team_planner_dates_spec.rb +++ b/modules/team_planner/spec/features/team_planner_dates_spec.rb @@ -37,7 +37,7 @@ describe 'Team planner working days', type: :feature, js: true do include_context 'with team planner full access' context 'with week days defined' do - let!(:week_days) { create :week_days } + let!(:week_days) { week_with_saturday_and_sunday_as_weekend } it 'renders sat and sun as non working' do team_planner.visit! @@ -72,13 +72,7 @@ describe 'Team planner working days', type: :feature, js: true do end context 'with all days marked as weekend' do - let!(:week_days) do - days = create(:week_with_saturday_and_sunday_as_weekend) - - WeekDay.update_all(working: false) - - days - end + let!(:week_days) { week_with_no_working_days } it 'renders all as non working' do team_planner.visit! diff --git a/spec/contracts/settings/working_days_params_contract_spec.rb b/spec/contracts/settings/working_days_params_contract_spec.rb new file mode 100644 index 0000000000..585bcb6466 --- /dev/null +++ b/spec/contracts/settings/working_days_params_contract_spec.rb @@ -0,0 +1,48 @@ +#-- 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' +require 'contracts/shared/model_contract_shared_context' + +describe Settings::WorkingDaysParamsContract do + include_context 'ModelContract shared context' + let(:setting) { Setting } + let(:current_user) { build_stubbed(:admin) } + let(:params) { { working_days: [1] } } + let(:contract) do + described_class.new(setting, current_user, params:) + end + + it_behaves_like 'contract is valid for active admins and invalid for regular users' + + context 'without working days' do + let(:params) { { working_days: [] } } + + include_examples 'contract is invalid', base: :working_days_are_missing + end +end diff --git a/spec/contracts/work_packages/base_contract_spec.rb b/spec/contracts/work_packages/base_contract_spec.rb index 939c900f70..b90b76f005 100644 --- a/spec/contracts/work_packages/base_contract_spec.rb +++ b/spec/contracts/work_packages/base_contract_spec.rb @@ -600,7 +600,7 @@ describe WorkPackages::BaseContract do context 'when setting duration and dates while covering non-working days' do before do - create(:week_with_saturday_and_sunday_as_weekend) + week_with_saturday_and_sunday_as_weekend work_package.ignore_non_working_days = false work_package.duration = 6 work_package.start_date = "2022-08-22" @@ -623,7 +623,7 @@ describe WorkPackages::BaseContract do context 'when setting duration and dates while covering non-working days and duration is too small' do before do - create(:week_with_saturday_and_sunday_as_weekend) + week_with_saturday_and_sunday_as_weekend work_package.ignore_non_working_days = false work_package.duration = 1 work_package.start_date = "2022-08-22" @@ -646,7 +646,7 @@ describe WorkPackages::BaseContract do context 'when setting duration and dates while covering non-working days and duration is too big' do before do - create(:week_with_saturday_and_sunday_as_weekend) + week_with_saturday_and_sunday_as_weekend work_package.ignore_non_working_days = false work_package.duration = 99 work_package.start_date = "2022-08-22" diff --git a/spec/factories/week_day_factory.rb b/spec/factories/week_day_factory.rb index 875eb78de8..a909209cba 100644 --- a/spec/factories/week_day_factory.rb +++ b/spec/factories/week_day_factory.rb @@ -28,45 +28,14 @@ FactoryBot.define do factory :week_day do - sequence :day, [1, 2, 3, 4, 5, 6, 7].cycle - working { day < 6 } - - # hack to reuse the day if it already exists in database - to_create do |instance| - instance.attributes = WeekDay.find_or_create_by(instance.attributes.slice("day", "working")).attributes - instance.instance_variable_set('@new_record', false) - end - - trait :tuesday do - day { 2 } - end - end - - # Factory to create all 7 week days at once, Saturday and Sunday being weekend days - factory :week_with_saturday_and_sunday_as_weekend, aliases: [:week_days], parent: :week do - working_days { %w[monday tuesday wednesday thursday friday] } - end - - # Factory to create all 7 week days at once - # - # use +working: ['monday', 'tuesday', ...]+ to define which days of the week - # will be working days. By default, all days are working days. - factory :week, class: 'Array' do - transient do - working_days { %w[monday tuesday wednesday thursday friday saturday sunday] } - end - # Skip the create callback to be able to use non-AR models. Otherwise FactoryBot will # try to call #save! on any created object. skip_create + sequence :day, [1, 2, 3, 4, 5, 6, 7].cycle + initialize_with do - %w[monday tuesday wednesday thursday friday saturday sunday] - .map.with_index do |day_name, i| - day = i + 1 - working = working_days.include?(day_name) - create(:week_day, day:, working:) - end + new(day:) end end end diff --git a/spec/features/admin/working_days_spec.rb b/spec/features/admin/working_days_spec.rb new file mode 100644 index 0000000000..4e12616ad7 --- /dev/null +++ b/spec/features/admin/working_days_spec.rb @@ -0,0 +1,86 @@ +#-- 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 'Working Days', type: :feature do + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } + shared_let(:admin) { create :admin } + + before do + login_as(admin) + visit admin_settings_working_days_path + end + + it 'contains all defined days from the settings' do + WeekDay.all.each do |day| + expect(page).to have_selector('label', text: day.name) + if day.working + expect(page).to have_checked_field day.name + end + end + end + + it 'updates the values and saves the settings' do + expect(Setting.working_days).to eq([1, 2, 3, 4, 5]) + uncheck 'Monday' + uncheck 'Friday' + click_on 'Save' + + expect(page).to have_selector('.flash.notice', text: 'Successful update.') + expect(page).to have_unchecked_field 'Monday' + expect(page).to have_unchecked_field 'Friday' + expect(page).to have_unchecked_field 'Saturday' + expect(page).to have_unchecked_field 'Sunday' + expect(page).to have_checked_field 'Tuesday' + expect(page).to have_checked_field 'Wednesday' + expect(page).to have_checked_field 'Thursday' + expect(Setting.working_days).to eq([2, 3, 4]) + end + + it 'shows error when no working days are set' do + uncheck 'Monday' + uncheck 'Tuesday' + uncheck 'Wednesday' + uncheck 'Thursday' + uncheck 'Friday' + + click_on 'Save' + + expect(page).to have_selector('.flash.error', text: 'At least one working day needs to be specified.') + # Restore the checkboxes to their valid state + expect(page).to have_checked_field 'Monday' + expect(page).to have_checked_field 'Tuesday' + expect(page).to have_checked_field 'Wednesday' + expect(page).to have_checked_field 'Thursday' + expect(page).to have_checked_field 'Friday' + expect(page).to have_unchecked_field 'Saturday' + expect(page).to have_unchecked_field 'Sunday' + expect(Setting.working_days).to eq([1, 2, 3, 4, 5]) + end +end diff --git a/spec/features/menu_items/admin_menu_item_spec.rb b/spec/features/menu_items/admin_menu_item_spec.rb index 3e45e69c45..e63ae075f1 100644 --- a/spec/features/menu_items/admin_menu_item_spec.rb +++ b/spec/features/menu_items/admin_menu_item_spec.rb @@ -44,8 +44,8 @@ describe 'Admin menu items', js: true do visit admin_index_path expect(page).to have_selector('[data-qa-selector="menu-blocks--container"]') - expect(page).to have_selector('[data-qa-selector="menu-block"]', count: 19) - expect(page).to have_selector('[data-qa-selector="op-menu--item-action"]', count: 20) # All plus 'overview' + expect(page).to have_selector('[data-qa-selector="menu-block"]', count: 20) + expect(page).to have_selector('[data-qa-selector="op-menu--item-action"]', count: 21) # All plus 'overview' end end @@ -57,10 +57,10 @@ describe 'Admin menu items', js: true do visit admin_index_path expect(page).to have_selector('[data-qa-selector="menu-blocks--container"]') - expect(page).to have_selector('[data-qa-selector="menu-block"]', count: 18) + expect(page).to have_selector('[data-qa-selector="menu-block"]', count: 19) expect(page).not_to have_selector('[data-qa-selector="menu-block"]', text: I18n.t('timelines.admin_menu.colors')) - expect(page).to have_selector('[data-qa-selector="op-menu--item-action"]', count: 19) # All plus 'overview' + expect(page).to have_selector('[data-qa-selector="op-menu--item-action"]', count: 20) # All plus 'overview' expect(page).not_to have_selector('[data-qa-selector="op-menu--item-action"]', text: I18n.t('timelines.admin_menu.colors')) end end diff --git a/spec/features/work_packages/datepicker/datepicker_logic_spec.rb b/spec/features/work_packages/datepicker/datepicker_logic_spec.rb index 1901d7a8e8..3b68e1e270 100644 --- a/spec/features/work_packages/datepicker/datepicker_logic_spec.rb +++ b/spec/features/work_packages/datepicker/datepicker_logic_spec.rb @@ -42,7 +42,7 @@ describe 'Datepicker modal logic test cases (WP #43539)', shared_let(:milestone_wp) { create :work_package, project:, type: type_milestone } # assume sat+sun are non working days - shared_let(:weekdays) { create :week_days } + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } let(:work_packages_page) { Pages::FullWorkPackage.new(work_package, project) } let(:wp_table) { Pages::WorkPackagesTable.new(project) } diff --git a/spec/features/work_packages/details/workdays_spec.rb b/spec/features/work_packages/details/workdays_spec.rb index 8bda951ac1..a5bcddaf1e 100644 --- a/spec/features/work_packages/details/workdays_spec.rb +++ b/spec/features/work_packages/details/workdays_spec.rb @@ -44,7 +44,6 @@ describe 'Work packages datepicker workdays', let(:combined_date) { work_packages_page.edit_field(:combinedDate) } before do - week_days login_as(user) work_packages_page.visit! @@ -55,7 +54,7 @@ describe 'Work packages datepicker workdays', end context 'with default work days' do - let(:week_days) { create :week_days } + shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend } it 'shows them as disabled' do expect(page).to have_selector('.dayContainer', count: 2) diff --git a/spec/features/work_packages/timeline/timeline_dates_spec.rb b/spec/features/work_packages/timeline/timeline_dates_spec.rb index 7d33502d70..803c130476 100644 --- a/spec/features/work_packages/timeline/timeline_dates_spec.rb +++ b/spec/features/work_packages/timeline/timeline_dates_spec.rb @@ -65,7 +65,6 @@ RSpec.describe 'Work package timeline date formatting', subject: 'Work Package ignoring non working days' end - let(:week_days) { nil } let(:wp_timeline) { Pages::WorkPackagesTimeline.new(project) } let!(:query_tl) do query = build(:query, user: current_user, project:) @@ -88,7 +87,6 @@ RSpec.describe 'Work package timeline date formatting', end before do - week_days login_as current_user wp_timeline.visit_query query_tl @@ -125,7 +123,8 @@ RSpec.describe 'Work package timeline date formatting', context 'with weekdays defined' do let(:current_user) { create :admin, language: 'en' } - let(:week_days) { create :week_days } + + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } it 'shows them as disabled' do expect_date_week work_package.start_date.iso8601, '01' @@ -160,8 +159,8 @@ RSpec.describe 'Work package timeline date formatting', end describe 'setting dates' do + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } let(:current_user) { create :admin } - let(:week_days) { create :week_days } let(:row) { wp_timeline.timeline_row work_package_with_non_working_days.id } shared_examples "sets dates, duration and displays bar" do diff --git a/spec/lib/api/v3/days/day_collection_representer_spec.rb b/spec/lib/api/v3/days/day_collection_representer_spec.rb index 38bface9f4..22773be52c 100644 --- a/spec/lib/api/v3/days/day_collection_representer_spec.rb +++ b/spec/lib/api/v3/days/day_collection_representer_spec.rb @@ -29,7 +29,6 @@ require 'spec_helper' describe ::API::V3::Days::DayCollectionRepresenter do - let!(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } let(:days) do [ build(:day, date: Date.new(2022, 12, 27)), diff --git a/spec/lib/api/v3/days/day_representer_spec.rb b/spec/lib/api/v3/days/day_representer_spec.rb index d29f78d0d4..df381d4391 100644 --- a/spec/lib/api/v3/days/day_representer_spec.rb +++ b/spec/lib/api/v3/days/day_representer_spec.rb @@ -41,7 +41,7 @@ describe ::API::V3::Days::DayRepresenter do subject(:generated) { representer.to_json } before do - create(:week_day, :tuesday, working:) + set_week_days('tuesday', working:) end it 'has _type: Day' do diff --git a/spec/lib/api/v3/days/week_day_collection_representer_spec.rb b/spec/lib/api/v3/days/week_day_collection_representer_spec.rb index af716f91db..d215623599 100644 --- a/spec/lib/api/v3/days/week_day_collection_representer_spec.rb +++ b/spec/lib/api/v3/days/week_day_collection_representer_spec.rb @@ -29,7 +29,7 @@ require 'spec_helper' describe ::API::V3::Days::WeekDayCollectionRepresenter do - let(:week_days) { build(:week_days) } + let(:week_days) { WeekDay.all } let(:representer) do described_class.new(week_days, self_link: '/api/v3/days/week', diff --git a/spec/lib/api/v3/days/week_day_representer_spec.rb b/spec/lib/api/v3/days/week_day_representer_spec.rb index 82e955f606..5aeb4448f7 100644 --- a/spec/lib/api/v3/days/week_day_representer_spec.rb +++ b/spec/lib/api/v3/days/week_day_representer_spec.rb @@ -29,7 +29,7 @@ require 'spec_helper' describe ::API::V3::Days::WeekDayRepresenter do - let(:week_day) { build_stubbed(:week_day, day: 1) } + let(:week_day) { build(:week_day, day: 1) } let(:representer) { described_class.new(week_day, current_user: instance_double(User, name: 'current_user')) } describe '#to_json' do @@ -99,8 +99,8 @@ describe ::API::V3::Days::WeekDayRepresenter do end end - it 'changes when the week_day is updated' do - week_day.updated_at = 20.seconds.from_now + it 'changes when the Setting is updated' do + set_week_days('tuesday') expect(representer.json_cache_key) .not_to eql former_cache_key diff --git a/spec/models/day_spec.rb b/spec/models/day_spec.rb index b180528bfb..2eaad53273 100644 --- a/spec/models/day_spec.rb +++ b/spec/models/day_spec.rb @@ -23,8 +23,8 @@ describe Day, type: :model do ) end - it 'eager loads week_day relation' do - expect(days).to(be_all { |d| d.association(:week_day).loaded? }) + it 'loads week_day method' do + expect(days).to(be_all { |d| d.week_day.present? }) end it 'eager loads non_working_days relation' do @@ -43,16 +43,16 @@ describe Day, type: :model do expect(days.first.day_of_week % 7).to eq(today.at_beginning_of_month.wday) # wday is from 0-6 end - it 'does not have a name' do - expect(days.first.name).to be_nil + it 'loads the name attribute' do + expect(days.first.name).to eq(today.at_beginning_of_month.strftime("%A")) end end context 'for collection with multiple non-working days' do + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } let(:non_working_dates) { [date_range.begin, date_range.begin + 1.day] } before do - create(:week_with_saturday_and_sunday_as_weekend) non_working_dates.each { |date| create(:non_working_day, date:) } end @@ -78,10 +78,6 @@ describe Day, type: :model do end context 'with the weekday present' do - before do - create(:week_day, day: 6) - end - it 'loads the name attribute' do expect(subject.name).to eq('Saturday') end @@ -89,9 +85,7 @@ describe Day, type: :model do describe '#working' do context 'when the week day is non-working' do - before do - create(:week_day, day: 6, working: false) - end + shared_let(:working_days) { week_with_no_working_days } it 'is false' do expect(subject.working).to be_falsy @@ -109,9 +103,7 @@ describe Day, type: :model do end context 'when the week day is working' do - before do - create(:week_day, day: 6, working: true) - end + shared_let(:working_days) { reset_working_week_days('saturday') } it 'is true' do expect(subject.working).to be_truthy diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index f1d6bfedae..be95b643ae 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -31,6 +31,7 @@ require 'spec_helper' describe Setting, type: :model do before do described_class.clear_cache + described_class.destroy_all end after do diff --git a/spec/models/week_day_spec.rb b/spec/models/week_day_spec.rb index a239501265..1c2187441f 100644 --- a/spec/models/week_day_spec.rb +++ b/spec/models/week_day_spec.rb @@ -3,11 +3,11 @@ require 'rails_helper' RSpec.describe WeekDay, type: :model do describe '#name' do it 'returns the translated week day name' do - expect(described_class.create(day: 1).name).to eq('Monday') - expect(described_class.create(day: 7).name).to eq('Sunday') + expect(described_class.new(day: 1).name).to eq('Monday') + expect(described_class.new(day: 7).name).to eq('Sunday') I18n.with_locale(:de) do - expect(described_class.create(day: 3).name).to eq('Mittwoch') - expect(described_class.create(day: 4).name).to eq('Donnerstag') + expect(described_class.new(day: 3).name).to eq('Mittwoch') + expect(described_class.new(day: 4).name).to eq('Donnerstag') end end end diff --git a/spec/requests/api/v3/days/day_spec.rb b/spec/requests/api/v3/days/day_spec.rb index 4b03a682d7..ed4deb247b 100644 --- a/spec/requests/api/v3/days/day_spec.rb +++ b/spec/requests/api/v3/days/day_spec.rb @@ -32,13 +32,13 @@ describe ::API::V3::Days::DaysAPI, type: :request do include API::V3::Utilities::PathHelper + shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend } let(:parsed_response) { JSON.parse(last_response.body) } let(:filters) { [] } current_user { user } before do - create(:week_with_saturday_and_sunday_as_weekend) get api_v3_paths.path_for :days, filters: end diff --git a/spec/requests/api/v3/days/week_show_resource_spec.rb b/spec/requests/api/v3/days/week_show_resource_spec.rb index dffb12dc1a..b9deb5a1a5 100644 --- a/spec/requests/api/v3/days/week_show_resource_spec.rb +++ b/spec/requests/api/v3/days/week_show_resource_spec.rb @@ -33,8 +33,7 @@ describe ::API::V3::Days::WeekAPI, type: :request do include API::V3::Utilities::PathHelper - let(:week_day) { create(:week_day, day: 1) } - let(:path) { api_v3_paths.days_week_day(week_day.day) } + let(:path) { api_v3_paths.days_week_day(1) } current_user { user } subject { last_response.body } diff --git a/spec/requests/api/v3/days/week_spec.rb b/spec/requests/api/v3/days/week_spec.rb index 6ac5b14515..38b80d61ce 100644 --- a/spec/requests/api/v3/days/week_spec.rb +++ b/spec/requests/api/v3/days/week_spec.rb @@ -38,7 +38,6 @@ describe ::API::V3::Days::WeekAPI, current_user { user } before do - create(:week_with_saturday_and_sunday_as_weekend) get api_v3_paths.days_week end diff --git a/spec/services/work_packages/set_attributes_service_spec.rb b/spec/services/work_packages/set_attributes_service_spec.rb index 7e8f0fe6bc..c408e07d6c 100644 --- a/spec/services/work_packages/set_attributes_service_spec.rb +++ b/spec/services/work_packages/set_attributes_service_spec.rb @@ -1039,7 +1039,7 @@ describe WorkPackages::SetAttributesService, end context 'with non-working days' do - shared_let(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } + shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend } let(:monday) { Time.zone.today.beginning_of_week } let(:tuesday) { monday + 1.day } let(:wednesday) { monday + 2.days } @@ -1643,12 +1643,12 @@ describe WorkPackages::SetAttributesService, end context 'when the soonest start date is a non-working day' do + shared_let(:working_days) { week_with_saturday_and_sunday_as_weekend } let(:saturday) { Time.zone.today.beginning_of_week.next_occurring(:saturday) } let(:next_monday) { saturday.next_occurring(:monday) } let(:soonest_start) { saturday } before do - create(:week_with_saturday_and_sunday_as_weekend) work_package.ignore_non_working_days = false end diff --git a/spec/services/work_packages/set_schedule_service_working_days_spec.rb b/spec/services/work_packages/set_schedule_service_working_days_spec.rb index 87e9250e7b..8d42075d5b 100644 --- a/spec/services/work_packages/set_schedule_service_working_days_spec.rb +++ b/spec/services/work_packages/set_schedule_service_working_days_spec.rb @@ -31,7 +31,7 @@ require 'spec_helper' describe WorkPackages::SetScheduleService, 'working days' do create_shared_association_defaults_for_work_package_factory - shared_let(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } let(:instance) do described_class.new(user:, work_package:) @@ -1023,10 +1023,10 @@ describe WorkPackages::SetScheduleService, 'working days' do end def set_non_working_week_days(*days) - days.each do |day| - wday = %w[xxx monday tuesday wednesday thursday friday saturday sunday].index(day.downcase) - WeekDay.find_by!(day: wday).update(working: false) + non_working_days = days.map do |day| + %w[xxx monday tuesday wednesday thursday friday saturday sunday].index(day.downcase) end + Setting.working_days -= non_working_days end context 'when moving forward due to days and predecessor due date now being non-working days' do diff --git a/spec/services/work_packages/shared/shared_examples_days.rb b/spec/services/work_packages/shared/shared_examples_days.rb index 2328121258..2ccd31d652 100644 --- a/spec/services/work_packages/shared/shared_examples_days.rb +++ b/spec/services/work_packages/shared/shared_examples_days.rb @@ -29,7 +29,7 @@ Date::DATE_FORMATS[:wday_date] = '%a %-d %b %Y' # Fri 5 Aug 2022 RSpec.shared_context 'with weekend days Saturday and Sunday' do - shared_let(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } end RSpec.shared_context 'with non working days Christmas 2022 and new year 2023' do @@ -41,7 +41,7 @@ RSpec.shared_context 'with no working days' do include_context 'with weekend days Saturday and Sunday' before do - WeekDay.update_all(working: false) + week_with_no_working_days end end diff --git a/spec/services/work_packages/shared/working_days_spec.rb b/spec/services/work_packages/shared/working_days_spec.rb index ef49a86969..c3c99b7db8 100644 --- a/spec/services/work_packages/shared/working_days_spec.rb +++ b/spec/services/work_packages/shared/working_days_spec.rb @@ -181,7 +181,8 @@ RSpec.describe WorkPackages::Shared::WorkingDays do describe '#add_days' do it 'when positive, adds the number of working days to the date, ignoring non-working days' do - create(:week_day, day: 5, working: false) + # Friday is a non working week day + set_non_working_week_days('friday') create(:non_working_day, date: wednesday_2022_08_03) # Wednesday is skipped (non working day) @@ -195,7 +196,8 @@ RSpec.describe WorkPackages::Shared::WorkingDays do end it 'when negative, removes the number of working days to the date, ignoring non-working days' do - create(:week_day, day: 5, working: false) + # Friday is a non working week day + set_non_working_week_days('friday') create(:non_working_day, date: sunday_2022_07_31) # Sunday is skipped (non working day) diff --git a/spec/support/settings.rb b/spec/support/settings.rb new file mode 100644 index 0000000000..8552938928 --- /dev/null +++ b/spec/support/settings.rb @@ -0,0 +1,78 @@ +#-- 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. +#++ + +def week_with_saturday_and_sunday_as_weekend + Setting.working_days = Array(1..5) +end + +def week_with_all_days_working + Setting.working_days = Array(1..7) +end + +def week_with_no_working_days + # This a hack to make all days non-working, + # because we don't allow that by definition + Setting.working_days = [false] +end + +def set_non_working_week_days(*days) + week_days = get_week_days(*days) + Setting.working_days -= week_days +end + +def set_working_week_days(*days) + week_days = get_week_days(*days) + Setting.working_days += week_days +end + +def set_week_days(*days, working: true) + if working + set_working_week_days(*days) + else + set_non_working_week_days(*days) + end +end + +def reset_working_week_days(*days) + week_days = get_week_days(*days) + Setting.working_days = week_days +end + +def get_week_days(*days) + days.map do |day| + %w[xxx monday tuesday wednesday thursday friday saturday sunday].index(day.downcase) + end +end + +RSpec.configure do |config| + config.before(:suite) do + # The test suite assumes the default of all days working. + # Since the Setting default is with Sat-Sun non-working, we update it before the tests. + week_with_all_days_working + end +end diff --git a/spec/support_spec/schedule_helpers/chart_representer_spec.rb b/spec/support_spec/schedule_helpers/chart_representer_spec.rb index 0c858211db..a5dc73dd53 100644 --- a/spec/support_spec/schedule_helpers/chart_representer_spec.rb +++ b/spec/support_spec/schedule_helpers/chart_representer_spec.rb @@ -39,7 +39,7 @@ describe ScheduleHelpers::ChartRepresenter do let(:sunday) { Date.new(2022, 6, 26) } describe '#normalized_to_s' do - let!(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } context 'when both charts have different work packages items and/or order' do def to_first_columns(charts) diff --git a/spec/support_spec/schedule_helpers/chart_spec.rb b/spec/support_spec/schedule_helpers/chart_spec.rb index 0043e44326..3b66ca4b73 100644 --- a/spec/support_spec/schedule_helpers/chart_spec.rb +++ b/spec/support_spec/schedule_helpers/chart_spec.rb @@ -157,7 +157,7 @@ describe ScheduleHelpers::Chart do end describe '#to_s' do - let!(:week_days) { create(:week_with_saturday_and_sunday_as_weekend) } + shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } context 'with a chart built from ascii representation' do let(:chart) do diff --git a/spec/workers/work_packages/apply_working_days_change_job_spec.rb b/spec/workers/work_packages/apply_working_days_change_job_spec.rb index b749ba6f6e..df255d5f08 100644 --- a/spec/workers/work_packages/apply_working_days_change_job_spec.rb +++ b/spec/workers/work_packages/apply_working_days_change_job_spec.rb @@ -33,22 +33,7 @@ RSpec.describe WorkPackages::ApplyWorkingDaysChangeJob do shared_let(:user) { create(:user) } - let!(:week) { create(:week_with_saturday_and_sunday_as_weekend) } - - def set_non_working_week_days(*days) - set_week_days(*days, working: false) - end - - def set_working_week_days(*days) - set_week_days(*days, working: true) - end - - def set_week_days(*days, working:) - days.each do |day| - wday = %w[xxx monday tuesday wednesday thursday friday saturday sunday].index(day.downcase) - WeekDay.find_by!(day: wday).update(working:) - end - end + let!(:week) { week_with_saturday_and_sunday_as_weekend } context 'when a work package includes a date that is now a non-working day' do let_schedule(<<~CHART) @@ -138,7 +123,7 @@ RSpec.describe WorkPackages::ApplyWorkingDaysChangeJob do end context 'when a follower has a predecessor with dates covering a day that is now a working day' do - let!(:week) { create(:week, working_days: ['monday', 'tuesday', 'thursday', 'friday']) } + let!(:week) { reset_working_week_days('monday', 'tuesday', 'thursday', 'friday') } let_schedule(<<~CHART) days | MTWTFSS | @@ -161,7 +146,7 @@ RSpec.describe WorkPackages::ApplyWorkingDaysChangeJob do end xcontext 'when a follower has a predecessor with a non-working day between them that is now a working day' do - let!(:week) { create(:week, working_days: ['monday', 'tuesday', 'thursday', 'friday']) } + let!(:week) { reset_working_week_days('monday', 'tuesday', 'thursday', 'friday') } let_schedule(<<~CHART) days | MTWTFSS | @@ -245,7 +230,7 @@ RSpec.describe WorkPackages::ApplyWorkingDaysChangeJob do end xcontext 'when having multiple work packages following each other, and having days becoming working days' do - let!(:week) { create(:week, working_days: ['monday', 'thursday']) } + let!(:week) { reset_working_week_days('monday', 'thursday') } let_schedule(<<~CHART) days | MTWTFSSmtwtfssmtwtfss |