From 8af579be538f196ab7806c31c4799017e3a1333b Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 8 Oct 2026 16:46:54 +0200 Subject: [PATCH 1/3] feat(#206): backend cartes rose soin_enfant (3 sous-types, instant T, correction 24h) Co-authored-by: Cursor --- .../src/entities/evenements_agenda.entity.ts | 1 + .../src/modules/cards/cards.service.spec.ts | 92 ++++++++++ backend/src/modules/cards/cards.service.ts | 171 +++++++++++++++--- backend/src/modules/cards/dto/cards.dto.ts | 8 + .../evenements-agenda.service.ts | 7 +- database/BDD.sql | 2 +- database/migrations/2026_cards_system.sql | 5 + database/migrations/2026_soin_enfant_enum.sql | 2 + 8 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 database/migrations/2026_soin_enfant_enum.sql diff --git a/backend/src/entities/evenements_agenda.entity.ts b/backend/src/entities/evenements_agenda.entity.ts index 33b693b..30db57c 100644 --- a/backend/src/entities/evenements_agenda.entity.ts +++ b/backend/src/entities/evenements_agenda.entity.ts @@ -14,6 +14,7 @@ export enum TypeEvenementAgendaType { ABSENCE_ENFANT = 'absence_enfant', CONGE_AM = 'conge_am', ARRET_MALADIE_AM = 'arret_maladie_am', + SOIN_ENFANT = 'soin_enfant', } export enum StatutEvenementAgendaType { diff --git a/backend/src/modules/cards/cards.service.spec.ts b/backend/src/modules/cards/cards.service.spec.ts index cfdb925..ebed7ab 100644 --- a/backend/src/modules/cards/cards.service.spec.ts +++ b/backend/src/modules/cards/cards.service.spec.ts @@ -281,4 +281,96 @@ describe('CardsService (#194)', () => { ); expect(res.statut).toBe(CardInstanceStatutType.TRAITEE); }); + + it('AM crée soin_enfant (fievre) → motif dérivé + payload structuré', async () => { + typesRepo.findOne.mockResolvedValue({ + code: 'soin_enfant', + system: true, + emitter_roles: [RoleType.ASSISTANTE_MATERNELLE], + response_mode: CardResponseModeType.ACK, + retention_days: 7, + audience_resolver: 'couple_parents', + }); + evenementsService.creer.mockResolvedValue({ id: 'ev-soin' }); + cardsRepo.create.mockImplementation((c) => ({ ...c, id: 'card-soin' })); + cardsRepo.save.mockResolvedValue({ id: 'card-soin' }); + cardsRepo.findOne.mockResolvedValue({ + id: 'card-soin', + type_code: 'soin_enfant', + statut: CardInstanceStatutType.OUVERTE, + payload: { + date_debut: '2026-10-28', + date_fin: '2026-10-28', + motif: '38,5 °C à 14:30 (Médicament : Doliprane)', + sous_type: 'fievre', + heure_releve: '14:30', + temperature_c: 38.5, + medicament_donne: 'Doliprane', + }, + type: { response_mode: CardResponseModeType.ACK }, + }); + + const res = await service.creer('am-1', RoleType.ASSISTANTE_MATERNELLE, { + type_code: 'soin_enfant', + id_placement: 'pl-1', + date_debut: '2026-10-28', + date_fin: '2026-10-28', + payload: { + sous_type: 'fievre', + heure_releve: '14:30', + temperature_c: 38.5, + medicament_donne: 'Doliprane', + }, + }); + + expect(evenementsService.creer).toHaveBeenCalledWith( + 'am-1', + RoleType.ASSISTANTE_MATERNELLE, + expect.objectContaining({ + type: TypeEvenementAgendaType.SOIN_ENFANT, + motif: '38,5 °C à 14:30 (Médicament : Doliprane)', + }), + ); + expect(res.payload.motif).toBe('38,5 °C à 14:30 (Médicament : Doliprane)'); + }); + + it('AM corrige soin_enfant dans les 24h → historique + re-ack', async () => { + const creeLe = new Date(); + cardsRepo.findOne.mockResolvedValue({ + id: 'card-soin', + type_code: 'soin_enfant', + id_placement: 'pl-1', + id_evenement: 'ev-soin', + cree_par: 'am-1', + cree_le: creeLe, + statut: CardInstanceStatutType.TRAITEE, // déjà acquitté + payload: { + date_debut: '2026-10-28', + date_fin: '2026-10-28', + sous_type: 'fievre', + heure_releve: '14:30', + temperature_c: 38.5, + }, + type: { retention_days: 7 }, + }); + evenementsService.maj.mockResolvedValue({}); + + await service.majEnAttente('am-1', RoleType.ASSISTANTE_MATERNELLE, 'card-soin', { + date_debut: '2026-10-28', + date_fin: '2026-10-28', + payload: { + sous_type: 'fievre', + heure_releve: '14:30', + temperature_c: 37.5, // correction + }, + }); + + expect(cardsRepo.manager.query).toHaveBeenCalledWith( + expect.stringContaining('UPDATE card_instances'), + expect.arrayContaining([ + CardInstanceStatutType.OUVERTE, // repasse ouverte + expect.stringContaining('37,5'), // nouveau payload stringifié + ]), + ); + }); }); diff --git a/backend/src/modules/cards/cards.service.ts b/backend/src/modules/cards/cards.service.ts index b0402a2..468fa1d 100644 --- a/backend/src/modules/cards/cards.service.ts +++ b/backend/src/modules/cards/cards.service.ts @@ -136,23 +136,76 @@ export class CardsService { let absenceId = dto.id_evenement; const absenceType = this.mapAbsenceType(dto.type_code); + let motif = dto.motif ?? null; + let finalPayload = { + date_debut: dto.date_debut, + date_fin: dto.date_fin, + motif, + ...(dto.payload ?? {}), + }; + + if (dto.type_code === 'soin_enfant') { + const p = dto.payload; + if (!p || !p.sous_type || !p.heure_releve) { + throw new BadRequestException('sous_type et heure_releve requis pour soin_enfant'); + } + if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p.heure_releve)) { + throw new BadRequestException('Format heure_releve invalide (HH:mm)'); + } + + if (p.sous_type === 'fievre') { + if (typeof p.temperature_c !== 'number' || p.temperature_c < 34.0 || p.temperature_c > 43.0) { + throw new BadRequestException('temperature_c invalide (34.0 - 43.0)'); + } + motif = `${p.temperature_c.toFixed(1).replace('.', ',')} °C à ${p.heure_releve}`; + if (p.medicament_donne && typeof p.medicament_donne === 'string' && p.medicament_donne.trim()) { + motif += ` (Médicament : ${p.medicament_donne.trim()})`; + } + } else if (p.sous_type === 'bobo') { + const natures = ['chute', 'coup', 'egratignure', 'piqure', 'brulure', 'autre']; + if (!natures.includes(p.bobo_nature)) { + throw new BadRequestException('bobo_nature invalide'); + } + motif = `Bobo (${p.bobo_nature}) à ${p.heure_releve}`; + if (p.description && typeof p.description === 'string' && p.description.trim()) { + const desc = p.description.trim(); + if (desc.length > 120) throw new BadRequestException('description trop longue (max 120)'); + motif += ` : ${desc.length > 30 ? desc.substring(0, 30) + '...' : desc}`; + } + } else if (p.sous_type === 'medicament') { + if (!p.medicament_nom || typeof p.medicament_nom !== 'string' || !p.medicament_nom.trim()) { + throw new BadRequestException('medicament_nom requis'); + } + motif = `Médicament (${p.medicament_nom.trim()}) à ${p.heure_releve}`; + if (p.contexte && typeof p.contexte === 'string' && p.contexte.trim()) { + if (p.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); + } + } else { + throw new BadRequestException('sous_type invalide pour soin_enfant'); + } + finalPayload.motif = motif; + } + if (operation === CardOperationType.CREATE) { const absence = await this.evenementsService.creer(userId, role, { id_placement: dto.id_placement, type: absenceType, date_debut: dto.date_debut, date_fin: dto.date_fin, - motif: dto.motif, + motif: motif ?? undefined, }); absenceId = absence.id; } else { + if (dto.type_code === 'soin_enfant') { + throw new BadRequestException('operation=update interdite pour soin_enfant'); + } if (!absenceId) { throw new BadRequestException('id_evenement requis pour operation=update'); } await this.evenementsService.maj(userId, role, absenceId, { date_debut: dto.date_debut, date_fin: dto.date_fin, - motif: dto.motif, + motif: motif ?? undefined, // Congé accepté modifié → repasse en attente via cartes respond flow; // pour absence enfant update immédiat déjà fait. ...(dto.type_code === 'conge_am' @@ -173,11 +226,7 @@ export class CardsService { cree_par: userId, operation, statut: statutInitial, - payload: { - date_debut: dto.date_debut, - date_fin: dto.date_fin, - motif: dto.motif ?? null, - }, + payload: finalPayload, purge_at: this.purgeAt(type.retention_days, statutInitial), }); const saved = await this.cardsRepo.save(card); @@ -280,11 +329,19 @@ export class CardsService { if (card.cree_par !== userId) { throw new ForbiddenException('Seul le créateur peut modifier cette carte'); } - if ( - card.statut !== CardInstanceStatutType.OUVERTE && - card.statut !== CardInstanceStatutType.REFUSEE - ) { - throw new BadRequestException('Carte non modifiable dans cet état'); + + if (card.type_code === 'soin_enfant') { + const msSinceCreation = Date.now() - new Date(card.cree_le).getTime(); + if (msSinceCreation > 24 * 60 * 60 * 1000) { + throw new BadRequestException('Délai de correction de 24h dépassé'); + } + } else { + if ( + card.statut !== CardInstanceStatutType.OUVERTE && + card.statut !== CardInstanceStatutType.REFUSEE + ) { + throw new BadRequestException('Carte non modifiable dans cet état'); + } } const debut = @@ -294,21 +351,79 @@ export class CardsService { throw new BadRequestException('Dates invalides'); } + let motif = dto.motif ?? card.payload?.['motif'] ?? null; + let finalPayload = { + ...card.payload, + date_debut: debut, + date_fin: fin, + ...(dto.payload ?? {}), + }; + + if (card.type_code === 'soin_enfant') { + const p = finalPayload; + if (!p || !p.sous_type || !p.heure_releve) { + throw new BadRequestException('sous_type et heure_releve requis pour soin_enfant'); + } + if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p.heure_releve)) { + throw new BadRequestException('Format heure_releve invalide (HH:mm)'); + } + + if (p.sous_type === 'fievre') { + if (typeof p.temperature_c !== 'number' || p.temperature_c < 34.0 || p.temperature_c > 43.0) { + throw new BadRequestException('temperature_c invalide (34.0 - 43.0)'); + } + motif = `${p.temperature_c.toFixed(1).replace('.', ',')} °C à ${p.heure_releve}`; + if (p.medicament_donne && typeof p.medicament_donne === 'string' && p.medicament_donne.trim()) { + motif += ` (Médicament : ${p.medicament_donne.trim()})`; + } + } else if (p.sous_type === 'bobo') { + const natures = ['chute', 'coup', 'egratignure', 'piqure', 'brulure', 'autre']; + if (!natures.includes(p.bobo_nature)) { + throw new BadRequestException('bobo_nature invalide'); + } + motif = `Bobo (${p.bobo_nature}) à ${p.heure_releve}`; + if (p.description && typeof p.description === 'string' && p.description.trim()) { + const desc = p.description.trim(); + if (desc.length > 120) throw new BadRequestException('description trop longue (max 120)'); + motif += ` : ${desc.length > 30 ? desc.substring(0, 30) + '...' : desc}`; + } + } else if (p.sous_type === 'medicament') { + if (!p.medicament_nom || typeof p.medicament_nom !== 'string' || !p.medicament_nom.trim()) { + throw new BadRequestException('medicament_nom requis'); + } + motif = `Médicament (${p.medicament_nom.trim()}) à ${p.heure_releve}`; + if (p.contexte && typeof p.contexte === 'string' && p.contexte.trim()) { + if (p.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); + } + } else { + throw new BadRequestException('sous_type invalide pour soin_enfant'); + } + finalPayload.motif = motif; + + const hist = Array.isArray(card.payload?.['historique']) ? card.payload['historique'] : []; + const oldPayload = { ...card.payload }; + delete oldPayload.historique; + hist.push({ + ...oldPayload, + corrige_le: new Date().toISOString(), + corrige_par: userId, + }); + finalPayload.historique = hist; + finalPayload.corrige_le = new Date().toISOString(); + } else { + finalPayload.motif = motif; + } + if (card.id_evenement) { await this.evenementsService.maj(userId, role, card.id_evenement, { date_debut: debut, date_fin: fin, - motif: dto.motif, - statut: StatutEvenementAgendaType.EN_ATTENTE, + motif: motif ?? undefined, + ...(card.type_code !== 'soin_enfant' ? { statut: StatutEvenementAgendaType.EN_ATTENTE } : {}), }); } - card.payload = { - ...card.payload, - date_debut: debut, - date_fin: fin, - motif: dto.motif ?? card.payload?.['motif'] ?? null, - }; + card.payload = finalPayload; card.statut = CardInstanceStatutType.OUVERTE; card.purge_at = this.purgeAt(card.type.retention_days, card.statut); await this.cardsRepo.manager.query( @@ -332,16 +447,25 @@ export class CardsService { if (card.cree_par !== userId) { throw new ForbiddenException('Seul le créateur peut supprimer cette carte'); } + + if (card.type_code === 'soin_enfant') { + const msSinceCreation = Date.now() - new Date(card.cree_le).getTime(); + if (msSinceCreation > 24 * 60 * 60 * 1000) { + throw new BadRequestException('Délai de correction de 24h dépassé, suppression impossible'); + } + } + const audienceIds = await this.audienceUserIds(card); if (card.id_evenement) { const absStatut = card.statut === CardInstanceStatutType.TRAITEE ? null : card.id_evenement; - // Supprime l’absence liée si pas encore acceptée définitivement + // Supprime l’absence liée si pas encore acceptée définitivement (ou si soin_enfant dans les 24h) if ( card.statut === CardInstanceStatutType.OUVERTE || - card.statut === CardInstanceStatutType.REFUSEE + card.statut === CardInstanceStatutType.REFUSEE || + card.type_code === 'soin_enfant' ) { await this.evenementsService.supprimer(userId, role, card.id_evenement); } @@ -379,6 +503,9 @@ export class CardsService { if (typeCode === 'arret_maladie_am') { return TypeEvenementAgendaType.ARRET_MALADIE_AM; } + if (typeCode === 'soin_enfant') { + return TypeEvenementAgendaType.SOIN_ENFANT; + } throw new BadRequestException(`Type non mappé à une absence: ${typeCode}`); } diff --git a/backend/src/modules/cards/dto/cards.dto.ts b/backend/src/modules/cards/dto/cards.dto.ts index d086dd5..7632672 100644 --- a/backend/src/modules/cards/dto/cards.dto.ts +++ b/backend/src/modules/cards/dto/cards.dto.ts @@ -48,6 +48,10 @@ export class CreerCarteDto { @IsOptional() @IsEnum(CardOperationType) operation?: CardOperationType; + + @ApiPropertyOptional() + @IsOptional() + payload?: Record; } export class RepondreCarteDto { @@ -79,6 +83,10 @@ export class MajCarteDto { @IsString() @MaxLength(2000) motif?: string; + + @ApiPropertyOptional() + @IsOptional() + payload?: Record; } export class CarteDto { diff --git a/backend/src/modules/evenements-agenda/evenements-agenda.service.ts b/backend/src/modules/evenements-agenda/evenements-agenda.service.ts index 1c88723..728e49c 100644 --- a/backend/src/modules/evenements-agenda/evenements-agenda.service.ts +++ b/backend/src/modules/evenements-agenda/evenements-agenda.service.ts @@ -196,7 +196,7 @@ export class EvenementsAgendaService { } private statutInitial(type: TypeEvenementAgendaType): StatutEvenementAgendaType { - if (type === TypeEvenementAgendaType.ABSENCE_ENFANT) { + if (type === TypeEvenementAgendaType.ABSENCE_ENFANT || type === TypeEvenementAgendaType.SOIN_ENFANT) { return StatutEvenementAgendaType.ACCEPTE; } return StatutEvenementAgendaType.EN_ATTENTE; @@ -233,10 +233,11 @@ export class EvenementsAgendaService { if (role === RoleType.ASSISTANTE_MATERNELLE) { if ( type !== TypeEvenementAgendaType.CONGE_AM && - type !== TypeEvenementAgendaType.ARRET_MALADIE_AM + type !== TypeEvenementAgendaType.ARRET_MALADIE_AM && + type !== TypeEvenementAgendaType.SOIN_ENFANT ) { throw new ForbiddenException( - 'Une AM ne peut créer que congé ou arrêt maladie', + 'Une AM ne peut créer que congé, arrêt maladie ou soin enfant', ); } return; diff --git a/database/BDD.sql b/database/BDD.sql index cb5cea0..eaecc44 100644 --- a/database/BDD.sql +++ b/database/BDD.sql @@ -37,7 +37,7 @@ DO $$ BEGIN END IF; IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_agenda_type') THEN CREATE TYPE type_evenement_agenda_type AS ENUM ( - 'absence_enfant', 'conge_am', 'arret_maladie_am' + 'absence_enfant', 'conge_am', 'arret_maladie_am', 'soin_enfant' ); END IF; IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_agenda_type') THEN diff --git a/database/migrations/2026_cards_system.sql b/database/migrations/2026_cards_system.sql index 4751c1b..63a73f5 100644 --- a/database/migrations/2026_cards_system.sql +++ b/database/migrations/2026_cards_system.sql @@ -108,6 +108,11 @@ INSERT INTO card_types ( 'arret_maladie_am', true, 'Arrêt maladie AM', ARRAY['assistante_maternelle'], ARRAY['parent'], 'couple_parents', 'ack', 14, 'pink' + ), + ( + 'soin_enfant', true, 'Soins enfant', + ARRAY['assistante_maternelle'], ARRAY['parent'], + 'couple_parents', 'ack', 7, 'pink' ) ON CONFLICT (code) DO UPDATE SET titre = EXCLUDED.titre, diff --git a/database/migrations/2026_soin_enfant_enum.sql b/database/migrations/2026_soin_enfant_enum.sql new file mode 100644 index 0000000..4d9f66e --- /dev/null +++ b/database/migrations/2026_soin_enfant_enum.sql @@ -0,0 +1,2 @@ +-- Migration incrémentale : ajout du type soin_enfant à l'agenda +ALTER TYPE type_evenement_agenda_type ADD VALUE IF NOT EXISTS 'soin_enfant'; -- 2.54.0 From 1e4e2d5198e89ef814d31f843e7c616874423fa4 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 8 Oct 2026 18:03:24 +0200 Subject: [PATCH 2/3] fix(cards): allow payload object in DTOs Co-authored-by: Cursor --- backend/src/modules/cards/dto/cards.dto.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/modules/cards/dto/cards.dto.ts b/backend/src/modules/cards/dto/cards.dto.ts index 7632672..a22c8a3 100644 --- a/backend/src/modules/cards/dto/cards.dto.ts +++ b/backend/src/modules/cards/dto/cards.dto.ts @@ -3,6 +3,7 @@ import { IsDateString, IsEnum, IsOptional, + IsObject, IsString, IsUUID, MaxLength, @@ -51,6 +52,7 @@ export class CreerCarteDto { @ApiPropertyOptional() @IsOptional() + @IsObject() payload?: Record; } @@ -86,6 +88,7 @@ export class MajCarteDto { @ApiPropertyOptional() @IsOptional() + @IsObject() payload?: Record; } -- 2.54.0 From a5d1616a1792bc0fec5dba35d573019e4ad81112 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 8 Oct 2026 18:09:15 +0200 Subject: [PATCH 3/3] fix(cards): resolve typescript errors with payload typing Co-authored-by: Cursor --- backend/src/modules/cards/cards.service.ts | 80 +++++++++++----------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/backend/src/modules/cards/cards.service.ts b/backend/src/modules/cards/cards.service.ts index 468fa1d..18efe6a 100644 --- a/backend/src/modules/cards/cards.service.ts +++ b/backend/src/modules/cards/cards.service.ts @@ -137,7 +137,7 @@ export class CardsService { const absenceType = this.mapAbsenceType(dto.type_code); let motif = dto.motif ?? null; - let finalPayload = { + let finalPayload: Record = { date_debut: dto.date_debut, date_fin: dto.date_fin, motif, @@ -145,40 +145,40 @@ export class CardsService { }; if (dto.type_code === 'soin_enfant') { - const p = dto.payload; - if (!p || !p.sous_type || !p.heure_releve) { + const p = dto.payload as Record; + if (!p || !p?.sous_type || !p?.heure_releve) { throw new BadRequestException('sous_type et heure_releve requis pour soin_enfant'); } - if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p.heure_releve)) { + if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p?.heure_releve)) { throw new BadRequestException('Format heure_releve invalide (HH:mm)'); } - if (p.sous_type === 'fievre') { - if (typeof p.temperature_c !== 'number' || p.temperature_c < 34.0 || p.temperature_c > 43.0) { + if (p?.sous_type === 'fievre') { + if (typeof p?.temperature_c !== 'number' || p?.temperature_c < 34.0 || p?.temperature_c > 43.0) { throw new BadRequestException('temperature_c invalide (34.0 - 43.0)'); } - motif = `${p.temperature_c.toFixed(1).replace('.', ',')} °C à ${p.heure_releve}`; - if (p.medicament_donne && typeof p.medicament_donne === 'string' && p.medicament_donne.trim()) { - motif += ` (Médicament : ${p.medicament_donne.trim()})`; + motif = `${p?.temperature_c.toFixed(1).replace('.', ',')} °C à ${p?.heure_releve}`; + if (p?.medicament_donne && typeof p?.medicament_donne === 'string' && p?.medicament_donne.trim()) { + motif += ` (Médicament : ${p?.medicament_donne.trim()})`; } - } else if (p.sous_type === 'bobo') { + } else if (p?.sous_type === 'bobo') { const natures = ['chute', 'coup', 'egratignure', 'piqure', 'brulure', 'autre']; - if (!natures.includes(p.bobo_nature)) { + if (!natures.includes(p?.bobo_nature)) { throw new BadRequestException('bobo_nature invalide'); } - motif = `Bobo (${p.bobo_nature}) à ${p.heure_releve}`; - if (p.description && typeof p.description === 'string' && p.description.trim()) { - const desc = p.description.trim(); + motif = `Bobo (${p?.bobo_nature}) à ${p?.heure_releve}`; + if (p?.description && typeof p?.description === 'string' && p?.description.trim()) { + const desc = p?.description.trim(); if (desc.length > 120) throw new BadRequestException('description trop longue (max 120)'); motif += ` : ${desc.length > 30 ? desc.substring(0, 30) + '...' : desc}`; } - } else if (p.sous_type === 'medicament') { - if (!p.medicament_nom || typeof p.medicament_nom !== 'string' || !p.medicament_nom.trim()) { + } else if (p?.sous_type === 'medicament') { + if (!p?.medicament_nom || typeof p?.medicament_nom !== 'string' || !p?.medicament_nom.trim()) { throw new BadRequestException('medicament_nom requis'); } - motif = `Médicament (${p.medicament_nom.trim()}) à ${p.heure_releve}`; - if (p.contexte && typeof p.contexte === 'string' && p.contexte.trim()) { - if (p.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); + motif = `Médicament (${p?.medicament_nom.trim()}) à ${p?.heure_releve}`; + if (p?.contexte && typeof p?.contexte === 'string' && p?.contexte.trim()) { + if (p?.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); } } else { throw new BadRequestException('sous_type invalide pour soin_enfant'); @@ -192,7 +192,7 @@ export class CardsService { type: absenceType, date_debut: dto.date_debut, date_fin: dto.date_fin, - motif: motif ?? undefined, + motif: motif ? String(motif) : undefined, }); absenceId = absence.id; } else { @@ -205,7 +205,7 @@ export class CardsService { await this.evenementsService.maj(userId, role, absenceId, { date_debut: dto.date_debut, date_fin: dto.date_fin, - motif: motif ?? undefined, + motif: motif ? String(motif) : undefined, // Congé accepté modifié → repasse en attente via cartes respond flow; // pour absence enfant update immédiat déjà fait. ...(dto.type_code === 'conge_am' @@ -352,7 +352,7 @@ export class CardsService { } let motif = dto.motif ?? card.payload?.['motif'] ?? null; - let finalPayload = { + let finalPayload: Record = { ...card.payload, date_debut: debut, date_fin: fin, @@ -361,39 +361,39 @@ export class CardsService { if (card.type_code === 'soin_enfant') { const p = finalPayload; - if (!p || !p.sous_type || !p.heure_releve) { + if (!p || !p?.sous_type || !p?.heure_releve) { throw new BadRequestException('sous_type et heure_releve requis pour soin_enfant'); } - if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p.heure_releve)) { + if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(p?.heure_releve)) { throw new BadRequestException('Format heure_releve invalide (HH:mm)'); } - if (p.sous_type === 'fievre') { - if (typeof p.temperature_c !== 'number' || p.temperature_c < 34.0 || p.temperature_c > 43.0) { + if (p?.sous_type === 'fievre') { + if (typeof p?.temperature_c !== 'number' || p?.temperature_c < 34.0 || p?.temperature_c > 43.0) { throw new BadRequestException('temperature_c invalide (34.0 - 43.0)'); } - motif = `${p.temperature_c.toFixed(1).replace('.', ',')} °C à ${p.heure_releve}`; - if (p.medicament_donne && typeof p.medicament_donne === 'string' && p.medicament_donne.trim()) { - motif += ` (Médicament : ${p.medicament_donne.trim()})`; + motif = `${p?.temperature_c.toFixed(1).replace('.', ',')} °C à ${p?.heure_releve}`; + if (p?.medicament_donne && typeof p?.medicament_donne === 'string' && p?.medicament_donne.trim()) { + motif += ` (Médicament : ${p?.medicament_donne.trim()})`; } - } else if (p.sous_type === 'bobo') { + } else if (p?.sous_type === 'bobo') { const natures = ['chute', 'coup', 'egratignure', 'piqure', 'brulure', 'autre']; - if (!natures.includes(p.bobo_nature)) { + if (!natures.includes(p?.bobo_nature)) { throw new BadRequestException('bobo_nature invalide'); } - motif = `Bobo (${p.bobo_nature}) à ${p.heure_releve}`; - if (p.description && typeof p.description === 'string' && p.description.trim()) { - const desc = p.description.trim(); + motif = `Bobo (${p?.bobo_nature}) à ${p?.heure_releve}`; + if (p?.description && typeof p?.description === 'string' && p?.description.trim()) { + const desc = p?.description.trim(); if (desc.length > 120) throw new BadRequestException('description trop longue (max 120)'); motif += ` : ${desc.length > 30 ? desc.substring(0, 30) + '...' : desc}`; } - } else if (p.sous_type === 'medicament') { - if (!p.medicament_nom || typeof p.medicament_nom !== 'string' || !p.medicament_nom.trim()) { + } else if (p?.sous_type === 'medicament') { + if (!p?.medicament_nom || typeof p?.medicament_nom !== 'string' || !p?.medicament_nom.trim()) { throw new BadRequestException('medicament_nom requis'); } - motif = `Médicament (${p.medicament_nom.trim()}) à ${p.heure_releve}`; - if (p.contexte && typeof p.contexte === 'string' && p.contexte.trim()) { - if (p.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); + motif = `Médicament (${p?.medicament_nom.trim()}) à ${p?.heure_releve}`; + if (p?.contexte && typeof p?.contexte === 'string' && p?.contexte.trim()) { + if (p?.contexte.trim().length > 120) throw new BadRequestException('contexte trop long (max 120)'); } } else { throw new BadRequestException('sous_type invalide pour soin_enfant'); @@ -418,7 +418,7 @@ export class CardsService { await this.evenementsService.maj(userId, role, card.id_evenement, { date_debut: debut, date_fin: fin, - motif: motif ?? undefined, + motif: motif ? String(motif) : undefined, ...(card.type_code !== 'soin_enfant' ? { statut: StatutEvenementAgendaType.EN_ATTENTE } : {}), }); } -- 2.54.0