diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb index 618d859fb..bd51d18b5 100644 --- a/app/controllers/api/children_controller.rb +++ b/app/controllers/api/children_controller.rb @@ -50,6 +50,9 @@ class API::ChildrenController < API::APIController end def child_params - params.require(:child).permit(:first_name, :last_name, :email, :phone, :birthday, :user_id) + params.require(:child).permit(:first_name, :last_name, :email, :phone, :birthday, :user_id, + supporting_document_files_attributes: %i[id supportable_id supportable_type + supporting_document_type_id + attachment _destroy]) end end diff --git a/app/controllers/api/supporting_document_files_controller.rb b/app/controllers/api/supporting_document_files_controller.rb index 34868aacb..93d7b86b5 100644 --- a/app/controllers/api/supporting_document_files_controller.rb +++ b/app/controllers/api/supporting_document_files_controller.rb @@ -48,6 +48,6 @@ class API::SupportingDocumentFilesController < API::APIController # Never trust parameters from the scary internet, only allow the white list through. def supporting_document_file_params - params.required(:supporting_document_file).permit(:supporting_document_type_id, :attachment, :user_id) + params.required(:supporting_document_file).permit(:supporting_document_type_id, :attachment, :supportable_id, :supportable_type) end end diff --git a/app/controllers/api/supporting_document_refusals_controller.rb b/app/controllers/api/supporting_document_refusals_controller.rb index 1da9cabe4..8cf619e8e 100644 --- a/app/controllers/api/supporting_document_refusals_controller.rb +++ b/app/controllers/api/supporting_document_refusals_controller.rb @@ -27,6 +27,7 @@ class API::SupportingDocumentRefusalsController < API::APIController # Never trust parameters from the scary internet, only allow the white list through. def supporting_document_refusal_params - params.required(:supporting_document_refusal).permit(:message, :operator_id, :user_id, supporting_document_type_ids: []) + params.required(:supporting_document_refusal).permit(:message, :operator_id, :supportable_id, :supportable_type, + supporting_document_type_ids: []) end end diff --git a/app/controllers/api/supporting_document_types_controller.rb b/app/controllers/api/supporting_document_types_controller.rb index 68a57569a..dc2223ab2 100644 --- a/app/controllers/api/supporting_document_types_controller.rb +++ b/app/controllers/api/supporting_document_types_controller.rb @@ -45,6 +45,6 @@ class API::SupportingDocumentTypesController < API::APIController end def supporting_document_type_params - params.require(:supporting_document_type).permit(:name, group_ids: []) + params.require(:supporting_document_type).permit(:name, :document_type, group_ids: []) end end diff --git a/app/frontend/src/javascript/api/child.ts b/app/frontend/src/javascript/api/child.ts index 1ff948219..586964973 100644 --- a/app/frontend/src/javascript/api/child.ts +++ b/app/frontend/src/javascript/api/child.ts @@ -15,12 +15,22 @@ export default class ChildAPI { } static async create (child: Child): Promise { - const res: AxiosResponse = await apiClient.post('/api/children', { child }); + const data = ApiLib.serializeAttachments(child, 'child', ['supporting_document_files_attributes']); + const res: AxiosResponse = await apiClient.post('/api/children', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); return res?.data; } static async update (child: Child): Promise { - const res: AxiosResponse = await apiClient.patch(`/api/children/${child.id}`, { child }); + const data = ApiLib.serializeAttachments(child, 'child', ['supporting_document_files_attributes']); + const res: AxiosResponse = await apiClient.put(`/api/children/${child.id}`, data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); return res?.data; } diff --git a/app/frontend/src/javascript/components/family-account/child-form.tsx b/app/frontend/src/javascript/components/family-account/child-form.tsx index 2a70e4b9b..33834ebe8 100644 --- a/app/frontend/src/javascript/components/family-account/child-form.tsx +++ b/app/frontend/src/javascript/components/family-account/child-form.tsx @@ -1,33 +1,34 @@ import React from 'react'; -import { useForm } from 'react-hook-form'; +import { useForm, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import moment from 'moment'; import { Child } from '../../models/child'; -import { TDateISODate } from '../../typings/date-iso'; import { FormInput } from '../form/form-input'; import { FabButton } from '../base/fab-button'; +import { FormFileUpload } from '../form/form-file-upload'; +import { FileType } from '../../models/file'; +import { SupportingDocumentType } from '../../models/supporting-document-type'; interface ChildFormProps { child: Child; - onChange: (field: string, value: string | TDateISODate) => void; onSubmit: (data: Child) => void; + supportingDocumentsTypes: Array; } /** * A form for creating or editing a child. */ -export const ChildForm: React.FC = ({ child, onChange, onSubmit }) => { +export const ChildForm: React.FC = ({ child, onSubmit, supportingDocumentsTypes }) => { const { t } = useTranslation('public'); - const { register, formState, handleSubmit } = useForm({ + const { register, formState, handleSubmit, setValue, control } = useForm({ defaultValues: child }); + const output = useWatch({ control }); // eslint-disable-line - /** - * Handle the change of a child form field - */ - const handleChange = (event: React.ChangeEvent): void => { - onChange(event.target.id, event.target.value); + const getSupportingDocumentsTypeName = (id: number): string => { + const supportingDocumentType = supportingDocumentsTypes.find((supportingDocumentType) => supportingDocumentType.id === id); + return supportingDocumentType ? supportingDocumentType.name : ''; }; return ( @@ -41,14 +42,12 @@ export const ChildForm: React.FC = ({ child, onChange, onSubmit rules={{ required: true }} formState={formState} label={t('app.public.child_form.first_name')} - onChange={handleChange} /> = ({ child, onChange, onSubmit label={t('app.public.child_form.birthday')} type="date" max={moment().subtract(18, 'year').format('YYYY-MM-DD')} - onChange={handleChange} /> + {output.supporting_document_files_attributes.map((sf, index) => { + return ( + + ); + })}
diff --git a/app/frontend/src/javascript/components/family-account/child-modal.tsx b/app/frontend/src/javascript/components/family-account/child-modal.tsx index baa9af408..c108659b5 100644 --- a/app/frontend/src/javascript/components/family-account/child-modal.tsx +++ b/app/frontend/src/javascript/components/family-account/child-modal.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; -import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FabModal, ModalSize } from '../base/fab-modal'; import { Child } from '../../models/child'; -import { TDateISODate } from '../../typings/date-iso'; import ChildAPI from '../../api/child'; import { ChildForm } from './child-form'; +import { SupportingDocumentType } from '../../models/supporting-document-type'; interface ChildModalProps { child?: Child; @@ -13,23 +12,19 @@ interface ChildModalProps { toggleModal: () => void; onSuccess: (child: Child) => void; onError: (error: string) => void; + supportingDocumentsTypes: Array; } /** * A modal for creating or editing a child. */ -export const ChildModal: React.FC = ({ child, isOpen, toggleModal, onSuccess, onError }) => { +export const ChildModal: React.FC = ({ child, isOpen, toggleModal, onSuccess, onError, supportingDocumentsTypes }) => { const { t } = useTranslation('public'); - const [data, setData] = useState(child); - - useEffect(() => { - setData(child); - }, [child]); /** * Save the child to the API */ - const handleSaveChild = async (): Promise => { + const handleSaveChild = async (data: Child): Promise => { try { if (child?.id) { await ChildAPI.update(data); @@ -43,25 +38,14 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod } }; - /** - * Handle the change of a child form field - */ - const handleChildChanged = (field: string, value: string | TDateISODate): void => { - setData({ - ...data, - [field]: value - }); - }; - return ( - + confirmButton={false} > + ); }; diff --git a/app/frontend/src/javascript/components/family-account/children-list.tsx b/app/frontend/src/javascript/components/family-account/children-list.tsx index a383f95a5..4e3e18e46 100644 --- a/app/frontend/src/javascript/components/family-account/children-list.tsx +++ b/app/frontend/src/javascript/components/family-account/children-list.tsx @@ -9,6 +9,8 @@ import { IApplication } from '../../models/application'; import { ChildModal } from './child-modal'; import { ChildItem } from './child-item'; import { FabButton } from '../base/fab-button'; +import { SupportingDocumentType } from '../../models/supporting-document-type'; +import SupportingDocumentTypeAPI from '../../api/supporting-document-type'; declare const Application: IApplication; @@ -27,9 +29,13 @@ export const ChildrenList: React.FC = ({ currentUser, onError const [children, setChildren] = useState>([]); const [isOpenChildModal, setIsOpenChildModal] = useState(false); const [child, setChild] = useState(); + const [supportingDocumentsTypes, setSupportingDocumentsTypes] = useState>([]); useEffect(() => { ChildAPI.index({ user_id: currentUser.id }).then(setChildren); + SupportingDocumentTypeAPI.index({ document_type: 'Child' }).then(tData => { + setSupportingDocumentsTypes(tData); + }); }, [currentUser]); /** @@ -37,7 +43,12 @@ export const ChildrenList: React.FC = ({ currentUser, onError */ const addChild = () => { setIsOpenChildModal(true); - setChild({ user_id: currentUser.id } as Child); + setChild({ + user_id: currentUser.id, + supporting_document_files_attributes: supportingDocumentsTypes.map(t => { + return { supporting_document_type_id: t.id }; + }) + } as Child); }; /** @@ -45,7 +56,13 @@ export const ChildrenList: React.FC = ({ currentUser, onError */ const editChild = (child: Child) => { setIsOpenChildModal(true); - setChild(child); + setChild({ + ...child, + supporting_document_files_attributes: supportingDocumentsTypes.map(t => { + const file = child.supporting_document_files_attributes.find(f => f.supporting_document_type_id === t.id); + return file || { supporting_document_type_id: t.id }; + }) + } as Child); }; /** @@ -78,7 +95,7 @@ export const ChildrenList: React.FC = ({ currentUser, onError ))}
- setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} /> + setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} /> ); }; diff --git a/app/frontend/src/javascript/components/form/form-file-upload.tsx b/app/frontend/src/javascript/components/form/form-file-upload.tsx index 253974e96..18d25fbae 100644 --- a/app/frontend/src/javascript/components/form/form-file-upload.tsx +++ b/app/frontend/src/javascript/components/form/form-file-upload.tsx @@ -19,12 +19,13 @@ type FormFileUploadProps = FormComponent & AbstractF accept?: string, onFileChange?: (value: FileType) => void, onFileRemove?: () => void, + showRemoveButton?: boolean, } /** * This component allows to upload file, in forms managed by react-hook-form. */ -export const FormFileUpload = ({ id, label, register, defaultFile, className, rules, disabled, error, warning, formState, onFileChange, onFileRemove, accept, setValue }: FormFileUploadProps) => { +export const FormFileUpload = ({ id, label, register, defaultFile, className, rules, disabled, error, warning, formState, onFileChange, onFileRemove, accept, setValue, showRemoveButton = true }: FormFileUploadProps) => { const { t } = useTranslation('shared'); const [file, setFile] = useState(defaultFile); @@ -100,7 +101,7 @@ export const FormFileUpload = ({ id, label, re id={`${id}[attachment_files]`} onChange={onFileSelected} placeholder={placeholder()}/> - {hasFile() && + {showRemoveButton && hasFile() && } className="is-main" /> } diff --git a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-files.tsx b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-files.tsx index 8c5ecf5f0..a1b5b4f6b 100644 --- a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-files.tsx +++ b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-files.tsx @@ -49,7 +49,7 @@ export const SupportingDocumentsFiles: React.FC = SupportingDocumentTypeAPI.index({ group_id: currentUser.group_id }).then(tData => { setSupportingDocumentsTypes(tData); }); - SupportingDocumentFileAPI.index({ user_id: currentUser.id }).then(fData => { + SupportingDocumentFileAPI.index({ supportable_id: currentUser.id, supportable_type: 'User' }).then(fData => { setSupportingDocumentsFiles(fData); }); }, []); @@ -106,7 +106,8 @@ export const SupportingDocumentsFiles: React.FC = for (const proofOfIdentityTypeId of Object.keys(files)) { const formData = new FormData(); - formData.append('supporting_document_file[user_id]', currentUser.id.toString()); + formData.append('supporting_document_file[supportable_id]', currentUser.id.toString()); + formData.append('supporting_document_file[supportable_type]', 'User'); formData.append('supporting_document_file[supporting_document_type_id]', proofOfIdentityTypeId); formData.append('supporting_document_file[attachment]', files[proofOfIdentityTypeId]); const proofOfIdentityFile = getSupportingDocumentsFileByType(parseInt(proofOfIdentityTypeId, 10)); @@ -117,7 +118,7 @@ export const SupportingDocumentsFiles: React.FC = } } if (Object.keys(files).length > 0) { - SupportingDocumentFileAPI.index({ user_id: currentUser.id }).then(fData => { + SupportingDocumentFileAPI.index({ supportable_id: currentUser.id, supportable_type: 'User' }).then(fData => { setSupportingDocumentsFiles(fData); setFiles({}); onSuccess(t('app.logged.dashboard.supporting_documents_files.file_successfully_uploaded')); diff --git a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-refusal-modal.tsx b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-refusal-modal.tsx index 1e2336024..0439a5c8e 100644 --- a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-refusal-modal.tsx +++ b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-refusal-modal.tsx @@ -27,7 +27,8 @@ export const SupportingDocumentsRefusalModal: React.FC({ id: null, operator_id: operator.id, - user_id: member.id, + supportable_id: member.id, + supportable_type: 'User', supporting_document_type_ids: [], message: '' }); diff --git a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-form.tsx b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-form.tsx index 3e26b2963..5005f4d4b 100644 --- a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-form.tsx +++ b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-form.tsx @@ -63,13 +63,15 @@ export const SupportingDocumentsTypeForm: React.FC
-
- +
+ }
} diff --git a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-modal.tsx b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-modal.tsx index be457f531..c42c490cd 100644 --- a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-modal.tsx +++ b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-type-modal.tsx @@ -14,18 +14,19 @@ interface SupportingDocumentsTypeModalProps { onError: (message: string) => void, groups: Array, proofOfIdentityType?: SupportingDocumentType, + documentType: 'User' | 'Child', } /** * Modal dialog to create/edit a supporting documents type */ -export const SupportingDocumentsTypeModal: React.FC = ({ isOpen, toggleModal, onSuccess, onError, proofOfIdentityType, groups }) => { +export const SupportingDocumentsTypeModal: React.FC = ({ isOpen, toggleModal, onSuccess, onError, proofOfIdentityType, groups, documentType }) => { const { t } = useTranslation('admin'); - const [data, setData] = useState({ id: proofOfIdentityType?.id, group_ids: proofOfIdentityType?.group_ids || [], name: proofOfIdentityType?.name || '' }); + const [data, setData] = useState({ id: proofOfIdentityType?.id, group_ids: proofOfIdentityType?.group_ids || [], name: proofOfIdentityType?.name || '', document_type: documentType }); useEffect(() => { - setData({ id: proofOfIdentityType?.id, group_ids: proofOfIdentityType?.group_ids || [], name: proofOfIdentityType?.name || '' }); + setData({ id: proofOfIdentityType?.id, group_ids: proofOfIdentityType?.group_ids || [], name: proofOfIdentityType?.name || '', document_type: documentType }); }, [proofOfIdentityType]); /** @@ -63,7 +64,7 @@ export const SupportingDocumentsTypeModal: React.FC { - return !data.name || data.group_ids.length === 0; + return !data.name || (documentType === 'User' && data.group_ids.length === 0); }; return ( diff --git a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-types-list.tsx b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-types-list.tsx index 8fb888908..35efbd874 100644 --- a/app/frontend/src/javascript/components/supporting-documents/supporting-documents-types-list.tsx +++ b/app/frontend/src/javascript/components/supporting-documents/supporting-documents-types-list.tsx @@ -21,12 +21,13 @@ declare const Application: IApplication; interface SupportingDocumentsTypesListProps { onSuccess: (message: string) => void, onError: (message: string) => void, + documentType: 'User' | 'Child', } /** * This component shows a list of all types of supporting documents (e.g. student ID, Kbis extract, etc.) */ -const SupportingDocumentsTypesList: React.FC = ({ onSuccess, onError }) => { +const SupportingDocumentsTypesList: React.FC = ({ onSuccess, onError, documentType }) => { const { t } = useTranslation('admin'); // list of displayed supporting documents type @@ -48,7 +49,7 @@ const SupportingDocumentsTypesList: React.FC useEffect(() => { GroupAPI.index({ disabled: false }).then(data => { setGroups(data); - SupportingDocumentTypeAPI.index().then(pData => { + SupportingDocumentTypeAPI.index({ document_type: documentType }).then(pData => { setSupportingDocumentsTypes(pData); }); }); @@ -91,7 +92,7 @@ const SupportingDocumentsTypesList: React.FC */ const onSaveTypeSuccess = (message: string): void => { setModalIsOpen(false); - SupportingDocumentTypeAPI.index().then(pData => { + SupportingDocumentTypeAPI.index({ document_type: documentType }).then(pData => { setSupportingDocumentsTypes(orderTypes(pData, supportingDocumentsTypeOrder)); onSuccess(message); }).catch((error) => { @@ -121,7 +122,7 @@ const SupportingDocumentsTypesList: React.FC */ const onDestroySuccess = (message: string): void => { setDestroyModalIsOpen(false); - SupportingDocumentTypeAPI.index().then(pData => { + SupportingDocumentTypeAPI.index({ document_type: documentType }).then(pData => { setSupportingDocumentsTypes(pData); setSupportingDocumentsTypes(orderTypes(pData, supportingDocumentsTypeOrder)); onSuccess(message); @@ -190,83 +191,150 @@ const SupportingDocumentsTypesList: React.FC window.location.href = '/#!/admin/members?tabs=1'; }; - return ( - - {t('app.admin.settings.account.supporting_documents_types_list.add_supporting_documents_types')} -
}> -
-
-

{t('app.admin.settings.account.supporting_documents_types_list.supporting_documents_type_info')}

- - - {t('app.admin.settings.account.supporting_documents_types_list.create_groups')} - + if (documentType === 'User') { + return ( + + {t('app.admin.settings.account.supporting_documents_types_list.add_supporting_documents_types')} +
}> +
+
+

{t('app.admin.settings.account.supporting_documents_types_list.supporting_documents_type_info')}

+ + + {t('app.admin.settings.account.supporting_documents_types_list.create_groups')} + +
+ +
+

{t('app.admin.settings.account.supporting_documents_types_list.supporting_documents_type_title')}

+ {t('app.admin.settings.account.supporting_documents_types_list.add_type')} +
+ + + + + + + + + + + + + + {supportingDocumentsTypes.map(poit => { + return ( + + + + + + ); + })} + +
+ + {t('app.admin.settings.account.supporting_documents_types_list.group_name')} + + + + + {t('app.admin.settings.account.supporting_documents_types_list.name')} + + +
{getGroupsNames(poit.group_ids)}{poit.name} +
+ + + + + + +
+
+ {!hasTypes() && ( +

+ +

+ )}
+ + ); + } else if (documentType === 'Child') { + return ( +
+
+
+

{t('app.admin.settings.account.supporting_documents_types_list.supporting_documents_type_title')}

+ {t('app.admin.settings.account.supporting_documents_types_list.add_type')} +
-
-

{t('app.admin.settings.account.supporting_documents_types_list.supporting_documents_type_title')}

- {t('app.admin.settings.account.supporting_documents_types_list.add_type')} + + + + + + + + + + + + {supportingDocumentsTypes.map(poit => { + return ( + + + + + ); + })} + +
+ + {t('app.admin.settings.account.supporting_documents_types_list.name')} + + +
{poit.name} +
+ + + + + + +
+
+ {!hasTypes() && ( +

+ +

+ )}
- - - - - - - - - - - - - - {supportingDocumentsTypes.map(poit => { - return ( - - - - - - ); - })} - -
- - {t('app.admin.settings.account.supporting_documents_types_list.group_name')} - - - - - {t('app.admin.settings.account.supporting_documents_types_list.name')} - - -
{getGroupsNames(poit.group_ids)}{poit.name} -
- - - - - - -
-
- {!hasTypes() && ( -

- -

- )}
- - ); + ); + } else { + return null; + } }; const SupportingDocumentsTypesListWrapper: React.FC = (props) => { @@ -277,4 +345,4 @@ const SupportingDocumentsTypesListWrapper: React.FC { setDocumentsTypes(tData); }); - SupportingDocumentFileAPI.index({ user_id: member.id }).then(fData => { + SupportingDocumentFileAPI.index({ supportable_id: member.id, supportable_type: 'User' }).then(fData => { setDocumentsFiles(fData); }); }, []); diff --git a/app/frontend/src/javascript/lib/api.ts b/app/frontend/src/javascript/lib/api.ts index 1ef3b9b82..76e99d6fc 100644 --- a/app/frontend/src/javascript/lib/api.ts +++ b/app/frontend/src/javascript/lib/api.ts @@ -35,6 +35,9 @@ export default class ApiLib { if (file?.is_main) { data.set(`${name}[${attr}][${i}][is_main]`, file.is_main.toString()); } + if (file?.supporting_document_type_id) { + data.set(`${name}[${attr}][${i}][supporting_document_type_id]`, file.supporting_document_type_id.toString()); + } }); } else { if (object[attr]?.attachment_files && object[attr]?.attachment_files[0]) { diff --git a/app/frontend/src/javascript/models/child.ts b/app/frontend/src/javascript/models/child.ts index cea24bf1d..be33bdec6 100644 --- a/app/frontend/src/javascript/models/child.ts +++ b/app/frontend/src/javascript/models/child.ts @@ -12,5 +12,15 @@ export interface Child { email?: string, phone?: string, birthday: TDateISODate, - user_id: number + user_id: number, + supporting_document_files_attributes?: Array<{ + id?: number, + supportable_id?: number, + supportable_type?: 'User' | 'Child', + supporting_document_type_id: number, + attachment?: File, + attachment_name?: string, + attachment_url?: string, + _destroy?: boolean + }>, } diff --git a/app/frontend/src/javascript/models/supporting-document-file.ts b/app/frontend/src/javascript/models/supporting-document-file.ts index 581c75fb0..c762b4664 100644 --- a/app/frontend/src/javascript/models/supporting-document-file.ts +++ b/app/frontend/src/javascript/models/supporting-document-file.ts @@ -1,12 +1,14 @@ import { ApiFilter } from './api'; export interface SupportingDocumentFileIndexFilter extends ApiFilter { - user_id: number, + supportable_id: number, + supportable_type?: 'User' | 'Child', } export interface SupportingDocumentFile { id?: number, attachment?: string, - user_id?: number, + supportable_id?: number, + supportable_type?: 'User' | 'Child', supporting_document_type_id: number, } diff --git a/app/frontend/src/javascript/models/supporting-document-refusal.ts b/app/frontend/src/javascript/models/supporting-document-refusal.ts index 29f045bbe..f220baf4e 100644 --- a/app/frontend/src/javascript/models/supporting-document-refusal.ts +++ b/app/frontend/src/javascript/models/supporting-document-refusal.ts @@ -1,13 +1,15 @@ import { ApiFilter } from './api'; export interface SupportingDocumentRefusalIndexFilter extends ApiFilter { - user_id: number, + supportable_id: number, + supportable_type: 'User' | 'Child', } export interface SupportingDocumentRefusal { id: number, message: string, - user_id: number, + supportable_id: number, + supportable_type: 'User' | 'Child', operator_id: number, supporting_document_type_ids: Array, } diff --git a/app/frontend/src/javascript/models/supporting-document-type.ts b/app/frontend/src/javascript/models/supporting-document-type.ts index a2dcf3c78..2c590e1f1 100644 --- a/app/frontend/src/javascript/models/supporting-document-type.ts +++ b/app/frontend/src/javascript/models/supporting-document-type.ts @@ -2,10 +2,12 @@ import { ApiFilter } from './api'; export interface SupportingDocumentTypeIndexfilter extends ApiFilter { group_id?: number, + document_type?: 'User' | 'Child' } export interface SupportingDocumentType { id: number, name: string, - group_ids: Array + group_ids: Array, + document_type: 'User' | 'Child' } diff --git a/app/frontend/templates/admin/settings/compte.html b/app/frontend/templates/admin/settings/compte.html index 3935b9c98..af485e8a1 100644 --- a/app/frontend/templates/admin/settings/compte.html +++ b/app/frontend/templates/admin/settings/compte.html @@ -62,6 +62,12 @@ on-error="onError">
+
+
+ +
+
+

{{ 'app.admin.settings.captcha' }}

@@ -167,4 +173,4 @@
- + diff --git a/app/models/child.rb b/app/models/child.rb index 93332bde8..7dd5c70ed 100644 --- a/app/models/child.rb +++ b/app/models/child.rb @@ -4,9 +4,13 @@ class Child < ApplicationRecord belongs_to :user + has_many :supporting_document_files, as: :supportable, dependent: :destroy + accepts_nested_attributes_for :supporting_document_files, allow_destroy: true, reject_if: :all_blank + has_many :supporting_document_refusals, as: :supportable, dependent: :destroy + validates :first_name, presence: true validates :last_name, presence: true - validates :email, presence: true, format: { with: Devise.email_regexp } + # validates :email, presence: true, format: { with: Devise.email_regexp } validate :validate_age # birthday should less than 18 years ago diff --git a/app/models/supporting_document_file.rb b/app/models/supporting_document_file.rb index 77b2132eb..d672191fb 100644 --- a/app/models/supporting_document_file.rb +++ b/app/models/supporting_document_file.rb @@ -6,7 +6,7 @@ class SupportingDocumentFile < ApplicationRecord mount_uploader :attachment, SupportingDocumentFileUploader belongs_to :supporting_document_type - belongs_to :user + belongs_to :supportable, polymorphic: true validates :attachment, file_size: { maximum: ENV.fetch('MAX_SUPPORTING_DOCUMENT_FILE_SIZE', 5.megabytes).to_i } end diff --git a/app/models/supporting_document_refusal.rb b/app/models/supporting_document_refusal.rb index 29157e932..843bc46c8 100644 --- a/app/models/supporting_document_refusal.rb +++ b/app/models/supporting_document_refusal.rb @@ -2,7 +2,7 @@ # An admin can mark an uploaded document as refused, this will notify the member class SupportingDocumentRefusal < ApplicationRecord - belongs_to :user + belongs_to :supportable, polymorphic: true belongs_to :operator, class_name: 'User', inverse_of: :supporting_document_refusals has_many :supporting_document_refusals_types, dependent: :destroy has_many :supporting_document_types, through: :supporting_document_refusals_types diff --git a/app/models/supporting_document_type.rb b/app/models/supporting_document_type.rb index 98c596be1..d0be09fe0 100644 --- a/app/models/supporting_document_type.rb +++ b/app/models/supporting_document_type.rb @@ -8,4 +8,6 @@ class SupportingDocumentType < ApplicationRecord has_many :supporting_document_refusals_types, dependent: :destroy has_many :supporting_document_refusals, through: :supporting_document_refusals_types + + validates :document_type, presence: true, inclusion: { in: %w[User Child] } end diff --git a/app/models/user.rb b/app/models/user.rb index e8a31f6d0..bfcca8093 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,8 +47,8 @@ class User < ApplicationRecord has_many :accounting_periods, foreign_key: 'closed_by', dependent: :nullify, inverse_of: :user - has_many :supporting_document_files, dependent: :destroy - has_many :supporting_document_refusals, dependent: :destroy + has_many :supporting_document_files, as: :supportable, dependent: :destroy + has_many :supporting_document_refusals, as: :supportable, dependent: :destroy has_many :notifications, as: :receiver, dependent: :destroy has_many :notification_preferences, dependent: :destroy diff --git a/app/policies/supporting_document_file_policy.rb b/app/policies/supporting_document_file_policy.rb index 9cfaa10f2..2b91f5aab 100644 --- a/app/policies/supporting_document_file_policy.rb +++ b/app/policies/supporting_document_file_policy.rb @@ -6,15 +6,11 @@ class SupportingDocumentFilePolicy < ApplicationPolicy user.privileged? end - def create? - user.privileged? or record.user_id == user.id - end - - def update? - user.privileged? or record.user_id == user.id - end - - def download? - user.privileged? or record.user_id == user.id + %w[create update download].each do |action| + define_method "#{action}?" do + user.privileged? || + (record.supportable_type == 'User' && record.supportable_id.to_i == user.id) || + (record.supportable_type == 'Child' && user.children.exists?(id: record.supportable_id.to_i)) + end end end diff --git a/app/services/supporting_document_file_service.rb b/app/services/supporting_document_file_service.rb index 49cd017ec..67c9b1eef 100644 --- a/app/services/supporting_document_file_service.rb +++ b/app/services/supporting_document_file_service.rb @@ -4,23 +4,32 @@ class SupportingDocumentFileService def self.list(operator, filters = {}) files = [] - if filters[:user_id].present? && (operator.privileged? || filters[:user_id].to_i == operator.id) - files = SupportingDocumentFile.where(user_id: filters[:user_id]) + if filters[:supportable_id].present? && can_list?(operator, filters[:supportable_id], filters[:supportable_type]) + files = SupportingDocumentFile.where(supportable_id: filters[:supportable_id], supportable_type: filters[:supportable_type]) end files end + def self.can_list?(operator, supportable_id, supportable_type) + operator.privileged? || + (supportable_type == 'User' && supportable_id.to_i == operator.id) || + (supportable_type == 'Child' && operator.children.exists?(id: supportable_id.to_i)) + end + def self.create(supporting_document_file) saved = supporting_document_file.save if saved - user = User.find(supporting_document_file.user_id) all_files_are_upload = true - user.group.supporting_document_types.each do |type| - file = type.supporting_document_files.find_by(user_id: supporting_document_file.user_id) - all_files_are_upload = false unless file + if supporting_document_file.supportable_type == 'User' + user = supporting_document_file.supportable + user.group.supporting_document_types.each do |type| + file = type.supporting_document_files.find_by(supportable_id: supporting_document_file.supportable_id, + supportable_type: supporting_document_file.supportable_type) + all_files_are_upload = false unless file + end end - if all_files_are_upload + if all_files_are_upload && (supporting_document_file.supportable_type == 'User') NotificationCenter.call type: 'notify_admin_user_supporting_document_files_created', receiver: User.admins_and_managers, attached_object: user @@ -32,13 +41,16 @@ class SupportingDocumentFileService def self.update(supporting_document_file, params) updated = supporting_document_file.update(params) if updated - user = supporting_document_file.user all_files_are_upload = true - user.group.supporting_document_types.each do |type| - file = type.supporting_document_files.find_by(user_id: supporting_document_file.user_id) - all_files_are_upload = false unless file + if supporting_document_file.supportable_type == 'User' + user = supporting_document_file.supportable + user.group.supporting_document_types.each do |type| + file = type.supporting_document_files.find_by(supportable_id: supporting_document_file.supportable_id, + supportable_type: supporting_document_file.supportable_type) + all_files_are_upload = false unless file + end end - if all_files_are_upload + if all_files_are_upload && (supporting_document_file.supportable_type == 'User') NotificationCenter.call type: 'notify_admin_user_supporting_document_files_updated', receiver: User.admins_and_managers, attached_object: supporting_document_file diff --git a/app/services/supporting_document_refusal_service.rb b/app/services/supporting_document_refusal_service.rb index 10bf7f52a..42c78d68f 100644 --- a/app/services/supporting_document_refusal_service.rb +++ b/app/services/supporting_document_refusal_service.rb @@ -4,19 +4,22 @@ class SupportingDocumentRefusalService def self.list(filters = {}) refusals = [] - refusals = SupportingDocumentRefusal.where(user_id: filters[:user_id]) if filters[:user_id].present? + if filters[:supportable_id].present? + refusals = SupportingDocumentRefusal.where(supportable_id: filters[:supportable_id], + supportable_type: filters[:supportable_type]) + end refusals end def self.create(supporting_document_refusal) saved = supporting_document_refusal.save - if saved + if saved && supporting_document_refusal.supportable_type == 'User' NotificationCenter.call type: 'notify_admin_user_supporting_document_refusal', receiver: User.admins_and_managers, attached_object: supporting_document_refusal NotificationCenter.call type: 'notify_user_supporting_document_refusal', - receiver: supporting_document_refusal.user, + receiver: supporting_document_refusal.supportable, attached_object: supporting_document_refusal end saved diff --git a/app/services/supporting_document_type_service.rb b/app/services/supporting_document_type_service.rb index e393a464d..342a567d2 100644 --- a/app/services/supporting_document_type_service.rb +++ b/app/services/supporting_document_type_service.rb @@ -9,7 +9,7 @@ class SupportingDocumentTypeService group.supporting_document_types.includes(:groups) else - SupportingDocumentType.all + SupportingDocumentType.where(document_type: filters[:document_type] || 'User') end end end diff --git a/app/views/api/children/_child.json.jbuilder b/app/views/api/children/_child.json.jbuilder index d80836a4e..d65d54e3c 100644 --- a/app/views/api/children/_child.json.jbuilder +++ b/app/views/api/children/_child.json.jbuilder @@ -1,3 +1,12 @@ # frozen_string_literal: true json.extract! child, :id, :first_name, :last_name, :email, :birthday, :phone, :user_id +json.supporting_document_files_attributes child.supporting_document_files do |f| + json.id f.id + json.supportable_id f.supportable_id + json.supportable_type f.supportable_type + json.supporting_document_type_id f.supporting_document_type_id + json.attachment f.attachment.file.filename + json.attachment_name f.attachment_identifier + json.attachment_url f.attachment_url +end diff --git a/app/views/api/notifications/_notify_admin_user_supporting_document_files_updated.json.jbuilder b/app/views/api/notifications/_notify_admin_user_supporting_document_files_updated.json.jbuilder index 0f85e7d9b..070055a91 100644 --- a/app/views/api/notifications/_notify_admin_user_supporting_document_files_updated.json.jbuilder +++ b/app/views/api/notifications/_notify_admin_user_supporting_document_files_updated.json.jbuilder @@ -2,4 +2,4 @@ json.title notification.notification_type json.description t('.supporting_document_files_uploaded', - NAME: notification.attached_object&.user&.profile&.full_name || t('api.notifications.deleted_user')) + NAME: notification.attached_object&.supportable&.profile&.full_name || t('api.notifications.deleted_user')) diff --git a/app/views/api/notifications/_notify_admin_user_supporting_document_refusal.json.jbuilder b/app/views/api/notifications/_notify_admin_user_supporting_document_refusal.json.jbuilder index 93fb21f32..e92b63f79 100644 --- a/app/views/api/notifications/_notify_admin_user_supporting_document_refusal.json.jbuilder +++ b/app/views/api/notifications/_notify_admin_user_supporting_document_refusal.json.jbuilder @@ -2,4 +2,4 @@ json.title notification.notification_type json.description t('.refusal', - NAME: notification.attached_object&.user&.profile&.full_name || t('api.notifications.deleted_user')) + NAME: notification.attached_object&.supportable&.profile&.full_name || t('api.notifications.deleted_user')) diff --git a/app/views/api/supporting_document_files/_supporting_document_file.json.jbuilder b/app/views/api/supporting_document_files/_supporting_document_file.json.jbuilder index 361abd8a1..89fbb7f1d 100644 --- a/app/views/api/supporting_document_files/_supporting_document_file.json.jbuilder +++ b/app/views/api/supporting_document_files/_supporting_document_file.json.jbuilder @@ -1,4 +1,4 @@ # frozen_string_literal: true -json.extract! supporting_document_file, :id, :user_id, :supporting_document_type_id +json.extract! supporting_document_file, :id, :supportable_id, :supportable_type, :supporting_document_type_id json.attachment supporting_document_file.attachment.file.filename diff --git a/app/views/api/supporting_document_refusals/_supporting_document_refusal.json.jbuilder b/app/views/api/supporting_document_refusals/_supporting_document_refusal.json.jbuilder index 71e6e28a1..d5e0bdd1c 100644 --- a/app/views/api/supporting_document_refusals/_supporting_document_refusal.json.jbuilder +++ b/app/views/api/supporting_document_refusals/_supporting_document_refusal.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! supporting_document_refusal, :id, :user_id, :operator_id, :supporting_document_type_ids, :message +json.extract! supporting_document_refusal, :id, :supportable_id, :supportable_type, :operator_id, :supporting_document_type_ids, :message diff --git a/app/views/api/supporting_document_types/_supporting_document_type.json.jbuilder b/app/views/api/supporting_document_types/_supporting_document_type.json.jbuilder index 68e5dec61..af33eefd5 100644 --- a/app/views/api/supporting_document_types/_supporting_document_type.json.jbuilder +++ b/app/views/api/supporting_document_types/_supporting_document_type.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! supporting_document_type, :id, :name, :group_ids +json.extract! supporting_document_type, :id, :name, :group_ids, :document_type diff --git a/app/views/notifications_mailer/notify_admin_user_supporting_document_files_updated.html.erb b/app/views/notifications_mailer/notify_admin_user_supporting_document_files_updated.html.erb index 7cae027c1..000e9cc5e 100644 --- a/app/views/notifications_mailer/notify_admin_user_supporting_document_files_updated.html.erb +++ b/app/views/notifications_mailer/notify_admin_user_supporting_document_files_updated.html.erb @@ -2,7 +2,7 @@

<%= t('.body.user_update_supporting_document_file', - NAME: @attached_object&.user&.profile&.full_name || t('api.notifications.deleted_user')) %> + NAME: @attached_object&.supportable&.profile&.full_name || t('api.notifications.deleted_user')) %>

  • <%= @attached_object.supporting_document_type.name %>
  • diff --git a/app/views/notifications_mailer/notify_admin_user_supporting_document_refusal.html.erb b/app/views/notifications_mailer/notify_admin_user_supporting_document_refusal.html.erb index 8a5c2cdef..8c720cfbc 100644 --- a/app/views/notifications_mailer/notify_admin_user_supporting_document_refusal.html.erb +++ b/app/views/notifications_mailer/notify_admin_user_supporting_document_refusal.html.erb @@ -2,7 +2,7 @@

    <%= t('.body.user_supporting_document_files_refusal', - NAME: @attached_object&.user&.profile&.full_name || t('api.notifications.deleted_user'), + NAME: @attached_object&.supportable&.profile&.full_name || t('api.notifications.deleted_user'), OPERATOR: @attached_object&.operator&.profile&.full_name || t('api.notifications.deleted_user')) %>

      diff --git a/db/migrate/20230524080448_add_ducument_type_to_supporting_document_type.rb b/db/migrate/20230524080448_add_ducument_type_to_supporting_document_type.rb new file mode 100644 index 000000000..f835e5646 --- /dev/null +++ b/db/migrate/20230524080448_add_ducument_type_to_supporting_document_type.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# add document_type to supporting_document_type +class AddDucumentTypeToSupportingDocumentType < ActiveRecord::Migration[7.0] + def change + add_column :supporting_document_types, :document_type, :string, default: 'User' + end +end diff --git a/db/migrate/20230524083558_change_user_id_to_supportable_from_supporting_document_file.rb b/db/migrate/20230524083558_change_user_id_to_supportable_from_supporting_document_file.rb new file mode 100644 index 000000000..59253357b --- /dev/null +++ b/db/migrate/20230524083558_change_user_id_to_supportable_from_supporting_document_file.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# change user_id to supportable from supporting_document_file +class ChangeUserIdToSupportableFromSupportingDocumentFile < ActiveRecord::Migration[7.0] + def change + rename_column :supporting_document_files, :user_id, :supportable_id + add_column :supporting_document_files, :supportable_type, :string, default: 'User' + end +end diff --git a/db/migrate/20230524110215_change_user_id_to_supportable_from_supporting_document_refusal.rb b/db/migrate/20230524110215_change_user_id_to_supportable_from_supporting_document_refusal.rb new file mode 100644 index 000000000..8c0663572 --- /dev/null +++ b/db/migrate/20230524110215_change_user_id_to_supportable_from_supporting_document_refusal.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# change user_id to supportable from supporting_document_refusal +class ChangeUserIdToSupportableFromSupportingDocumentRefusal < ActiveRecord::Migration[7.0] + def change + rename_column :supporting_document_refusals, :user_id, :supportable_id + add_column :supporting_document_refusals, :supportable_type, :string, default: 'User' + end +end diff --git a/db/structure.sql b/db/structure.sql index 35a9af3e8..794b8ec2a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3746,10 +3746,11 @@ ALTER SEQUENCE public.subscriptions_id_seq OWNED BY public.subscriptions.id; CREATE TABLE public.supporting_document_files ( id bigint NOT NULL, supporting_document_type_id bigint, - user_id bigint, + supportable_id bigint, attachment character varying, created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL + updated_at timestamp without time zone NOT NULL, + supportable_type character varying DEFAULT 'User'::character varying ); @@ -3778,11 +3779,12 @@ ALTER SEQUENCE public.supporting_document_files_id_seq OWNED BY public.supportin CREATE TABLE public.supporting_document_refusals ( id bigint NOT NULL, - user_id bigint, + supportable_id bigint, operator_id integer, message text, created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL + updated_at timestamp without time zone NOT NULL, + supportable_type character varying DEFAULT 'User'::character varying ); @@ -3823,7 +3825,8 @@ CREATE TABLE public.supporting_document_types ( id bigint NOT NULL, name character varying, created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL + updated_at timestamp without time zone NOT NULL, + document_type character varying DEFAULT 'User'::character varying ); @@ -7320,6 +7323,13 @@ CREATE INDEX index_subscriptions_on_plan_id ON public.subscriptions USING btree CREATE INDEX index_subscriptions_on_statistic_profile_id ON public.subscriptions USING btree (statistic_profile_id); +-- +-- Name: index_supporting_document_files_on_supportable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_supporting_document_files_on_supportable_id ON public.supporting_document_files USING btree (supportable_id); + + -- -- Name: index_supporting_document_files_on_supporting_document_type_id; Type: INDEX; Schema: public; Owner: - -- @@ -7328,17 +7338,10 @@ CREATE INDEX index_supporting_document_files_on_supporting_document_type_id ON p -- --- Name: index_supporting_document_files_on_user_id; Type: INDEX; Schema: public; Owner: - +-- Name: index_supporting_document_refusals_on_supportable_id; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_supporting_document_files_on_user_id ON public.supporting_document_files USING btree (user_id); - - --- --- Name: index_supporting_document_refusals_on_user_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_supporting_document_refusals_on_user_id ON public.supporting_document_refusals USING btree (user_id); +CREATE INDEX index_supporting_document_refusals_on_supportable_id ON public.supporting_document_refusals USING btree (supportable_id); -- @@ -8144,7 +8147,7 @@ ALTER TABLE ONLY public.orders -- ALTER TABLE ONLY public.supporting_document_refusals - ADD CONSTRAINT fk_rails_91d424352e FOREIGN KEY (user_id) REFERENCES public.users(id); + ADD CONSTRAINT fk_rails_91d424352e FOREIGN KEY (supportable_id) REFERENCES public.users(id); -- @@ -8929,6 +8932,9 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230509161557'), ('20230510141305'), ('20230511080650'), -('20230511081018'); +('20230511081018'), +('20230524080448'), +('20230524083558'), +('20230524110215');