diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts index 8a67e3d16..43ae42613 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts @@ -1,4 +1,10 @@ -import { Scope } from "../../types"; +import { + DynamicKeys, + Scope, + ValidationExceptionInterface, + ValidationProblemFromMap, + ViolationFromMap +} from "../../types"; export type body = Record; export type fetchOption = Record; @@ -25,50 +31,10 @@ export interface TransportExceptionInterface { name: string; } -export type ViolationFromMap< - M extends Record>, -> = { - [K in Extract]: { - propertyPath: K; - title: string; - parameters?: M[K]; - type?: string; - }; -}[Extract]; - -export type ValidationProblemFromMap< - M extends Record>, -> = { - type: string; - title: string; - detail?: string; - violations: ViolationFromMap[]; -} & Record; - -export interface ValidationExceptionInterface< - M extends Record> = Record< - string, - Record - >, -> extends Error { - name: "ValidationException"; - /** Full server payload copy */ - problems: ValidationProblemFromMap; - /** A list of all violations, with property key */ - violationsList: ViolationFromMap[]; - /** Compact list "Title: path" */ - violations: string[]; - /** Only titles */ - titles: string[]; - /** Only property paths */ - propertyPaths: Extract[]; - /** Indexing by property (useful for display by field) */ - byProperty: Record, string[]>; -} export class ValidationException< - M extends Record> = Record< + M extends Record> = Record< string, - Record + Record >, > extends Error @@ -79,7 +45,7 @@ export class ValidationException< public readonly violations: string[]; public readonly violationsList: ViolationFromMap[]; public readonly titles: string[]; - public readonly propertyPaths: Extract[]; + public readonly propertyPaths: DynamicKeys & string[]; public readonly byProperty: Record, string[]>; constructor(problem: ValidationProblemFromMap) { @@ -98,11 +64,11 @@ export class ValidationException< this.propertyPaths = problem.violations.map( (v) => v.propertyPath, - ) as Extract[]; + ) as DynamicKeys & string[]; this.byProperty = problem.violations.reduce( (acc, v) => { - const key = v.propertyPath as Extract; + const key = v.propertyPath.replace('/\[\d+\]$/', "") as Extract; (acc[key] ||= []).push(v.title); return acc; }, @@ -113,13 +79,38 @@ export class ValidationException< Error.captureStackTrace(this, ValidationException); } } + + violationsByNormalizedProperty(property: Extract): ViolationFromMap[] { + return this.violationsList.filter((v) => v.propertyPath.replace(/\[\d+\]$/, "") === property); + } + + violationsByNormalizedPropertyAndParams< + P extends Extract, + K extends Extract + >( + property: P, + param: K, + param_value: M[P][K] + ): ViolationFromMap[] + { + const list = this.violationsByNormalizedProperty(property); + + return list.filter( + (v): boolean => + !!v.parameters && + // `with_parameter in v.parameters` check indexing + param in v.parameters && + // the cast is safe, because we have overloading that bind the types + (v.parameters as M[P])[param] === param_value + ); + } } /** * Check that the exception is a ValidationExceptionInterface * @param x */ -export function isValidationException>>( +export function isValidationException>>( x: unknown, ): x is ValidationExceptionInterface { return ( @@ -315,9 +306,9 @@ export interface ConflictHttpExceptionInterface export const makeFetch = async < Input, Output, - M extends Record> = Record< + M extends Record> = Record< string, - Record + Record >, >( method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE", diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts index d8c92101a..38b148881 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/types.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -275,13 +275,63 @@ export interface TransportExceptionInterface { name: string; } -export interface ValidationExceptionInterface - extends TransportExceptionInterface { +type IndexedKey = `${Base}[${number}]`; +type BaseKeys = Extract; + +export type DynamicKeys>> = + | BaseKeys + | { [K in BaseKeys as IndexedKey]: K }[IndexedKey>]; + +type NormalizeKey = K extends `${infer B}[${number}]` ? B : K; + +export type ViolationFromMap>> = { + [K in DynamicKeys & string]: { // <- note le "& string" ici + propertyPath: K; + title: string; + parameters?: M[NormalizeKey]; + type?: string; + } +}[DynamicKeys & string]; + +export type ValidationProblemFromMap< + M extends Record>, +> = { + type: string; + title: string; + detail?: string; + violations: ViolationFromMap[]; +} & Record; + +export interface ValidationExceptionInterface< + M extends Record> = Record< + string, + Record + >, +> extends Error { name: "ValidationException"; - error: object; + /** Full server payload copy */ + problems: ValidationProblemFromMap; + /** A list of all violations, with property key */ + violationsList: ViolationFromMap[]; + /** Compact list "Title: path" */ violations: string[]; + /** Only titles */ titles: string[]; - propertyPaths: string[]; + /** Only property paths */ + propertyPaths: DynamicKeys & string[]; + /** Indexing by property (useful for display by field) */ + byProperty: Record, string[]>; + + violationsByNormalizedProperty(property: Extract): ViolationFromMap[]; + + violationsByNormalizedPropertyAndParams< + P extends Extract, + K extends Extract + >( + property: P, + param: K, + param_value: M[P][K] + ): ViolationFromMap[]; } export interface AccessExceptionInterface extends TransportExceptionInterface { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/OnTheFly.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/OnTheFly.ts index 382d4c8a7..29d552515 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/OnTheFly.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/OnTheFly.ts @@ -45,15 +45,15 @@ export const getPersonIdentifiers = async (): Promise< > => fetchResults("/api/1.0/person/identifiers/workers"); export interface WritePersonViolationMap - extends Record> { + extends Record> { firstName: { - "{{ value }}": string | null; + "{{ value }}": string }; lastName: { - "{{ value }}": string | null; + "{{ value }}": string; }; gender: { - "{{ value }}": string | null; + "{{ value }}": string; }; mobilenumber: { "{{ types }}": string; // ex: "mobile number" @@ -64,17 +64,17 @@ export interface WritePersonViolationMap "{{ value }}": string; // ex: "+33 1 02 03 04 05" }; email: { - "{{ value }}": string | null; + "{{ value }}": string; }; center: { - "{{ value }}": string | null; + "{{ value }}": string; }; civility: { - "{{ value }}": string | null; + "{{ value }}": string; }; birthdate: {}; identifiers: { - "{{ value }}": string | null; + "{{ value }}": string; "definition_id": string; }; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/PersonEdit.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/PersonEdit.vue index 4c77a0c23..690a62450 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/PersonEdit.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/PersonEdit.vue @@ -366,7 +366,7 @@ import { Center, Civility, Gender, - DateTimeWrite, + DateTimeWrite, ValidationExceptionInterface, } from "ChillMainAssets/types"; import { AltName, @@ -378,7 +378,6 @@ import { } from "ChillPersonAssets/types"; import { isValidationException, - ValidationExceptionInterface, } from "ChillMainAssets/lib/api/apiMethods"; import {useToast} from "vue-toast-notification"; import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date"; @@ -610,10 +609,13 @@ function addQueryItem(field: "lastName" | "firstName", queryItem: string) { } type WritePersonViolationKey = Extract; -const violationsList = ref["violationsList"]>([]); +const violationsList = ref|null>(null); function violationTitles

(property: P): string[] { - return violationsList.value.filter((v) => v.propertyPath === property).map((v) => v.title); + if (null === violationsList.value) { + return []; + } + return violationsList.value.violationsByNormalizedProperty(property).map((v) => v.title); } function violationTitlesWithParameter< @@ -624,20 +626,11 @@ function violationTitlesWithParameter< with_parameter: Param, with_parameter_value: WritePersonViolationMap[P][Param], ): string[] { - const list = violationsList.value.filter((v) => v.propertyPath === property); - - const filtered = list.filter( - (v): boolean => - !!v.parameters && - // `with_parameter in v.parameters` check indexing - with_parameter in v.parameters && - // the cast is safe, because we have overloading that bind the types - (v.parameters as WritePersonViolationMap[P])[with_parameter] === with_parameter_value - ); - return filtered.map((v) => v.title); - - // Sinon, retourner simplement les titres de la propriété - return list.map((v) => v.title); + if (violationsList.value === null) { + return []; + } + return violationsList.value.violationsByNormalizedPropertyAndParams(property, with_parameter, with_parameter_value) + .map((v) => v.title); } @@ -660,14 +653,13 @@ function submitNewAddress(payload: { addressId: number }) { } async function postPerson(): Promise { - console.log("postPerson"); try { const createdPerson = await createPerson(person); emit("onPersonCreated", { person: createdPerson }); } catch (e: unknown) { if (isValidationException(e)) { - violationsList.value = e.violationsList; + violationsList.value = e; } else { toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING)); } diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index a20a7d829..ab113d6ec 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -75,6 +75,7 @@ person_creation: person_identifier: This identifier must be set: Cet identifiant doit être présent. + Identifier must be unique. The same identifier already exists for {{ persons }}: Identifiant déjà utilisé pour {{ persons }} accompanying_course_work: The endDate should be greater or equal than the start date: La date de fin doit être égale ou supérieure à la date de début