1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-01 21:52:19 +01:00

(feat) child validation

This commit is contained in:
Du Peng 2023-05-25 20:17:37 +02:00
parent ecc4fde47f
commit f1d3fdf2de
31 changed files with 238 additions and 36 deletions

View File

@ -4,7 +4,7 @@
# Children are used to provide a way to manage multiple users in the family account # Children are used to provide a way to manage multiple users in the family account
class API::ChildrenController < API::APIController class API::ChildrenController < API::APIController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_child, only: %i[show update destroy] before_action :set_child, only: %i[show update destroy validate]
def index def index
authorize Child authorize Child
@ -43,6 +43,17 @@ class API::ChildrenController < API::APIController
head :no_content head :no_content
end end
def validate
authorize @child
cparams = params.require(:child).permit(:validated_at)
if @child.update(validated_at: cparams[:validated_at].present? ? Time.current : nil)
render :show, status: :ok, location: child_path(@child)
else
render json: @child.errors, status: :unprocessable_entity
end
end
private private
def set_child def set_child

View File

@ -38,4 +38,9 @@ export default class ChildAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/children/${childId}`); const res: AxiosResponse<void> = await apiClient.delete(`/api/children/${childId}`);
return res?.data; return res?.data;
} }
static async validate (child: Child): Promise<Child> {
const res: AxiosResponse<Child> = await apiClient.patch(`/api/children/${child.id}/validate`, { child });
return res?.data;
}
} }

View File

@ -8,9 +8,11 @@ import { FabButton } from '../base/fab-button';
import { FormFileUpload } from '../form/form-file-upload'; import { FormFileUpload } from '../form/form-file-upload';
import { FileType } from '../../models/file'; import { FileType } from '../../models/file';
import { SupportingDocumentType } from '../../models/supporting-document-type'; import { SupportingDocumentType } from '../../models/supporting-document-type';
import { User } from '../../models/user';
interface ChildFormProps { interface ChildFormProps {
child: Child; child: Child;
operator: User;
onSubmit: (data: Child) => void; onSubmit: (data: Child) => void;
supportingDocumentsTypes: Array<SupportingDocumentType>; supportingDocumentsTypes: Array<SupportingDocumentType>;
} }
@ -18,7 +20,7 @@ interface ChildFormProps {
/** /**
* A form for creating or editing a child. * A form for creating or editing a child.
*/ */
export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportingDocumentsTypes }) => { export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportingDocumentsTypes, operator }) => {
const { t } = useTranslation('public'); const { t } = useTranslation('public');
const { register, formState, handleSubmit, setValue, control } = useForm<Child>({ const { register, formState, handleSubmit, setValue, control } = useForm<Child>({
@ -31,11 +33,20 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
return supportingDocumentType ? supportingDocumentType.name : ''; return supportingDocumentType ? supportingDocumentType.name : '';
}; };
/**
* Check if the current operator has administrative rights or is a normal member
*/
const isPrivileged = (): boolean => {
return (operator?.role === 'admin' || operator?.role === 'manager');
};
return ( return (
<div className="child-form"> <div className="child-form">
<div className="info-area"> {isPrivileged() &&
{t('app.public.child_form.child_form_info')} <div className="info-area">
</div> {t('app.public.child_form.child_form_info')}
</div>
}
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<FormInput id="first_name" <FormInput id="first_name"
register={register} register={register}
@ -69,6 +80,22 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
label={t('app.public.child_form.email')} label={t('app.public.child_form.email')}
/> />
{output.supporting_document_files_attributes.map((sf, index) => { {output.supporting_document_files_attributes.map((sf, index) => {
if (isPrivileged()) {
return (
<div key={index} className="document-type">
<div className="type-name">{getSupportingDocumentsTypeName(sf.supporting_document_type_id)}</div>
{sf.attachment_url && (
<a href={sf.attachment_url} target="_blank" rel="noreferrer">
<span className="filename">{sf.attachment}</span>
<i className="fa fa-download"></i>
</a>
)}
{!sf.attachment_url && (
<div className="missing-file">{t('app.public.child_form.to_complete')}</div>
)}
</div>
);
}
return ( return (
<FormFileUpload key={index} <FormFileUpload key={index}
defaultFile={sf as FileType} defaultFile={sf as FileType}

View File

@ -5,12 +5,15 @@ import { Child } from '../../models/child';
import ChildAPI from '../../api/child'; import ChildAPI from '../../api/child';
import { ChildForm } from './child-form'; import { ChildForm } from './child-form';
import { SupportingDocumentType } from '../../models/supporting-document-type'; import { SupportingDocumentType } from '../../models/supporting-document-type';
import { ChildValidation } from './child-validation';
import { User } from '../../models/user';
interface ChildModalProps { interface ChildModalProps {
child?: Child; child?: Child;
operator: User;
isOpen: boolean; isOpen: boolean;
toggleModal: () => void; toggleModal: () => void;
onSuccess: (child: Child) => void; onSuccess: (child: Child, msg: string) => void;
onError: (error: string) => void; onError: (error: string) => void;
supportingDocumentsTypes: Array<SupportingDocumentType>; supportingDocumentsTypes: Array<SupportingDocumentType>;
} }
@ -18,7 +21,7 @@ interface ChildModalProps {
/** /**
* A modal for creating or editing a child. * A modal for creating or editing a child.
*/ */
export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleModal, onSuccess, onError, supportingDocumentsTypes }) => { export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleModal, onSuccess, onError, supportingDocumentsTypes, operator }) => {
const { t } = useTranslation('public'); const { t } = useTranslation('public');
/** /**
@ -32,7 +35,7 @@ export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleMod
await ChildAPI.create(data); await ChildAPI.create(data);
} }
toggleModal(); toggleModal();
onSuccess(data); onSuccess(data, '');
} catch (error) { } catch (error) {
onError(error); onError(error);
} }
@ -45,7 +48,10 @@ export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleMod
toggleModal={toggleModal} toggleModal={toggleModal}
closeButton={true} closeButton={true}
confirmButton={false} > confirmButton={false} >
<ChildForm child={child} onSubmit={handleSaveChild} supportingDocumentsTypes={supportingDocumentsTypes}/> {(operator?.role === 'admin' || operator?.role === 'manager') &&
<ChildValidation child={child} onSuccess={onSuccess} onError={onError} />
}
<ChildForm child={child} onSubmit={handleSaveChild} supportingDocumentsTypes={supportingDocumentsTypes} operator={operator}/>
</FabModal> </FabModal>
); );
}; };

View File

@ -0,0 +1,54 @@
import { useState, useEffect } from 'react';
import * as React from 'react';
import Switch from 'react-switch';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { Child } from '../../models/child';
import ChildAPI from '../../api/child';
import { TDateISO } from '../../typings/date-iso';
interface ChildValidationProps {
child: Child
onSuccess: (child: Child, message: string) => void,
onError: (message: string) => void,
}
/**
* This component allows to configure boolean value for a setting.
*/
export const ChildValidation: React.FC<ChildValidationProps> = ({ child, onSuccess, onError }) => {
const { t } = useTranslation('admin');
const [value, setValue] = useState<boolean>(!!(child?.validated_at));
useEffect(() => {
setValue(!!(child?.validated_at));
}, [child]);
/**
* Callback triggered when the 'switch' is changed.
*/
const handleChanged = (_value: boolean) => {
setValue(_value);
const _child = _.clone(child);
if (_value) {
_child.validated_at = new Date().toISOString() as TDateISO;
} else {
_child.validated_at = null;
}
ChildAPI.validate(_child)
.then((child: Child) => {
onSuccess(child, t(`app.admin.child_validation.${_value ? 'validate' : 'invalidate'}_child_success`));
}).catch(err => {
setValue(!_value);
onError(t(`app.admin.child_validation.${_value ? 'validate' : 'invalidate'}_child_error`) + err);
});
};
return (
<div className="child-validation">
<label htmlFor="child-validation-switch">{t('app.admin.child_validation.validate_child')}</label>
<Switch checked={value} id="child-validation-switch" onChange={handleChanged} className="switch"></Switch>
</div>
);
};

View File

@ -15,7 +15,8 @@ import SupportingDocumentTypeAPI from '../../api/supporting-document-type';
declare const Application: IApplication; declare const Application: IApplication;
interface ChildrenListProps { interface ChildrenListProps {
currentUser: User; user: User;
operator: User;
onSuccess: (error: string) => void; onSuccess: (error: string) => void;
onError: (error: string) => void; onError: (error: string) => void;
} }
@ -23,7 +24,7 @@ interface ChildrenListProps {
/** /**
* A list of children belonging to the current user. * A list of children belonging to the current user.
*/ */
export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError }) => { export const ChildrenList: React.FC<ChildrenListProps> = ({ user, operator, onError, onSuccess }) => {
const { t } = useTranslation('public'); const { t } = useTranslation('public');
const [children, setChildren] = useState<Array<Child>>([]); const [children, setChildren] = useState<Array<Child>>([]);
@ -32,11 +33,11 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError
const [supportingDocumentsTypes, setSupportingDocumentsTypes] = useState<Array<SupportingDocumentType>>([]); const [supportingDocumentsTypes, setSupportingDocumentsTypes] = useState<Array<SupportingDocumentType>>([]);
useEffect(() => { useEffect(() => {
ChildAPI.index({ user_id: currentUser.id }).then(setChildren); ChildAPI.index({ user_id: user.id }).then(setChildren);
SupportingDocumentTypeAPI.index({ document_type: 'Child' }).then(tData => { SupportingDocumentTypeAPI.index({ document_type: 'Child' }).then(tData => {
setSupportingDocumentsTypes(tData); setSupportingDocumentsTypes(tData);
}); });
}, [currentUser]); }, [user]);
/** /**
* Open the add child modal * Open the add child modal
@ -44,7 +45,7 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError
const addChild = () => { const addChild = () => {
setIsOpenChildModal(true); setIsOpenChildModal(true);
setChild({ setChild({
user_id: currentUser.id, user_id: user.id,
supporting_document_files_attributes: supportingDocumentsTypes.map(t => { supporting_document_files_attributes: supportingDocumentsTypes.map(t => {
return { supporting_document_type_id: t.id }; return { supporting_document_type_id: t.id };
}) })
@ -70,24 +71,36 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError
*/ */
const deleteChild = (child: Child) => { const deleteChild = (child: Child) => {
ChildAPI.destroy(child.id).then(() => { ChildAPI.destroy(child.id).then(() => {
ChildAPI.index({ user_id: currentUser.id }).then(setChildren); ChildAPI.index({ user_id: user.id }).then(setChildren);
}); });
}; };
/** /**
* Handle save child success from the API * Handle save child success from the API
*/ */
const handleSaveChildSuccess = () => { const handleSaveChildSuccess = (_child: Child, msg: string) => {
ChildAPI.index({ user_id: currentUser.id }).then(setChildren); ChildAPI.index({ user_id: user.id }).then(setChildren);
if (msg) {
onSuccess(msg);
}
};
/**
* Check if the current operator has administrative rights or is a normal member
*/
const isPrivileged = (): boolean => {
return (operator?.role === 'admin' || operator?.role === 'manager');
}; };
return ( return (
<section> <section>
<header> <header>
<h2>{t('app.public.children_list.heading')}</h2> <h2>{t('app.public.children_list.heading')}</h2>
<FabButton onClick={addChild}> {!isPrivileged() && (
{t('app.public.children_list.add_child')} <FabButton onClick={addChild}>
</FabButton> {t('app.public.children_list.add_child')}
</FabButton>
)}
</header> </header>
<div className="children-list"> <div className="children-list">
@ -95,7 +108,7 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ currentUser, onError
<ChildItem key={child.id} child={child} onEdit={editChild} onDelete={deleteChild} /> <ChildItem key={child.id} child={child} onEdit={editChild} onDelete={deleteChild} />
))} ))}
</div> </div>
<ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} /> <ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} operator={operator} />
</section> </section>
); );
}; };
@ -108,4 +121,4 @@ const ChildrenListWrapper: React.FC<ChildrenListProps> = (props) => {
); );
}; };
Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['currentUser', 'onSuccess', 'onError'])); Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['user', 'operator', 'onSuccess', 'onError']));

View File

@ -201,6 +201,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
// Global config: is the user validation required ? // Global config: is the user validation required ?
$scope.enableUserValidationRequired = settingsPromise.user_validation_required === 'true'; $scope.enableUserValidationRequired = settingsPromise.user_validation_required === 'true';
// Global config: is the child validation required ?
$scope.enableChildValidationRequired = settingsPromise.child_validation_required === 'true';
// online payments (by card) // online payments (by card)
$scope.onlinePayment = { $scope.onlinePayment = {
showModal: false, showModal: false,
@ -635,6 +638,9 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
if (!user.booked) { if (!user.booked) {
return false; return false;
} }
if ($scope.enableChildValidationRequired && user.booked.type === 'Child' && !user.booked.validatedAt) {
return false;
}
} }
} }
} }
@ -731,7 +737,8 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
key, key,
name: child.first_name + ' ' + child.last_name, name: child.first_name + ' ' + child.last_name,
id: child.id, id: child.id,
type: 'Child' type: 'Child',
validatedAt: child.validated_at
}); });
} }
} }

View File

@ -13,6 +13,7 @@ export interface Child {
phone?: string, phone?: string,
birthday: TDateISODate, birthday: TDateISODate,
user_id: number, user_id: number,
validated_at?: TDateISODate,
supporting_document_files_attributes?: Array<{ supporting_document_files_attributes?: Array<{
id?: number, id?: number,
supportable_id?: number, supportable_id?: number,

View File

@ -627,7 +627,7 @@ angular.module('application.router', ['ui.router'])
resolve: { resolve: {
eventPromise: ['Event', '$transition$', function (Event, $transition$) { return Event.get({ id: $transition$.params().id }).$promise; }], eventPromise: ['Event', '$transition$', function (Event, $transition$) { return Event.get({ id: $transition$.params().id }).$promise; }],
priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }], priceCategoriesPromise: ['PriceCategory', function (PriceCategory) { return PriceCategory.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'event_explications_alert', 'online_payment_module', 'user_validation_required', 'user_validation_required_list']" }).$promise; }] settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'event_explications_alert', 'online_payment_module', 'user_validation_required', 'user_validation_required_list', 'child_validation_required']" }).$promise; }]
} }
}) })

View File

@ -1,4 +1,4 @@
.user-validation { .user-validation, .child-validation {
label { label {
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: middle;

View File

@ -62,6 +62,10 @@
</uib-tab> </uib-tab>
<uib-tab heading="{{ 'app.shared.user_admin.children' | translate }}" ng-if="$root.settings.familyAccount">
<children-list user="user" operator="currentUser" on-success="onSuccess" on-error="onError" />
</uib-tab>
<uib-tab heading="{{ 'app.admin.members_edit.supporting_documents' | translate }}" ng-show="hasProofOfIdentityTypes"> <uib-tab heading="{{ 'app.admin.members_edit.supporting_documents' | translate }}" ng-show="hasProofOfIdentityTypes">
<supporting-documents-validation <supporting-documents-validation
operator="currentUser" operator="currentUser"

View File

@ -54,13 +54,20 @@
<div class="row"> <div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.family_account' }}</h3> <h3 class="m-l" translate>{{ 'app.admin.settings.family_account' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.family_account_info_html' | translate"></p> <p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.family_account_info_html' | translate"></p>
<div class="col-md-6"> <div class="col-md-10 col-md-offset-1">
<boolean-setting name="'family_account'" <boolean-setting name="'family_account'"
settings="allSettings" settings="allSettings"
label="'app.admin.settings.enable_family_account' | translate" label="'app.admin.settings.enable_family_account' | translate"
on-success="onSuccess" on-success="onSuccess"
on-error="onError"> on-error="onError">
</div> </div>
<div class="col-md-10 col-md-offset-1">
<boolean-setting name="'child_validation_required'"
settings="allSettings"
label="'app.admin.settings.child_validation_required_label' | translate"
on-success="onSuccess"
on-error="onError">
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">

View File

@ -7,5 +7,5 @@
</section> </section>
<children-list current-user="currentUser" on-success="onSuccess" on-error="onError" /> <children-list user="currentUser" operator="currentUser" on-success="onSuccess" on-error="onError" />
</div> </div>

View File

@ -136,6 +136,12 @@
class="form-control"> class="form-control">
<option value=""></option> <option value=""></option>
</select> </select>
<uib-alert type="danger" ng-if="enableChildValidationRequired && user.booked && user.booked.type === 'Child' && !user.booked.validatedAt">
<p class="text-sm">
<i class="fa fa-warning"></i>
<span translate>{{ 'app.shared.cart.child_validation_required_alert' }}</span>
</p>
</uib-alert>
</div> </div>
</div> </div>
</div> </div>
@ -162,6 +168,12 @@
class="form-control"> class="form-control">
<option value=""></option> <option value=""></option>
</select> </select>
<uib-alert type="danger" ng-if="enableChildValidationRequired && user.booked && user.booked.type === 'Child' && !user.booked.validatedAt">
<p class="text-sm">
<i class="fa fa-warning"></i>
<span translate>{{ 'app.shared.cart.child_validation_required_alert' }}</span>
</p>
</uib-alert>
</div> </div>
</div> </div>
</div> </div>

View File

@ -168,6 +168,7 @@ module SettingsHelper
user_validation_required_list user_validation_required_list
show_username_in_admin_list show_username_in_admin_list
family_account family_account
child_validation_required
store_module store_module
store_withdrawal_instructions store_withdrawal_instructions
store_hidden store_hidden

View File

@ -11,14 +11,18 @@ class ChildPolicy < ApplicationPolicy
end end
def show? def show?
user.id == record.user_id user.privileged? || user.id == record.user_id
end end
def update? def update?
user.id == record.user_id user.privileged? || user.id == record.user_id
end end
def destroy? def destroy?
user.id == record.user_id user.privileged? || user.id == record.user_id
end
def validate?
user.privileged?
end end
end end

View File

@ -46,7 +46,7 @@ class SettingPolicy < ApplicationPolicy
external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label
machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label
trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label
events_banner_cta_url family_account] events_banner_cta_url family_account child_validation_required]
end end
## ##

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
json.extract! child, :id, :first_name, :last_name, :email, :birthday, :phone, :user_id json.extract! child, :id, :first_name, :last_name, :email, :birthday, :phone, :user_id, :validated_at
json.supporting_document_files_attributes child.supporting_document_files do |f| json.supporting_document_files_attributes child.supporting_document_files do |f|
json.id f.id json.id f.id
json.supportable_id f.supportable_id json.supportable_id f.supportable_id
@ -8,5 +8,5 @@ json.supporting_document_files_attributes child.supporting_document_files do |f|
json.supporting_document_type_id f.supporting_document_type_id json.supporting_document_type_id f.supporting_document_type_id
json.attachment f.attachment.file.filename json.attachment f.attachment.file.filename
json.attachment_name f.attachment_identifier json.attachment_name f.attachment_identifier
json.attachment_url f.attachment_url json.attachment_url "/api/supporting_document_files/#{f.id}/download"
end end

View File

@ -0,0 +1,3 @@
# forzen_string_literal: true
json.partial! 'child', child: @child

View File

@ -1236,6 +1236,12 @@ en:
validate_member_error: "An unexpected error occurred: unable to validate this member." validate_member_error: "An unexpected error occurred: unable to validate this member."
invalidate_member_error: "An unexpected error occurred: unable to invalidate this member." invalidate_member_error: "An unexpected error occurred: unable to invalidate this member."
validate_account: "Validate the account" validate_account: "Validate the account"
child_validation:
validate_child_success: "Child successfully validated"
invalidate_child_success: "Child successfully invalidated"
validate_child_error: "An unexpected error occurred: unable to validate this child."
invalidate_child_error: "An unexpected error occurred: unable to invalidate this child."
validate_child: "Validate the child"
supporting_documents_refusal_form: supporting_documents_refusal_form:
refusal_comment: "Comment" refusal_comment: "Comment"
comment_placeholder: "Please type a comment here" comment_placeholder: "Please type a comment here"
@ -1781,6 +1787,7 @@ en:
family_account: "family account" family_account: "family account"
family_account_info_html: "By activating this option, you offer your members the possibility to add their child(ren) to their own account. You can also request proof if you wish to validate them." family_account_info_html: "By activating this option, you offer your members the possibility to add their child(ren) to their own account. You can also request proof if you wish to validate them."
enable_family_account: "Enable the Family Account option" enable_family_account: "Enable the Family Account option"
child_validation_required_label: "Activate the account validation option for children"
overlapping_options: overlapping_options:
training_reservations: "Trainings" training_reservations: "Trainings"
machine_reservations: "Machines" machine_reservations: "Machines"

View File

@ -1236,6 +1236,12 @@ fr:
validate_member_error: "Une erreur inattendue est survenue. Impossible de valider ce compte membre." validate_member_error: "Une erreur inattendue est survenue. Impossible de valider ce compte membre."
invalidate_member_error: "Une erreur inattendue est survenue. Impossible d'invalider ce compte membre." invalidate_member_error: "Une erreur inattendue est survenue. Impossible d'invalider ce compte membre."
validate_account: "Valider le compte" validate_account: "Valider le compte"
child_validation:
validate_child_success: "Le compte enfant a bien été validé"
invalidate_child_success: "Le compte enfant a bien été invalidé"
validate_child_error: "Une erreur inattendue est survenue. Impossible de valider ce compte enfant."
invalidate_child_error: "Une erreur inattendue est survenue. Impossible d'invalider ce compte enfant."
validate_child: "Valider le compte enfant"
supporting_documents_refusal_form: supporting_documents_refusal_form:
refusal_comment: "Commentaire" refusal_comment: "Commentaire"
comment_placeholder: "Veuillez saisir un commentaire ici" comment_placeholder: "Veuillez saisir un commentaire ici"
@ -1781,6 +1787,7 @@ fr:
family_account: "Compte famille" family_account: "Compte famille"
family_account_info_html: "En activant cette option, vous offrez à vos membres la possibilité d'ajouter sur leur propre compte leur(s) enfants. Vous pouvez aussi demander un justificatif si vous souhaitez les valider." family_account_info_html: "En activant cette option, vous offrez à vos membres la possibilité d'ajouter sur leur propre compte leur(s) enfants. Vous pouvez aussi demander un justificatif si vous souhaitez les valider."
enable_family_account: "Activer l'option Compte Famille" enable_family_account: "Activer l'option Compte Famille"
child_validation_required_label: "Activer l'option de validation des comptes enfants"
overlapping_options: overlapping_options:
training_reservations: "Formations" training_reservations: "Formations"
machine_reservations: "Machines" machine_reservations: "Machines"

View File

@ -491,6 +491,7 @@ en:
email: "Email" email: "Email"
phone: "Phone" phone: "Phone"
save: "Save" save: "Save"
to_complete: "To complete"
child_item: child_item:
first_name: "First name of the child" first_name: "First name of the child"
last_name: "Last name of the child" last_name: "Last name of the child"

View File

@ -491,6 +491,7 @@ fr:
email: "Courriel" email: "Courriel"
phone: "Téléphone" phone: "Téléphone"
save: "Enregistrer" save: "Enregistrer"
to_complete: "À compléter"
child_item: child_item:
first_name: "Prénom de l'enfant" first_name: "Prénom de l'enfant"
last_name: "Nom de l'enfant" last_name: "Nom de l'enfant"

View File

@ -195,6 +195,7 @@ en:
group_is_required: "Group is required." group_is_required: "Group is required."
trainings: "Trainings" trainings: "Trainings"
tags: "Tags" tags: "Tags"
children: "Children"
#machine/training slot modification modal #machine/training slot modification modal
confirm_modify_slot_modal: confirm_modify_slot_modal:
change_the_slot: "Change the slot" change_the_slot: "Change the slot"
@ -368,6 +369,7 @@ en:
user_tags: "User tags" user_tags: "User tags"
no_tags: "No tags" no_tags: "No tags"
user_validation_required_alert: "Warning!<br>Your administrator must validate your account. Then, you'll then be able to access all the booking features." user_validation_required_alert: "Warning!<br>Your administrator must validate your account. Then, you'll then be able to access all the booking features."
child_validation_required_alert: "Warning!<br>Your administrator must validate your child account. Then, you'll then be able to book the event."
# feature-tour modal # feature-tour modal
tour: tour:
previous: "Previous" previous: "Previous"

View File

@ -195,6 +195,7 @@ fr:
group_is_required: "Le groupe est requis." group_is_required: "Le groupe est requis."
trainings: "Formations" trainings: "Formations"
tags: "Étiquettes" tags: "Étiquettes"
children: "Enfants"
#machine/training slot modification modal #machine/training slot modification modal
confirm_modify_slot_modal: confirm_modify_slot_modal:
change_the_slot: "Modifier le créneau" change_the_slot: "Modifier le créneau"
@ -368,6 +369,7 @@ fr:
user_tags: "Étiquettes de l'utilisateur" user_tags: "Étiquettes de l'utilisateur"
no_tags: "Aucune étiquette" no_tags: "Aucune étiquette"
user_validation_required_alert: "Attention !<br>Votre administrateur doit valider votre compte. Vous pourrez alors accéder à l'ensemble des fonctionnalités de réservation." user_validation_required_alert: "Attention !<br>Votre administrateur doit valider votre compte. Vous pourrez alors accéder à l'ensemble des fonctionnalités de réservation."
child_validation_required_alert: "Attention !<br>Votre administrateur doit valider votre compte enfant. Vous pourrez alors réserver l'événement."
#feature-tour modal #feature-tour modal
tour: tour:
previous: "Précédent" previous: "Précédent"

View File

@ -183,7 +183,9 @@ Rails.application.routes.draw do
get 'withdrawal_instructions', on: :member get 'withdrawal_instructions', on: :member
end end
resources :children, only: %i[index show create update destroy] resources :children do
patch ':id/validate', action: 'validate', on: :collection
end
# for admin # for admin
resources :trainings do resources :trainings do

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
# add validated_at to child
class AddValidatedAtToChild < ActiveRecord::Migration[7.0]
def change
add_column :children, :validated_at, :datetime
end
end

View File

@ -730,3 +730,4 @@ Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Set
Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value) Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value)
Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value) Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value)
Setting.set('child_validation_required', false) unless Setting.find_by(name: 'child_validation_required').try(:value)

View File

@ -975,7 +975,8 @@ CREATE TABLE public.children (
phone character varying, phone character varying,
email character varying, email character varying,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL updated_at timestamp without time zone NOT NULL,
validated_at timestamp(6) without time zone
); );
@ -8935,6 +8936,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230511081018'), ('20230511081018'),
('20230524080448'), ('20230524080448'),
('20230524083558'), ('20230524083558'),
('20230524110215'); ('20230524110215'),
('20230525101006');

View File

@ -859,3 +859,11 @@ history_value_101:
created_at: '2023-03-31 14:38:40.000421' created_at: '2023-03-31 14:38:40.000421'
updated_at: '2023-03-31 14:38:40.000421' updated_at: '2023-03-31 14:38:40.000421'
invoicing_profile_id: 1 invoicing_profile_id: 1
history_value_102:
id: 102
setting_id: 101
value: 'false'
created_at: '2023-03-31 14:38:40.000421'
updated_at: '2023-03-31 14:38:40.000421'
invoicing_profile_id: 1

View File

@ -592,3 +592,9 @@ setting_100:
name: family_account name: family_account
created_at: 2023-03-31 14:38:40.000421500 Z created_at: 2023-03-31 14:38:40.000421500 Z
updated_at: 2023-03-31 14:38:40.000421500 Z updated_at: 2023-03-31 14:38:40.000421500 Z
setting_101:
id: 101
name: child_validation_required
created_at: 2023-03-31 14:38:40.000421500 Z
updated_at: 2023-03-31 14:38:40.000421500 Z