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:
parent
ecc4fde47f
commit
f1d3fdf2de
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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']));
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.user-validation {
|
.user-validation, .child-validation {
|
||||||
label {
|
label {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -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"
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -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
|
||||||
|
3
app/views/api/children/show.json.jbuilder
Normal file
3
app/views/api/children/show.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# forzen_string_literal: true
|
||||||
|
|
||||||
|
json.partial! 'child', child: @child
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
8
db/migrate/20230525101006_add_validated_at_to_child.rb
Normal file
8
db/migrate/20230525101006_add_validated_at_to_child.rb
Normal 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
|
@ -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)
|
||||||
|
@ -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');
|
||||||
|
|
||||||
|
|
||||||
|
8
test/fixtures/history_values.yml
vendored
8
test/fixtures/history_values.yml
vendored
@ -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
|
||||||
|
6
test/fixtures/settings.yml
vendored
6
test/fixtures/settings.yml
vendored
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user