mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
(feat) per training settings for auto cancel
This commit is contained in:
parent
e2edbb419a
commit
c804c84113
@ -26,6 +26,7 @@ class API::SettingsController < API::ApiController
|
|||||||
authorize Setting
|
authorize Setting
|
||||||
|
|
||||||
@settings = []
|
@settings = []
|
||||||
|
updated_settings = []
|
||||||
may_transaction params[:transactional] do
|
may_transaction params[:transactional] do
|
||||||
params[:settings].each do |setting|
|
params[:settings].each do |setting|
|
||||||
next if !setting[:name] || !setting[:value]
|
next if !setting[:name] || !setting[:value]
|
||||||
@ -34,8 +35,9 @@ class API::SettingsController < API::ApiController
|
|||||||
if !SettingService.update_allowed?(db_setting)
|
if !SettingService.update_allowed?(db_setting)
|
||||||
db_setting.errors.add(:-, "#{I18n.t("settings.#{setting[:name]}")}: #{I18n.t('settings.locked_setting')}")
|
db_setting.errors.add(:-, "#{I18n.t("settings.#{setting[:name]}")}: #{I18n.t('settings.locked_setting')}")
|
||||||
elsif db_setting.save
|
elsif db_setting.save
|
||||||
unless db_setting.value == setting[:value]
|
if db_setting.value != setting[:value] &&
|
||||||
db_setting.history_values.create(value: setting[:value], invoicing_profile: current_user.invoicing_profile)
|
db_setting.history_values.create(value: setting[:value], invoicing_profile: current_user.invoicing_profile)
|
||||||
|
updated_settings.push(db_setting)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ class API::SettingsController < API::ApiController
|
|||||||
may_rollback(params[:transactional]) if db_setting.errors.keys.count.positive?
|
may_rollback(params[:transactional]) if db_setting.errors.keys.count.positive?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
SettingService.run_after_update(@settings)
|
SettingService.run_after_update(updated_settings)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -77,6 +77,7 @@ class API::TrainingsController < API::ApiController
|
|||||||
def training_params
|
def training_params
|
||||||
params.require(:training)
|
params.require(:training)
|
||||||
.permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, :public_page, :disabled,
|
.permit(:id, :name, :description, :machine_ids, :plan_ids, :nb_total_places, :public_page, :disabled,
|
||||||
|
:auto_cancel, :auto_cancel_threshold, :auto_cancel_deadline,
|
||||||
training_image_attributes: %i[id attachment], machine_ids: [], plan_ids: [],
|
training_image_attributes: %i[id attachment], machine_ids: [], plan_ids: [],
|
||||||
advanced_accounting_attributes: %i[code analytical_section])
|
advanced_accounting_attributes: %i[code analytical_section])
|
||||||
end
|
end
|
||||||
|
@ -38,13 +38,13 @@ export const TrainingForm: React.FC<TrainingFormProps> = ({ action, training, on
|
|||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [machineModule, setMachineModule] = useState<Setting>(null);
|
const [machineModule, setMachineModule] = useState<Setting>(null);
|
||||||
const [isActiveCancellation, setIsActiveCancellation] = useState<boolean>(false);
|
|
||||||
const [isActiveAccounting, setIsActiveAccounting] = useState<boolean>(false);
|
const [isActiveAccounting, setIsActiveAccounting] = useState<boolean>(false);
|
||||||
const [isActiveAuthorizationValidity, setIsActiveAuthorizationValidity] = useState<boolean>(false);
|
const [isActiveAuthorizationValidity, setIsActiveAuthorizationValidity] = useState<boolean>(false);
|
||||||
const [isActiveValidationRule, setIsActiveValidationRule] = useState<boolean>(false);
|
const [isActiveValidationRule, setIsActiveValidationRule] = useState<boolean>(false);
|
||||||
|
|
||||||
const { handleSubmit, register, control, setValue, formState } = useForm<Training>({ defaultValues: { ...training } });
|
const { handleSubmit, register, control, setValue, formState } = useForm<Training>({ defaultValues: { ...training } });
|
||||||
const output = useWatch<Training>({ control });
|
const output = useWatch<Training>({ control });
|
||||||
|
const isActiveCancellation = useWatch({ control, name: 'auto_cancel' }) as boolean;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SettingAPI.get('machines_module').then(setMachineModule).catch(onError);
|
SettingAPI.get('machines_module').then(setMachineModule).catch(onError);
|
||||||
@ -79,13 +79,6 @@ export const TrainingForm: React.FC<TrainingFormProps> = ({ action, training, on
|
|||||||
}).catch(error => onError(error));
|
}).catch(error => onError(error));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the auto cancellation switch has changed.
|
|
||||||
*/
|
|
||||||
const toggleCancellationSwitch = (value: boolean) => {
|
|
||||||
setIsActiveCancellation(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the authorisation validity switch has changed.
|
* Callback triggered when the authorisation validity switch has changed.
|
||||||
*/
|
*/
|
||||||
@ -178,15 +171,15 @@ export const TrainingForm: React.FC<TrainingFormProps> = ({ action, training, on
|
|||||||
<p className="description">{t('app.admin.training_form.automatic_cancellation_info')}</p>
|
<p className="description">{t('app.admin.training_form.automatic_cancellation_info')}</p>
|
||||||
</header>
|
</header>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<FormSwitch id="active_cancellation" control={control}
|
<FormSwitch id="auto_cancel" control={control}
|
||||||
onChange={toggleCancellationSwitch} formState={formState}
|
formState={formState}
|
||||||
defaultValue={isActiveCancellation}
|
defaultValue={isActiveCancellation}
|
||||||
label={t('app.admin.training_form.automatic_cancellation_switch')} />
|
label={t('app.admin.training_form.automatic_cancellation_switch')} />
|
||||||
{isActiveCancellation && <>
|
{isActiveCancellation && <>
|
||||||
<FormInput register={register}
|
<FormInput register={register}
|
||||||
type="number"
|
type="number"
|
||||||
step={1}
|
step={1}
|
||||||
id="auto_cancellation_threshold"
|
id="auto_cancel_threshold"
|
||||||
formState={formState}
|
formState={formState}
|
||||||
rules={{ required: isActiveCancellation }}
|
rules={{ required: isActiveCancellation }}
|
||||||
nullable
|
nullable
|
||||||
@ -194,7 +187,7 @@ export const TrainingForm: React.FC<TrainingFormProps> = ({ action, training, on
|
|||||||
<FormInput register={register}
|
<FormInput register={register}
|
||||||
type="number"
|
type="number"
|
||||||
step={1}
|
step={1}
|
||||||
id="auto_cancellation_deadline"
|
id="auto_cancel_deadline"
|
||||||
formState={formState}
|
formState={formState}
|
||||||
rules={{ required: isActiveCancellation }}
|
rules={{ required: isActiveCancellation }}
|
||||||
nullable
|
nullable
|
||||||
|
@ -143,8 +143,11 @@ export const Trainings: React.FC<TrainingsProps> = ({ onError, onSuccess }) => {
|
|||||||
|
|
||||||
<div className='cancel'>
|
<div className='cancel'>
|
||||||
<span>{t('app.admin.trainings.cancellation')}</span>
|
<span>{t('app.admin.trainings.cancellation')}</span>
|
||||||
<p>5 {t('app.admin.trainings.cancellation_minimum')}<span>|</span>48 {t('app.admin.trainings.cancellation_deadline')}
|
{(training.auto_cancel && <p>
|
||||||
</p>
|
{t('app.admin.trainings.cancellation_minimum', { ATTENDEES: training.auto_cancel_threshold })}
|
||||||
|
<span>|</span>
|
||||||
|
{t('app.admin.trainings.cancellation_deadline', { DEADLINE: training.auto_cancel_deadline })}
|
||||||
|
</p>) || <p>---</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='capacity'>
|
<div className='capacity'>
|
||||||
|
@ -14,6 +14,9 @@ export interface Training {
|
|||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
plan_ids?: number[],
|
plan_ids?: number[],
|
||||||
training_image_attributes?: FileType,
|
training_image_attributes?: FileType,
|
||||||
|
auto_cancel: boolean,
|
||||||
|
auto_cancel_threshold: number,
|
||||||
|
auto_cancel_deadline: number,
|
||||||
availabilities?: Array<{
|
availabilities?: Array<{
|
||||||
id: number,
|
id: number,
|
||||||
start_at: TDateISO,
|
start_at: TDateISO,
|
||||||
|
@ -203,6 +203,11 @@ class Setting < ApplicationRecord
|
|||||||
last_value&.created_at
|
last_value&.created_at
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def previous_value
|
||||||
|
previous_value = history_values.order(HistoryValue.arel_table['created_at'].desc).limit(2).last
|
||||||
|
previous_value&.value
|
||||||
|
end
|
||||||
|
|
||||||
def previous_update
|
def previous_update
|
||||||
previous_value = history_values.order(HistoryValue.arel_table['created_at'].desc).limit(2).last
|
previous_value = history_values.order(HistoryValue.arel_table['created_at'].desc).limit(2).last
|
||||||
previous_value&.created_at
|
previous_value&.created_at
|
||||||
|
@ -5,12 +5,14 @@
|
|||||||
# so this service provides a wrapper around these operations.
|
# so this service provides a wrapper around these operations.
|
||||||
class SettingService
|
class SettingService
|
||||||
class << self
|
class << self
|
||||||
|
# @param setting [Setting]
|
||||||
def update_allowed?(setting)
|
def update_allowed?(setting)
|
||||||
return false if Rails.application.secrets.locked_settings.include? setting.name
|
return false if Rails.application.secrets.locked_settings.include? setting.name
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def run_after_update(settings)
|
def run_after_update(settings)
|
||||||
update_theme_stylesheet(settings)
|
update_theme_stylesheet(settings)
|
||||||
update_home_stylesheet(settings)
|
update_home_stylesheet(settings)
|
||||||
@ -20,11 +22,13 @@ class SettingService
|
|||||||
export_projects_to_openlab(settings)
|
export_projects_to_openlab(settings)
|
||||||
validate_admins(settings)
|
validate_admins(settings)
|
||||||
update_accounting_line(settings)
|
update_accounting_line(settings)
|
||||||
|
update_trainings_auto_cancel(settings)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# rebuild the theme stylesheet
|
# rebuild the theme stylesheet
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def update_theme_stylesheet(settings)
|
def update_theme_stylesheet(settings)
|
||||||
return unless (%w[main_color secondary_color] & settings.map(&:name)).count.positive?
|
return unless (%w[main_color secondary_color] & settings.map(&:name)).count.positive?
|
||||||
|
|
||||||
@ -32,6 +36,7 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# rebuild the home page stylesheet
|
# rebuild the home page stylesheet
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def update_home_stylesheet(settings)
|
def update_home_stylesheet(settings)
|
||||||
return unless settings.any? { |s| s.name == 'home_css' }
|
return unless settings.any? { |s| s.name == 'home_css' }
|
||||||
|
|
||||||
@ -39,6 +44,7 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# notify about a change in privacy policy
|
# notify about a change in privacy policy
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def notify_privacy_update(settings)
|
def notify_privacy_update(settings)
|
||||||
return unless settings.any? { |s| s.name == 'privacy_body' }
|
return unless settings.any? { |s| s.name == 'privacy_body' }
|
||||||
|
|
||||||
@ -47,6 +53,7 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# sync all objects on stripe
|
# sync all objects on stripe
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def sync_stripe_objects(settings)
|
def sync_stripe_objects(settings)
|
||||||
return unless (%w[stripe_secret_key online_payment_module] & settings.map(&:name)).count.positive?
|
return unless (%w[stripe_secret_key online_payment_module] & settings.map(&:name)).count.positive?
|
||||||
|
|
||||||
@ -55,6 +62,7 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# generate the statistics since the last update
|
# generate the statistics since the last update
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def build_stats(settings)
|
def build_stats(settings)
|
||||||
return unless settings.any? { |s| s.name == 'statistics_module' && s.value == 'true' }
|
return unless settings.any? { |s| s.name == 'statistics_module' && s.value == 'true' }
|
||||||
|
|
||||||
@ -63,6 +71,7 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# export projects to openlab
|
# export projects to openlab
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def export_projects_to_openlab(settings)
|
def export_projects_to_openlab(settings)
|
||||||
return unless (%w[openlab_app_id openlab_app_secret] & settings.map(&:name)).count.positive? &&
|
return unless (%w[openlab_app_id openlab_app_secret] & settings.map(&:name)).count.positive? &&
|
||||||
Setting.get('openlab_app_id').present? && Setting.get('openlab_app_secret').present?
|
Setting.get('openlab_app_id').present? && Setting.get('openlab_app_secret').present?
|
||||||
@ -71,16 +80,33 @@ class SettingService
|
|||||||
end
|
end
|
||||||
|
|
||||||
# automatically validate the admins
|
# automatically validate the admins
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def validate_admins(settings)
|
def validate_admins(settings)
|
||||||
return unless settings.any? { |s| s.name == 'user_validation_required' && s.value == 'true' }
|
return unless settings.any? { |s| s.name == 'user_validation_required' && s.value == 'true' }
|
||||||
|
|
||||||
User.admins.each { |admin| admin.update(validated_at: Time.current) if admin.validated_at.nil? }
|
User.admins.each { |admin| admin.update(validated_at: Time.current) if admin.validated_at.nil? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# rebuild accounting lines
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
def update_accounting_line(settings)
|
def update_accounting_line(settings)
|
||||||
return unless settings.any? { |s| s.name.match(/^accounting_/) || s.name == 'advanced_accounting' }
|
return unless settings.any? { |s| s.name.match(/^accounting_/) || s.name == 'advanced_accounting' }
|
||||||
|
|
||||||
AccountingWorker.perform_async(:all)
|
AccountingWorker.perform_async(:all)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# update tranings auto_cancel parameters
|
||||||
|
# @param settings [Array<Setting>]
|
||||||
|
def update_trainings_auto_cancel(settings)
|
||||||
|
return unless settings.any? { |s| s.name.match(/^trainings_auto_cancel/) }
|
||||||
|
|
||||||
|
tac = settings.find { |s| s.name == 'trainings_auto_cancel' }
|
||||||
|
threshold = settings.find { |s| s.name == 'trainings_auto_cancel_threshold' }
|
||||||
|
deadline = settings.find { |s| s.name == 'trainings_auto_cancel_deadline' }
|
||||||
|
|
||||||
|
Training.find_each do |t|
|
||||||
|
TrainingService.update_auto_cancel(t, tac, threshold, deadline)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -34,6 +34,29 @@ class TrainingService
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# update the given training, depending on the provided settings
|
||||||
|
# @param training [Training]
|
||||||
|
# @param auto_cancel [Setting,NilClass]
|
||||||
|
# @param threshold [Setting,NilClass]
|
||||||
|
# @param deadline [Setting,NilClass]
|
||||||
|
def update_auto_cancel(training, auto_cancel, threshold, deadline)
|
||||||
|
previous_auto_cancel = auto_cancel.nil? ? Setting.find_by(name: 'trainings_auto_cancel').value : auto_cancel.previous_value
|
||||||
|
previous_threshold = threshold.nil? ? Setting.find_by(name: 'trainings_auto_cancel_threshold').value : threshold.previous_value
|
||||||
|
previous_deadline = deadline.nil? ? Setting.find_by(name: 'trainings_auto_cancel_deadline').value : deadline.previous_value
|
||||||
|
is_default = training.auto_cancel.to_s == previous_auto_cancel &&
|
||||||
|
[nil, previous_threshold].include?(training.auto_cancel_threshold.to_s) &&
|
||||||
|
[nil, previous_deadline].include?(training.auto_cancel_deadline.to_s)
|
||||||
|
|
||||||
|
return unless is_default
|
||||||
|
|
||||||
|
# update parameters if the given training is default
|
||||||
|
params = {}
|
||||||
|
params[:auto_cancel] = auto_cancel.value unless auto_cancel.nil?
|
||||||
|
params[:auto_cancel_threshold] = threshold.value unless threshold.nil?
|
||||||
|
params[:auto_cancel_deadline] = deadline.value unless deadline.nil?
|
||||||
|
training.update(params)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# @param trainings [ActiveRecord::Relation<Training>]
|
# @param trainings [ActiveRecord::Relation<Training>]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.extract! training, :id, :name, :description, :machine_ids, :nb_total_places, :public_page, :disabled, :slug
|
json.extract! training, :id, :name, :description, :machine_ids, :nb_total_places, :public_page, :disabled, :slug,
|
||||||
|
:auto_cancel, :auto_cancel_threshold, :auto_cancel_deadline
|
||||||
if training.training_image
|
if training.training_image
|
||||||
json.training_image_attributes do
|
json.training_image_attributes do
|
||||||
json.id training.training_image.id
|
json.id training.training_image.id
|
||||||
|
@ -437,8 +437,8 @@ en:
|
|||||||
name: "Training name"
|
name: "Training name"
|
||||||
associated_machines: "Associated machines"
|
associated_machines: "Associated machines"
|
||||||
cancellation: "Cancellation (attendees | deadline)"
|
cancellation: "Cancellation (attendees | deadline)"
|
||||||
cancellation_minimum: "minimum"
|
cancellation_minimum: "{ATTENDEES} minimum"
|
||||||
cancellation_deadline: "h"
|
cancellation_deadline: "{DEADLINE} h"
|
||||||
capacity: "Capacity (max. attendees)"
|
capacity: "Capacity (max. attendees)"
|
||||||
authorisation: "Time-limited authorisation"
|
authorisation: "Time-limited authorisation"
|
||||||
period_MONTH: "{MONTH} {MONTH, plural, one{month} other{months}}"
|
period_MONTH: "{MONTH} {MONTH, plural, one{month} other{months}}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user