Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

305 lines
8.9 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 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 WorkPackages::DatePickerController < ApplicationController
ERROR_PRONE_ATTRIBUTES = %i[start_date
due_date
duration].freeze
layout false
before_action :find_work_package, except: %i[new create preview]
authorization_checked! :show, :preview, :update, :edit, :new, :create
attr_accessor :work_package
def show
set_date_attributes_to_work_package
render_form
end
2025-01-13 14:41:57 +01:00
def new
make_fake_initial_work_package
set_date_attributes_to_work_package
render_form
2025-01-13 14:41:57 +01:00
end
def edit
set_date_attributes_to_work_package
render_form
end
def preview
if params[:work_package_id]
find_work_package
else
make_fake_initial_work_package
end
set_date_attributes_to_work_package
render_form(preview: true)
end
2025-01-13 14:41:57 +01:00
def create
make_fake_initial_work_package
service_call = set_date_attributes_to_work_package
if service_call.errors
.map(&:attribute)
.intersect?(ERROR_PRONE_ATTRIBUTES)
render_form(status: :unprocessable_entity)
2025-01-13 14:41:57 +01:00
else
render json: {
startDate: @work_package.start_date,
dueDate: @work_package.due_date,
duration: @work_package.duration,
scheduleManually: @work_package.schedule_manually,
includeNonWorkingDays: if @work_package.ignore_non_working_days.nil?
false
else
@work_package.ignore_non_working_days
end
}
end
end
def update
wp_params = work_package_datepicker_params
wp_params = manage_params_for_automatic_mode(wp_params)
service_call = WorkPackages::UpdateService
.new(user: current_user,
model: @work_package)
.call(wp_params)
if service_call.success?
head :ok
else
render_form(status: :unprocessable_entity)
end
end
private
def render_form(status: :ok, preview: false)
render :show,
locals: {
work_package:,
schedule_manually:,
focused_field:,
touched_field_map:,
date_mode:,
preview:
},
status:
end
def focused_field
return params[:focused_field] if params[:focused_field].present?
trigger = params[:field]
# For automatic scheduling, we focus the due date initially and do not switch to start date after touching it
if !ActiveModel::Type::Boolean.new.cast(schedule_manually) && trigger != "duration"
return :due_date
end
# Decide which field to focus next
case trigger
when "startDate"
:start_date
when "work_package[start_date]"
handle_focus_order_for_fields(:start_date, :due_date)
when "work_package[duration]", "duration"
:duration
when "dueDate"
:due_date
when "work_package[due_date]"
handle_focus_order_for_fields(:due_date, :start_date)
else
:start_date
end
end
def find_work_package
@work_package = WorkPackage.visible.find(params[:work_package_id])
end
def touched_field_map
if params[:work_package]
params.require(:work_package)
.slice("schedule_manually_touched",
"ignore_non_working_days_touched",
"start_date_touched",
"due_date_touched",
"duration_touched")
2025-04-16 09:07:42 +02:00
.transform_values { it == "true" }
.permit!
else
{}
end
end
def schedule_manually
2024-12-19 11:00:03 +01:00
find_if_present(params[:schedule_manually]) ||
find_if_present(params.dig(:work_package, :schedule_manually)) ||
work_package.schedule_manually
2024-12-19 11:00:03 +01:00
end
def date_mode
# Once in range mode, always in range mode
return params[:date_mode] if params[:date_mode].present? && params[:date_mode] == "range"
if work_package.start_date.nil? || work_package.due_date.nil?
"single"
else
"range"
end
end
2024-12-19 11:00:03 +01:00
def find_if_present(value)
value.presence
end
def work_package_datepicker_params
if params[:work_package]
2024-12-20 12:50:36 +01:00
handle_milestone_dates
handle_form_cleared
2024-12-20 12:50:36 +01:00
params.require(:work_package)
.slice(*allowed_touched_params)
.merge(schedule_manually:, date_mode:, triggering_field: params[:triggering_field])
.permit!
2025-04-14 11:59:23 +02:00
else
{}
end
end
def allowed_touched_params
2025-04-16 09:07:42 +02:00
allowed_params.filter { touched?(it) }
end
def allowed_params
%i[schedule_manually ignore_non_working_days start_date due_date duration]
end
def touched?(field)
touched_field_map[:"#{field}_touched"]
end
2025-01-13 14:41:57 +01:00
def make_fake_initial_work_package
initial_params = params.require(:work_package)
.require(:initial)
.permit(:start_date, :due_date, :duration, :ignore_non_working_days)
2025-01-13 14:41:57 +01:00
@work_package = WorkPackage.new(initial_params)
@work_package.clear_changes_information
end
def set_date_attributes_to_work_package
wp_params = work_package_datepicker_params
wp_params = manage_params_for_automatic_mode(wp_params)
if wp_params.present?
WorkPackages::SetAttributesService
.new(user: current_user,
model: @work_package,
contract_class:)
.call(wp_params)
end
end
2024-12-20 12:50:36 +01:00
def contract_class
if @work_package.new_record?
WorkPackages::CreateContract
else
WorkPackages::UpdateContract
end
end
2024-12-20 12:50:36 +01:00
def handle_milestone_dates
if work_package.is_milestone? && params.require(:work_package).has_key?(:start_date)
2024-12-20 12:50:36 +01:00
# Set the dueDate as the SetAttributesService will otherwise throw an error because the fields do not match
params.require(:work_package)[:due_date] = params.require(:work_package)[:start_date]
params.require(:work_package)[:due_date_touched] = "true"
end
end
def handle_form_cleared
touched_params = params.require(:work_package).slice(*allowed_touched_params)
if two_fields_cleared?(touched_params)
# If two fields are already manually cleared, we assume that the user wants to clear the whole form
params_array = %i[start_date due_date duration]
params_array.each do |param|
if touched_params[param].nil?
params.require(:work_package)[param] = ""
params.require(:work_package)["#{param}_touched"] = "true"
end
end
end
end
def two_fields_cleared?(wp_params)
start_date = wp_params[:start_date]
due_date = wp_params[:due_date]
duration = wp_params[:duration]
# Check which params are set to an empty string
empty_params = [start_date, due_date, duration].select { |param| param == "" }
# Check which param was not touched
missing_param = [start_date, due_date, duration].select(&:nil?)
# If two values are deleted and one is untouched, return tru
empty_params.length == 2 && missing_param.length == 1
end
def handle_focus_order_for_fields(trigger_field, alternative_field)
if !!params[:work_package][:"#{trigger_field}_touched"] && params[:work_package][:"#{trigger_field}"].blank?
# Special case, when deleting a value: we want to keep the focus on that field instead of moving to the next field
trigger_field
else
alternative_field
end
end
def manage_params_for_automatic_mode(wp_params)
return wp_params if wp_params["schedule_manually"] != "false"
# For WP with children the dates and duration are always fixed
return wp_params.without("start_date", "due_date", "duration") if work_package.children.any?
# the start should be preserved and will thus be send as a parameter
wp_params["start_date"] = work_package.start_date.to_s
wp_params
end
end