From edcd3f24b23315db672bb8ea959c5d599a4ca430 Mon Sep 17 00:00:00 2001 From: Benjamin Levesque <14175665+benjlevesque@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:33:43 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9B=82=20Ouvrir=20l'acc=C3=A8s=20aux?= =?UTF-8?q?=20changements=20d'actionnaire=20=C3=A0=20la=20CRE=20et=20EDF?= =?UTF-8?q?=20OA=20(#2676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🛂 Ouvrir l'accès aux changements d'actionnaire à la CRE et EDF OA * ✨ Menu * 🐛 Fix link --- .../UI/organisms/UserNavigation.tsx | 36 ++++++++++++++---- .../src/laur\303\251at/actionnaire.routes.ts" | 17 ++++++++- ...repr\303\251sentantL\303\251gal.routes.ts" | 16 +++++++- .../molecules/UserBasedRoleNavigation.tsx | 37 ++++++++----------- .../domain/utilisateur/src/role.valueType.ts | 16 ++++++++ 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/packages/applications/legacy/src/views/components/UI/organisms/UserNavigation.tsx b/packages/applications/legacy/src/views/components/UI/organisms/UserNavigation.tsx index dbfdafb673..884a5f69ed 100644 --- a/packages/applications/legacy/src/views/components/UI/organisms/UserNavigation.tsx +++ b/packages/applications/legacy/src/views/components/UI/organisms/UserNavigation.tsx @@ -64,8 +64,22 @@ const MenuCre = (currentPage?: string) => ( > Projets - Abandons - Recours + + + Abandons + + + Recours + + + Changements de représentant légal + + + Actionnaire(s) + + Raccordements ( Recours - + Changements de représentant légal - + Actionnaire(s) @@ -196,10 +212,12 @@ const MenuPorteurProjet = (currentPage?: string) => ( Recours - + Changements de représentant légal - + Actionnaire(s) @@ -268,10 +286,12 @@ const MenuDreal = (currentPage?: string) => ( Recours - + Changements de représentant légal - + Actionnaire(s) diff --git "a/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" "b/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" index 3a7622d329..2ced222494 100644 --- "a/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" +++ "b/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" @@ -1,5 +1,11 @@ +import { Actionnaire } from '@potentiel-domain/laureat'; + import { encodeParameter } from '../encodeParameter'; +type ListerFilters = { + statut?: Actionnaire.StatutChangementActionnaire.RawType; +}; + export const modifier = (identifiantProjet: string) => `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/modifier`; @@ -10,5 +16,14 @@ export const changement = { `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/${demandéLe}`, téléchargerModèleRéponse: (identifiantProjet: string) => `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/modele-reponse`, - lister: `/laureats/changements/actionnaire`, + + lister: (filters: ListerFilters = {}) => { + const searchParams = new URLSearchParams(); + + if (filters?.statut) { + searchParams.set('statut', filters.statut); + } + + return `/laureats/changements/actionnaire${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; + }, }; diff --git "a/packages/applications/routes/src/laur\303\251at/repr\303\251sentantL\303\251gal.routes.ts" "b/packages/applications/routes/src/laur\303\251at/repr\303\251sentantL\303\251gal.routes.ts" index 2ece169074..c2c2e05d81 100644 --- "a/packages/applications/routes/src/laur\303\251at/repr\303\251sentantL\303\251gal.routes.ts" +++ "b/packages/applications/routes/src/laur\303\251at/repr\303\251sentantL\303\251gal.routes.ts" @@ -1,5 +1,11 @@ +import { ReprésentantLégal } from '@potentiel-domain/laureat'; + import { encodeParameter } from '../encodeParameter'; +type ListerFilters = { + statut?: ReprésentantLégal.StatutChangementReprésentantLégal.RawType; +}; + export const modifier = (identifiantProjet: string) => `/laureats/${encodeParameter(identifiantProjet)}/representant-legal/modifier`; @@ -10,5 +16,13 @@ export const changement = { `/laureats/${encodeParameter(identifiantProjet)}/representant-legal/changement/demander`, corriger: (identifiantProjet: string, demandéLe: string) => `/laureats/${encodeParameter(identifiantProjet)}/representant-legal/changement/${demandéLe}/corriger`, - lister: `/laureats/changements/representant-legal?statut=demandé`, + lister: (filters: ListerFilters = {}) => { + const searchParams = new URLSearchParams(); + + if (filters?.statut) { + searchParams.set('statut', filters.statut); + } + + return `/laureats/changements/representant-legal${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; + }, }; diff --git a/packages/applications/ssr/src/components/molecules/UserBasedRoleNavigation.tsx b/packages/applications/ssr/src/components/molecules/UserBasedRoleNavigation.tsx index e5a86bc17f..b86ca00ea5 100644 --- a/packages/applications/ssr/src/components/molecules/UserBasedRoleNavigation.tsx +++ b/packages/applications/ssr/src/components/molecules/UserBasedRoleNavigation.tsx @@ -40,15 +40,7 @@ const menuLinks = { }; const getNavigationItemsBasedOnRole = (utilisateur: Utilisateur.ValueType) => { - const demandesMenuLinks: Array = [ - { - text: 'Toutes les demandes', - linkProps: { - href: utilisateur.role.estÉgaleÀ(Role.porteur) - ? '/mes-demandes.html' - : '/admin/demandes.html', - }, - }, + const demandesMigrées: Array = [ { text: 'Abandons', linkProps: { @@ -64,15 +56,26 @@ const getNavigationItemsBasedOnRole = (utilisateur: Utilisateur.ValueType) => { { text: 'Changements de représentant légal', linkProps: { - href: Routes.ReprésentantLégal.changement.lister, + href: Routes.ReprésentantLégal.changement.lister({ statut: 'demandé' }), }, }, { text: 'Actionnaire(s)', linkProps: { - href: Routes.Actionnaire.changement.lister, + href: Routes.Actionnaire.changement.lister({ statut: 'demandé' }), + }, + }, + ]; + const demandesMenuLinks: Array = [ + { + text: 'Toutes les demandes', + linkProps: { + href: utilisateur.role.estÉgaleÀ(Role.porteur) + ? '/mes-demandes.html' + : '/admin/demandes.html', }, }, + ...demandesMigrées, ]; return match(utilisateur.role.nom) @@ -252,16 +255,8 @@ const getNavigationItemsBasedOnRole = (utilisateur: Utilisateur.ValueType) => { }, }, { - text: 'Abandons', - linkProps: { - href: Routes.Abandon.lister({ statut: 'demandé' }), - }, - }, - { - text: 'Recours', - linkProps: { - href: Routes.Recours.lister({ statut: 'demandé' }), - }, + text: 'Demandes', + menuLinks: demandesMigrées, }, { text: 'Raccordements', diff --git a/packages/domain/utilisateur/src/role.valueType.ts b/packages/domain/utilisateur/src/role.valueType.ts index 4aba9ef448..e41498192f 100644 --- a/packages/domain/utilisateur/src/role.valueType.ts +++ b/packages/domain/utilisateur/src/role.valueType.ts @@ -1104,6 +1104,14 @@ const crePolicies: ReadonlyArray = [ 'garantiesFinancières.dépôt.consulter', 'garantiesFinancières.mainlevée.lister', 'garantiesFinancières.enAttente.consulter', + + // Actionnaire + 'actionnaire.consulterChangement', + 'actionnaire.listerChangement', + + // Représentant Légal + 'représentantLégal.consulterChangement', + 'représentantLégal.listerChangement', ]; const drealPolicies: ReadonlyArray = [ @@ -1247,6 +1255,14 @@ const acheteurObligéPolicies: ReadonlyArray = [ // Achèvement // 'achèvement.transmettre', + + // Actionnaire + 'actionnaire.consulterChangement', + 'actionnaire.listerChangement', + + // Représentant Légal + 'représentantLégal.consulterChangement', + 'représentantLégal.listerChangement', ]; const caisseDesDépôtsPolicies: ReadonlyArray = [ From 05895b60c85a226713135f8d43b9423aa5aef12c Mon Sep 17 00:00:00 2001 From: Violette <27735540+VioMrqs@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:22:26 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Enregistrer=20un=20cha?= =?UTF-8?q?ngement=20d'actionnaire=20(#2677)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bootstrap/src/setupLaur\303\251at.ts" | 2 + .../cli/src/commands/actionnaire/migrer.ts | 14 +-- ...onnaire-mod\303\250le-r\303\251ponse.docx" | Bin 64103 -> 64294 bytes ...R\303\251ponseSign\303\251eActionnaire.ts" | 1 + .../getProjectPage/_utils/getActionnaire.ts | 119 +++++++++++------- .../components/ProjectActions.tsx | 9 +- .../projectDetailsPage/sections/Contact.tsx | 5 +- ...onnaireEnregistr\303\251.notifications.ts" | 49 ++++++++ .../laur\303\251at/actionnaire/index.ts" | 4 + .../actionnaire/templateIds.ts" | 1 + .../actionnaireModifi\303\251.projector.ts" | 45 +------ ...ActionnaireEnregistr\303\251.projector.ts" | 62 +++++++++ .../laur\303\251at/actionnaire/index.ts" | 5 + .../src/laur\303\251at/actionnaire.routes.ts" | 9 +- .../actionnaire/changement/demander/page.tsx | 4 +- .../changement/enregistrer/page.tsx | 42 +++++++ .../changement/modele-reponse/route.ts | 8 +- .../actionnaire/modifier/page.tsx | 43 +++---- .../mapToActionnaireTimelineItemProps.tsx | 7 ++ ...ireEnregistr\303\251TimelineItemProps.tsx" | 40 ++++++ .../AccorderChangementActionnaire.form.tsx" | 10 +- .../accorderChangementActionnaire.action.ts" | 1 + .../AnnulerChangementActionnaire.form.tsx" | 6 +- .../annulerChangementActionnaire.action.ts" | 2 +- .../RejeterChangementActionnaire.form.tsx" | 17 ++- .../DemanderChangementActionnaire.form.tsx | 6 +- .../InfoxBoxDemandeActionnaire.tsx | 2 +- .../demanderChangementActionnaire.action.ts | 2 +- .../EnregistrerChangementActionnaire.form.tsx | 104 +++++++++++++++ .../EnregistrerChangementActionnaire.page.tsx | 30 +++++ ...registrerChangementActionnaire.stories.tsx | 34 +++++ ...enregistrerChangementActionnaire.action.ts | 48 +++++++ .../modifier/ModifierActionnaire.form.tsx | 27 ++-- .../modifier/ModifierActionnaire.page.tsx | 15 +-- .../modifier/ModifierActionnaire.stories.tsx | 2 - .../modifier/modifierActionnaire.action.ts | 5 +- ...tRepr\303\251sentantL\303\251gal.page.tsx" | 6 +- ...tRepr\303\251sentantL\303\251gal.form.tsx" | 3 - ...Repr\303\251sentantL\303\251gal.action.ts" | 5 +- ...tRepr\303\251sentantL\303\251gal.form.tsx" | 3 - ...Repr\303\251sentantL\303\251gal.action.ts" | 6 +- .../src/actionnaire/actionnaire.aggregate.ts" | 12 ++ .../src/actionnaire/actionnaire.register.ts" | 4 + .../enregistrerChangement.behavior.ts" | 109 ++++++++++++++++ .../enregistrerChangement.command.ts" | 70 +++++++++++ .../enregistrerChangement.usecase.ts" | 62 +++++++++ .../laur\303\251at/src/actionnaire/errors.ts" | 6 - .../laur\303\251at/src/actionnaire/index.ts" | 11 +- .../modifier/modifierActionnaire.behavior.ts" | 51 +------- .../modifier/modifierActionnaire.command.ts" | 31 ----- .../modifier/modifierActionnaire.usecase.ts" | 6 - .../domain/utilisateur/src/role.valueType.ts | 9 +- .../enregistrerChangementActionnaire.feature" | 81 ++++++++++++ .../actionnaire/modifierActionnaire.feature" | 62 +-------- .../stepDefinitions/actionnaire.then.ts" | 11 ++ .../stepDefinitions/actionnaire.when.ts" | 86 ++++++++++--- 56 files changed, 1041 insertions(+), 373 deletions(-) create mode 100644 "packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.notifications.ts" create mode 100644 "packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.projector.ts" create mode 100644 packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/enregistrer/page.tsx create mode 100644 "packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToChangementActionnaireEnregistr\303\251TimelineItemProps.tsx" create mode 100644 packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.form.tsx create mode 100644 packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.page.tsx create mode 100644 packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.stories.tsx create mode 100644 packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/enregistrerChangementActionnaire.action.ts create mode 100644 "packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.behavior.ts" create mode 100644 "packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.command.ts" create mode 100644 "packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.usecase.ts" create mode 100644 "packages/specifications/src/projet/laur\303\251at/actionnaire/enregistrerChangementActionnaire.feature" diff --git "a/packages/applications/bootstrap/src/setupLaur\303\251at.ts" "b/packages/applications/bootstrap/src/setupLaur\303\251at.ts" index 6bf5f28d08..a29c54c808 100644 --- "a/packages/applications/bootstrap/src/setupLaur\303\251at.ts" +++ "b/packages/applications/bootstrap/src/setupLaur\303\251at.ts" @@ -108,6 +108,7 @@ export const setupLauréat = async ({ 'ChangementActionnaireAccordé-V1', 'ChangementActionnaireRejeté-V1', 'ChangementActionnaireSupprimé-V1', + 'ChangementActionnaireEnregistré-V1', ], eventHandler: async (event) => { await mediator.send({ @@ -405,6 +406,7 @@ export const setupLauréat = async ({ 'ChangementActionnaireAccordé-V1', 'ChangementActionnaireRejeté-V1', 'ChangementActionnaireAnnulé-V1', + 'ChangementActionnaireEnregistré-V1', ], eventHandler: async (event) => mediator.publish({ diff --git a/packages/applications/cli/src/commands/actionnaire/migrer.ts b/packages/applications/cli/src/commands/actionnaire/migrer.ts index 179d3bfab5..c8213647f7 100644 --- a/packages/applications/cli/src/commands/actionnaire/migrer.ts +++ b/packages/applications/cli/src/commands/actionnaire/migrer.ts @@ -137,15 +137,15 @@ export class Migrer extends Command { }, }; - const eventModifié: Actionnaire.ActionnaireModifiéEvent = { - type: 'ActionnaireModifié-V1', + const eventEnregistré: Actionnaire.ChangementActionnaireEnregistréEvent = { + type: 'ChangementActionnaireEnregistré-V1', payload: { actionnaire: cleanInput(modification.actionnaire), identifiantProjet, - modifiéLe: requestedOn, - modifiéPar: modification.email, + enregistréLe: requestedOn, + enregistréPar: modification.email, raison: cleanInput(modification.justification), - pièceJustificative: formatRequestFile ? { format: formatRequestFile } : undefined, + pièceJustificative: { format: formatRequestFile || 'application/pdf' }, }, }; @@ -187,7 +187,7 @@ export class Migrer extends Command { console.log( `📨 Demande automatiquement acceptée pour ${identifiantProjet} (${candidature?.emailContact})`, ); - eventsPerProjet[modification.identifiantProjet].push(eventModifié); + eventsPerProjet[modification.identifiantProjet].push(eventEnregistré); } else { eventsPerProjet[modification.identifiantProjet].push(request); @@ -209,7 +209,7 @@ export class Migrer extends Command { } break; case 'information validée': - eventsPerProjet[modification.identifiantProjet].push(eventModifié); + eventsPerProjet[modification.identifiantProjet].push(eventEnregistré); } } diff --git "a/packages/applications/document-builder/src/assets/docx/actionnaire-mod\303\250le-r\303\251ponse.docx" "b/packages/applications/document-builder/src/assets/docx/actionnaire-mod\303\250le-r\303\251ponse.docx" index 2091056e65b329f2d258007a8b3be1a002c797cc..01a5c907a6b90a611525fa0b53aca18dab3960f3 100644 GIT binary patch delta 28356 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 *N_8hZq{o4KUm&PwOR4q4 zM{pw0097peZgOi6DKyI8$NABtH#xnoxod>lyzUy0SWKtbb46=s{}2{K*Qjt|IO3Nm z5^yA*HB#?gHEIJ6O^7+29%`-9ojdJkl*eYvZ3|NKIb1${{ zQ8Fkqfdc_1ISfw@0dDp$FUsR}h%31bwuEg*{PnkJuw{@Dn}qxG1q+SZs>Y~wyQtqx zieGEBz7I(wOs5+=(?L7VC^ku4&Xk_!7ZcU6h}*`4)#ok7A81?3?(cJl$<*X6wpA@R zRUNn1fj)N6+AXdF0?}TUn+k9>z7YFRRct~kgTAK6sM4dXd-_g+E|l!}qbkr?eVoyk=ciHss3cN^8$fLj#Bo0~x!@e9~+cV3*|+O^N$s zdy(R(Q@H7_XZJ|+arkrlEwXkva;}Eb9|LZTxJaJtwUoGg3!)$QmHK4c3u}XJ?zg@+ z&$bgl3;SOUY%iNY$CMU|DQtW>UBPJugxt9rSthj6wGmGj2l^oOQ^@XW&hvadC)c5p zMVLEACb~>axy$qDP70ytn*nCcN4@mVRw_2Ol!pt%9(j_h!!mWVCP&l6 z8~tOlfT3e#EZy&OT0?kqK2CHl6UxD6t0jes!tJ70Ga&ntf_IkR`ur+e)P<|QR_@fO z#W4@1@c}P9FF`ie%oS!l4_X<>pQ6(HSm)ug(tn+B*TkToVIO>glChPdh0r#;1V)@c-3F>^PxQ#mVlKdQ{sH6KvZvBE9VtTUA#21G<;i{pBV>-8(o z3c6OdBWowEUV99(x41EX1-E>U@mV;teh0pOc2_3 z2hf3Fl1`{=y~B?AhGmA`fwHF0ZxGw0e%_4PHCQ(_hrTKoYrEwOZV%VA?Dd@6<8>HOd=bY;i3>i8aC zg8;WZHYM++_mVF2XHt(kt=o6;)ETccvgkDI#L0L+7WrTmYB8pCNZVYATN!sKo%DnhA$*yk`E` z!s_o0*Ol=GDLiLx`tQIBUzx4ersjdO0Qu-fM@_vwFiLr@GkO-pjE%1&n-X>s0ZXyvG7$CZ8QWy{{~dS=-`|97QY7`d^X;aJL#`C! ziEWpnS*a~~d-F^vDoOxOV0>}6jxBCz5z(7k=#fAIeEO9A89NGUPrgQ@`!f>YNqmxF z1(oX%>_CT^J%gNQeX9BAdc#>X1PxPL$uMM!9sPt?3s9(ftDuO0OFbYDu{8z|hQp~*7^JqIBr#uS; z1brs{tPB5r>lR4mjN>;31Uw^!nNS7=E8~G`&d?}-%jVyal25^sP*0|lrzaY;ct-h* z`FAYX@7h0@UJ&v5AI!faUjD}1fpq_a`M2}wZ%jVu-8rT4Z+`ymS@;{XY4V?&!~VFd zp8X7JO3+V6?%y|#JqLgq{M+pPH+2;V8}f(Ke_8{7BmUc)2GA=A)pL*kvLHQK46*+} z{?p@HP$C5IjQRQ~J`D2|Avg2nO8Y0K3^WP>JP-IOgcN4c0c7St!18Za|29?pn>+{f z>f#T1#}z3|iXSNPg88|%wM$ZHIS|z)9_ZkL;IERqr;Bj3jP?&O!nX_vwP(M%{y%}$9TWfn delta 28110 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 + estAccordé: boolean; }; }; diff --git a/packages/applications/legacy/src/controllers/project/getProjectPage/_utils/getActionnaire.ts b/packages/applications/legacy/src/controllers/project/getProjectPage/_utils/getActionnaire.ts index c249606572..2d5d159d2e 100644 --- a/packages/applications/legacy/src/controllers/project/getProjectPage/_utils/getActionnaire.ts +++ b/packages/applications/legacy/src/controllers/project/getProjectPage/_utils/getActionnaire.ts @@ -7,13 +7,15 @@ import { Routes } from '@potentiel-applications/routes'; import { Role } from '@potentiel-domain/utilisateur'; import { getLogger } from '@potentiel-libraries/monitoring'; import { IdentifiantProjet } from '@potentiel-domain/common'; -import { getAbandonStatut } from './getAbandon'; import { getAttestationDeConformité } from './getAttestationDeConformité'; export type GetActionnaireForProjectPage = { nom: string; affichage?: { + // label dans la page projet label: string; + // action dans le menu déroulant page projet + action?: string; url: string; }; demandeEnCours?: { @@ -33,61 +35,79 @@ export const getActionnaire = async ({ demandeNécessiteInstruction, }: Props): Promise => { try { - const utilisateur = Role.convertirEnValueType(rôle); + const role = Role.convertirEnValueType(rôle); const actionnaire = await mediator.send({ type: 'Lauréat.Actionnaire.Query.ConsulterActionnaire', data: { identifiantProjet: identifiantProjet.formatter() }, }); - const estAbandonnéOuEnCoursAbandonOuAchevé = await checkAbandonAndAchèvement( - identifiantProjet, - rôle, - ); - - const nePeutFaireAucuneAction = - utilisateur.nom === 'porteur-projet' && estAbandonnéOuEnCoursAbandonOuAchevé; + const estAbandonnéOuAchevé = await checkAbandonAndAchèvement(identifiantProjet, rôle); if (Option.isSome(actionnaire)) { + const nom = actionnaire.actionnaire; + const dateDemandeExistanteDeChangement = await mediator.send({ type: 'Lauréat.Actionnaire.Query.ConsulterDateChangementActionnaire', data: { identifiantProjet: identifiantProjet.formatter() }, }); - const aUneDemandeEnCours = Option.isSome(dateDemandeExistanteDeChangement); + if (Option.isSome(dateDemandeExistanteDeChangement)) { + return { + nom, + demandeEnCours: role.aLaPermission('actionnaire.consulterChangement') + ? { + demandéeLe: dateDemandeExistanteDeChangement.formatter(), + } + : undefined, + }; + } + const peutModifier = role.aLaPermission('actionnaire.modifier'); const peutFaireUneDemandeDeChangement = demandeNécessiteInstruction && - utilisateur.aLaPermission('actionnaire.demanderChangement') && - !aUneDemandeEnCours; + role.aLaPermission('actionnaire.demanderChangement') && + !estAbandonnéOuAchevé; - const peutModifier = + const peutEnregistrerChangement = !demandeNécessiteInstruction && - utilisateur.aLaPermission('actionnaire.modifier') && - !aUneDemandeEnCours; - + role.aLaPermission('actionnaire.enregistrerChangement') && + !estAbandonnéOuAchevé; + + if (peutModifier) { + return { + nom, + affichage: { + url: Routes.Actionnaire.modifier(identifiantProjet.formatter()), + label: 'Modifier', + }, + }; + } + + if (peutEnregistrerChangement) { + return { + nom, + affichage: { + url: Routes.Actionnaire.changement.enregistrer(identifiantProjet.formatter()), + label: 'Faire un changement', + action: "Changer d'actionnaire(s)", + }, + }; + } + + if (peutFaireUneDemandeDeChangement) { + return { + nom, + affichage: { + url: Routes.Actionnaire.changement.demander(identifiantProjet.formatter()), + label: 'Faire une demande de changement', + action: 'Demander un changement d’actionnaire(s)', + }, + }; + } return { - nom: actionnaire.actionnaire, - affichage: nePeutFaireAucuneAction - ? undefined - : peutModifier - ? { - url: Routes.Actionnaire.modifier(identifiantProjet.formatter()), - label: "Changer d'actionnaire(s)", - } - : peutFaireUneDemandeDeChangement - ? { - url: Routes.Actionnaire.changement.demander(identifiantProjet.formatter()), - label: 'Demander un changement d’actionnaire(s)', - } - : undefined, - demandeEnCours: - utilisateur.aLaPermission('actionnaire.consulterChangement') && aUneDemandeEnCours - ? { - demandéeLe: dateDemandeExistanteDeChangement.formatter(), - } - : undefined, + nom, }; } @@ -101,7 +121,7 @@ export const getActionnaire = async ({ if (Option.isSome(candidature)) { return { nom: candidature.sociétéMère, - affichage: utilisateur.aLaPermission('candidature.corriger') + affichage: role.aLaPermission('candidature.corriger') ? { url: Routes.Candidature.corriger(identifiantProjet.formatter()), label: 'Modifier la candidature', @@ -123,13 +143,24 @@ const checkAbandonAndAchèvement = async ( identifiantProjet: Props['identifiantProjet'], rôle: Props['rôle'], ) => { - const statutAbandon = await getAbandonStatut(identifiantProjet); const attestationConformitéExistante = await getAttestationDeConformité(identifiantProjet, rôle); - return ( - (statutAbandon && - (Abandon.StatutAbandon.convertirEnValueType(statutAbandon.statut).estAccordé() || - Abandon.StatutAbandon.convertirEnValueType(statutAbandon.statut).estEnCours())) || - !!attestationConformitéExistante - ); + if (attestationConformitéExistante) { + return true; + } + + try { + const abandon = await mediator.send({ + type: 'Lauréat.Abandon.Query.ConsulterAbandon', + data: { identifiantProjetValue: identifiantProjet.formatter() }, + }); + if (Option.isNone(abandon)) return false; + return abandon.statut.estAccordé() || abandon.statut.estEnCours(); + } catch (e) { + getLogger('getActionnaire.checkActionnaire').warn("Impossible de récupérer l'abandon", { + error: (e as Error)?.message, + identifiantProjet: identifiantProjet.formatter(), + }); + return false; + } }; diff --git a/packages/applications/legacy/src/views/pages/projectDetailsPage/components/ProjectActions.tsx b/packages/applications/legacy/src/views/pages/projectDetailsPage/components/ProjectActions.tsx index 9bce0b3639..17eb5074a6 100644 --- a/packages/applications/legacy/src/views/pages/projectDetailsPage/components/ProjectActions.tsx +++ b/packages/applications/legacy/src/views/pages/projectDetailsPage/components/ProjectActions.tsx @@ -33,7 +33,7 @@ const EnregistrerUneModification = ({ project }: EnregistrerUneModificationProps ).formatter(), )} > - Modification de l'actionnariat + Modification d'actionnaire(s) ); @@ -46,7 +46,7 @@ type PorteurProjetActionsProps = { hasAttestationConformité: boolean; peutFaireDemandeChangementReprésentantLégal: boolean; actionnaireMenu?: { - label: string; + action?: string; url: string; }; }; @@ -91,12 +91,12 @@ const PorteurProjetActions = ({ Changer de fournisseur - {actionnaireMenu ? ( + {actionnaireMenu?.action ? ( - {actionnaireMenu.label} + {actionnaireMenu.action} ) : ( <> @@ -221,6 +221,7 @@ type ProjectActionsProps = { peutFaireDemandeChangementReprésentantLégal: boolean; actionnaireMenu?: { label: string; + action?: string; url: string; }; }; diff --git a/packages/applications/legacy/src/views/pages/projectDetailsPage/sections/Contact.tsx b/packages/applications/legacy/src/views/pages/projectDetailsPage/sections/Contact.tsx index 11c9fc6972..b9a046861d 100644 --- a/packages/applications/legacy/src/views/pages/projectDetailsPage/sections/Contact.tsx +++ b/packages/applications/legacy/src/views/pages/projectDetailsPage/sections/Contact.tsx @@ -45,10 +45,7 @@ export const Contact = ({ {représentantLégal.modification && ( - Modifier{' '} - {représentantLégal.modification.type === 'lauréat' - ? 'le représentant légal' - : 'la candidature'} + Modifier {représentantLégal.modification.type === 'lauréat' ? '' : 'la candidature'} )} {représentantLégal.demandeDeModification?.peutConsulterLaDemandeExistante && ( diff --git "a/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.notifications.ts" "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.notifications.ts" new file mode 100644 index 0000000000..161032339c --- /dev/null +++ "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.notifications.ts" @@ -0,0 +1,49 @@ +import { récupérerDrealsParIdentifiantProjetAdapter } from '@potentiel-infrastructure/domain-adapters'; +import { IdentifiantProjet } from '@potentiel-domain/common'; +import { getLogger } from '@potentiel-libraries/monitoring'; +import { Routes } from '@potentiel-applications/routes'; +import { Actionnaire } from '@potentiel-domain/laureat'; + +import { RegisterActionnaireNotificationDependencies } from '.'; + +import { actionnaireNotificationTemplateId } from './templateIds'; + +type ChangementActionnaireEnregistréNotificationsProps = { + sendEmail: RegisterActionnaireNotificationDependencies['sendEmail']; + event: Actionnaire.ChangementActionnaireEnregistréEvent; + projet: { + nom: string; + département: string; + }; + baseUrl: string; +}; + +export const changementActionnaireEnregistréNotifications = async ({ + sendEmail, + event, + projet, + baseUrl, +}: ChangementActionnaireEnregistréNotificationsProps) => { + const identifiantProjet = IdentifiantProjet.convertirEnValueType(event.payload.identifiantProjet); + const dreals = await récupérerDrealsParIdentifiantProjetAdapter(identifiantProjet); + + if (dreals.length === 0) { + getLogger().error('Aucune dreal trouvé(e)', { + identifiantProjet: identifiantProjet.formatter(), + application: 'notifications', + fonction: 'changementActionnaireEnregistréNotifications', + }); + return; + } + + await sendEmail({ + templateId: actionnaireNotificationTemplateId.modifier, + messageSubject: `Potentiel - Enregistrement d'un changement d'actionnaire pour le projet ${projet.nom} dans le département ${projet.département}`, + recipients: dreals, + variables: { + nom_projet: projet.nom, + departement_projet: projet.département, + url: `${baseUrl}${Routes.Projet.details(identifiantProjet.formatter())}`, + }, + }); +}; diff --git "a/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/index.ts" "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/index.ts" index 667f351d34..245aa989cc 100644 --- "a/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/index.ts" +++ "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/index.ts" @@ -15,6 +15,7 @@ import { changementActionnaireDemandéNotifications } from './changementActionna import { changementActionnaireRejetéNotifications } from './changementActionnaireRejeté.notifications'; import { actionnaireModifiéNotifications } from './actionnaireModifié.notifications'; import { changementActionnaireAccordéNotifications } from './changementActionnaireAccordé.notifications'; +import { changementActionnaireEnregistréNotifications } from './changementActionnaireEnregistré.notifications'; export type SubscriptionEvent = Actionnaire.ActionnaireEvent & Event; @@ -83,6 +84,9 @@ export const register = ({ sendEmail }: RegisterActionnaireNotificationDependenc .with({ type: 'ChangementActionnaireAnnulé-V1' }, async (event) => changementActionnaireAnnuléNotifications({ sendEmail, event, projet, baseUrl }), ) + .with({ type: 'ChangementActionnaireEnregistré-V1' }, async (event) => + changementActionnaireEnregistréNotifications({ sendEmail, event, projet, baseUrl }), + ) .otherwise(() => Promise.resolve()); }; diff --git "a/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/templateIds.ts" "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/templateIds.ts" index 3cc779a3a4..384fe50887 100644 --- "a/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/templateIds.ts" +++ "b/packages/applications/notifications/src/subscribers/laur\303\251at/actionnaire/templateIds.ts" @@ -4,4 +4,5 @@ export const actionnaireNotificationTemplateId = { modifier: 6619337, rejeter: 6619256, demanderChangement: 6619284, + enregistrerChangement: 6677623, }; diff --git "a/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/actionnaireModifi\303\251.projector.ts" "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/actionnaireModifi\303\251.projector.ts" index e83c2ae01a..c38e112629 100644 --- "a/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/actionnaireModifi\303\251.projector.ts" +++ "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/actionnaireModifi\303\251.projector.ts" @@ -1,14 +1,9 @@ import { Actionnaire } from '@potentiel-domain/laureat'; -import { findProjection } from '@potentiel-infrastructure/pg-projections'; -import { Candidature } from '@potentiel-domain/candidature'; -import { IdentifiantProjet } from '@potentiel-domain/common'; -import { getLogger } from '@potentiel-libraries/monitoring'; -import { Option } from '@potentiel-libraries/monads'; -import { updateOneProjection, upsertProjection } from '../../../infrastructure'; +import { updateOneProjection } from '../../../infrastructure'; export const actionnaireModifiéProjector = async ({ - payload: { identifiantProjet, modifiéLe, actionnaire, modifiéPar, raison, pièceJustificative }, + payload: { identifiantProjet, modifiéLe, actionnaire }, }: Actionnaire.ActionnaireModifiéEvent) => { await updateOneProjection(`actionnaire|${identifiantProjet}`, { actionnaire: { @@ -16,40 +11,4 @@ export const actionnaireModifiéProjector = async ({ misÀJourLe: modifiéLe, }, }); - - const candidature = await findProjection( - `candidature|${identifiantProjet}`, - ); - - if (Option.isNone(candidature)) { - getLogger().error('Candidature non trouvée', { - identifiantProjet, - }); - return; - } - - const { appelOffre, période, famille } = - IdentifiantProjet.convertirEnValueType(identifiantProjet); - - await upsertProjection( - `changement-actionnaire|${identifiantProjet}#${modifiéLe}`, - { - identifiantProjet, - projet: { - nom: candidature.nomProjet, - appelOffre, - période, - famille, - région: candidature.localité.région, - }, - demande: { - nouvelActionnaire: actionnaire, - statut: Actionnaire.StatutChangementActionnaire.informationEnregistrée.statut, - demandéePar: modifiéPar, - demandéeLe: modifiéLe, - raison, - pièceJustificative, - }, - }, - ); }; diff --git "a/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.projector.ts" "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.projector.ts" new file mode 100644 index 0000000000..29a6ac91d4 --- /dev/null +++ "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/changementActionnaireEnregistr\303\251.projector.ts" @@ -0,0 +1,62 @@ +import { Actionnaire } from '@potentiel-domain/laureat'; +import { findProjection } from '@potentiel-infrastructure/pg-projections'; +import { Candidature } from '@potentiel-domain/candidature'; +import { IdentifiantProjet } from '@potentiel-domain/common'; +import { getLogger } from '@potentiel-libraries/monitoring'; +import { Option } from '@potentiel-libraries/monads'; + +import { updateOneProjection, upsertProjection } from '../../../infrastructure'; + +export const changementActionnaireEnregistréProjector = async ({ + payload: { + identifiantProjet, + enregistréLe, + actionnaire, + enregistréPar, + raison, + pièceJustificative, + }, +}: Actionnaire.ChangementActionnaireEnregistréEvent) => { + await updateOneProjection(`actionnaire|${identifiantProjet}`, { + actionnaire: { + nom: actionnaire, + misÀJourLe: enregistréLe, + }, + }); + + const candidature = await findProjection( + `candidature|${identifiantProjet}`, + ); + + if (Option.isNone(candidature)) { + getLogger().error('Candidature non trouvée', { + identifiantProjet, + }); + return; + } + + const { appelOffre, période, famille } = + IdentifiantProjet.convertirEnValueType(identifiantProjet); + + await upsertProjection( + `changement-actionnaire|${identifiantProjet}#${enregistréLe}`, + { + identifiantProjet, + projet: { + nom: candidature.nomProjet, + appelOffre, + période, + famille, + région: candidature.localité.région, + }, + demande: { + nouvelActionnaire: actionnaire, + statut: Actionnaire.StatutChangementActionnaire.informationEnregistrée.statut, + demandéePar: enregistréPar, + demandéeLe: enregistréLe, + raison, + pièceJustificative, + }, + }, + ); +}; diff --git "a/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/index.ts" "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/index.ts" index 33459505fa..264308380e 100644 --- "a/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/index.ts" +++ "b/packages/applications/projectors/src/subscribers/laur\303\251at/actionnaire/index.ts" @@ -12,6 +12,7 @@ import { changementActionnaireAnnuléProjector } from './changementActionnaireAn import { changementActionnaireDemandéProjector } from './changementActionnaireDemandé.projector'; import { changementActionnaireRejetéProjector } from './changementActionnaireRejeté.projector'; import { changementActionnaireSuppriméProjector } from './changementActionnaireSupprimé.projector'; +import { changementActionnaireEnregistréProjector } from './changementActionnaireEnregistré.projector'; export type SubscriptionEvent = (Actionnaire.ActionnaireEvent & Event) | RebuildTriggered; @@ -28,6 +29,10 @@ export const register = () => { .with({ type: 'ChangementActionnaireAccordé-V1' }, changementActionnaireAccordéProjector) .with({ type: 'ChangementActionnaireRejeté-V1' }, changementActionnaireRejetéProjector) .with({ type: 'ChangementActionnaireSupprimé-V1' }, changementActionnaireSuppriméProjector) + .with( + { type: 'ChangementActionnaireEnregistré-V1' }, + changementActionnaireEnregistréProjector, + ) .exhaustive(); mediator.register('System.Projector.Lauréat.Actionnaire', handler); diff --git "a/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" "b/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" index 2ced222494..1d82cf0d17 100644 --- "a/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" +++ "b/packages/applications/routes/src/laur\303\251at/actionnaire.routes.ts" @@ -12,11 +12,14 @@ export const modifier = (identifiantProjet: string) => export const changement = { demander: (identifiantProjet: string) => `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/demander`, + enregistrer: (identifiantProjet: string) => + `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/enregistrer`, détails: (identifiantProjet: string, demandéLe: string) => `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/${demandéLe}`, - téléchargerModèleRéponse: (identifiantProjet: string) => - `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/modele-reponse`, - + téléchargerModèleRéponseAccordé: (identifiantProjet: string) => + `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/modele-reponse?estAccordé=true`, + téléchargerModèleRéponseRejeté: (identifiantProjet: string) => + `/laureats/${encodeParameter(identifiantProjet)}/actionnaire/changement/modele-reponse?estAccordé=false`, lister: (filters: ListerFilters = {}) => { const searchParams = new URLSearchParams(); diff --git a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/demander/page.tsx b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/demander/page.tsx index c3aa0dd218..9f083cf57a 100644 --- a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/demander/page.tsx +++ b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/demander/page.tsx @@ -13,8 +13,8 @@ import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling'; import { DemanderChangementActionnairePage } from '@/components/pages/actionnaire/demanderChangement/DemanderChangementActionnaire.page'; export const metadata: Metadata = { - title: "Demander une modification de l'actionnariat d'un projet - Potentiel", - description: "Formulaire de demande de changement d'actionnaire d'un projet", + title: "Demander un changement d'actionnaire(s) d'un projet - Potentiel", + description: "Formulaire de demande de changement d'actionnaire(s) d'un projet", }; export default async function Page({ params: { identifiant } }: IdentifiantParameter) { diff --git a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/enregistrer/page.tsx b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/enregistrer/page.tsx new file mode 100644 index 0000000000..e6a3d8dba0 --- /dev/null +++ b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/enregistrer/page.tsx @@ -0,0 +1,42 @@ +import { mediator } from 'mediateur'; +import { Metadata } from 'next'; +import { notFound } from 'next/navigation'; + +import { Option } from '@potentiel-libraries/monads'; +import { Actionnaire } from '@potentiel-domain/laureat'; +import { IdentifiantProjet } from '@potentiel-domain/common'; +import { mapToPlainObject } from '@potentiel-domain/core'; + +import { decodeParameter } from '@/utils/decodeParameter'; +import { IdentifiantParameter } from '@/utils/identifiantParameter'; +import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling'; +import { EnregistrerChangementActionnairePage } from '@/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.page'; + +export const metadata: Metadata = { + title: "Changement d'actionnaire(s) d'un projet - Potentiel", + description: "Formulaire de demande de changement d'actionnaire(s) d'un projet", +}; + +export default async function Page({ params: { identifiant } }: IdentifiantParameter) { + return PageWithErrorHandling(async () => { + const identifiantProjet = IdentifiantProjet.convertirEnValueType(decodeParameter(identifiant)); + + const actionnaireActuel = await mediator.send({ + type: 'Lauréat.Actionnaire.Query.ConsulterActionnaire', + data: { + identifiantProjet: identifiantProjet.formatter(), + }, + }); + + if (Option.isNone(actionnaireActuel)) { + return notFound(); + } + + return ( + + ); + }); +} diff --git a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/modele-reponse/route.ts b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/modele-reponse/route.ts index 0826badf98..ea746a384b 100644 --- a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/modele-reponse/route.ts +++ b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/changement/modele-reponse/route.ts @@ -1,5 +1,6 @@ import { mediator } from 'mediateur'; import { notFound } from 'next/navigation'; +import { NextRequest } from 'next/server'; import { AppelOffre } from '@potentiel-domain/appel-offre'; import { Candidature } from '@potentiel-domain/candidature'; @@ -17,9 +18,13 @@ import { IdentifiantParameter } from '@/utils/identifiantParameter'; import { withUtilisateur } from '@/utils/withUtilisateur'; import { formatIdentifiantProjetForDocument } from '@/utils/modèle-document/formatIdentifiantProjetForDocument'; -export const GET = async (_: Request, { params: { identifiant } }: IdentifiantParameter) => +export const GET = async ( + request: NextRequest, + { params: { identifiant } }: IdentifiantParameter, +) => withUtilisateur(async (utilisateur) => { const identifiantProjet = decodeParameter(identifiant); + const estAccordé = request.nextUrl.searchParams.get('estAccordé') === 'true'; const utilisateurDétails = await mediator.send({ type: 'Utilisateur.Query.ConsulterUtilisateur', @@ -126,6 +131,7 @@ export const GET = async (_: Request, { params: { identifiant } }: IdentifiantPa nouvelActionnaire: demandeDeChangement.demande.nouvelActionnaire, referenceParagrapheActionnaire: texteChangementDActionnariat.référenceParagraphe, contenuParagrapheActionnaire: texteChangementDActionnariat?.dispositions, + estAccordé, }, }); diff --git a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/modifier/page.tsx b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/modifier/page.tsx index 1b08e77aed..651843f9ce 100644 --- a/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/modifier/page.tsx +++ b/packages/applications/ssr/src/app/laureats/[identifiant]/actionnaire/modifier/page.tsx @@ -6,12 +6,10 @@ import { Option } from '@potentiel-libraries/monads'; import { Actionnaire } from '@potentiel-domain/laureat'; import { IdentifiantProjet } from '@potentiel-domain/common'; import { mapToPlainObject } from '@potentiel-domain/core'; -import { Role } from '@potentiel-domain/utilisateur'; import { decodeParameter } from '@/utils/decodeParameter'; import { IdentifiantParameter } from '@/utils/identifiantParameter'; import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling'; -import { withUtilisateur } from '@/utils/withUtilisateur'; import { ModifierActionnairePage } from '@/components/pages/actionnaire/modifier/ModifierActionnaire.page'; export const metadata: Metadata = { @@ -20,30 +18,25 @@ export const metadata: Metadata = { }; export default async function Page({ params: { identifiant } }: IdentifiantParameter) { - return PageWithErrorHandling(async () => - withUtilisateur(async (utilisateur) => { - const identifiantProjet = IdentifiantProjet.convertirEnValueType( - decodeParameter(identifiant), - ); + return PageWithErrorHandling(async () => { + const identifiantProjet = IdentifiantProjet.convertirEnValueType(decodeParameter(identifiant)); - const actionnaireActuel = await mediator.send({ - type: 'Lauréat.Actionnaire.Query.ConsulterActionnaire', - data: { - identifiantProjet: identifiantProjet.formatter(), - }, - }); + const actionnaireActuel = await mediator.send({ + type: 'Lauréat.Actionnaire.Query.ConsulterActionnaire', + data: { + identifiantProjet: identifiantProjet.formatter(), + }, + }); - if (Option.isNone(actionnaireActuel)) { - return notFound(); - } + if (Option.isNone(actionnaireActuel)) { + return notFound(); + } - return ( - - ); - }), - ); + return ( + + ); + }); } diff --git a/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToActionnaireTimelineItemProps.tsx b/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToActionnaireTimelineItemProps.tsx index 17514dacc1..54e4991069 100644 --- a/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToActionnaireTimelineItemProps.tsx +++ b/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToActionnaireTimelineItemProps.tsx @@ -11,6 +11,7 @@ import { mapToChangementActionnaireAnnuléTimelineItemProps } from './mapToChang import { mapToActionnaireModifiéTimelineItemProps } from './mapToActionnaireModifiéTimelineItemsProps'; import { mapToChangementActionnaireDemandéTimelineItemProps } from './mapToChangementActionnaireDemandéTimelineItemProps'; import { mapToActionnaireImportéTimelineItemProps } from './mapToActionnaireImportéTimelineItemsProps'; +import { mapToChangementActionnaireEnregistréTimelineItemProps } from './mapToChangementActionnaireEnregistréTimelineItemProps'; export const mapToActionnaireTimelineItemProps = (record: HistoryRecord) => { return match(record) @@ -27,6 +28,12 @@ export const mapToActionnaireTimelineItemProps = (record: HistoryRecord) => { }, mapToActionnaireModifiéTimelineItemProps, ) + .with( + { + type: 'ChangementActionnaireEnregistré-V1', + }, + mapToChangementActionnaireEnregistréTimelineItemProps, + ) .with( { type: 'ChangementActionnaireDemandé-V1', diff --git "a/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToChangementActionnaireEnregistr\303\251TimelineItemProps.tsx" "b/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToChangementActionnaireEnregistr\303\251TimelineItemProps.tsx" new file mode 100644 index 0000000000..a48ab64381 --- /dev/null +++ "b/packages/applications/ssr/src/components/molecules/historique/timeline/actionnaire/mapToChangementActionnaireEnregistr\303\251TimelineItemProps.tsx" @@ -0,0 +1,40 @@ +import { Routes } from '@potentiel-applications/routes'; +import { DocumentProjet } from '@potentiel-domain/document'; +import { Historique } from '@potentiel-domain/historique'; +import { Actionnaire } from '@potentiel-domain/laureat'; + +import { DownloadDocument } from '@/components/atoms/form/document/DownloadDocument'; + +export const mapToChangementActionnaireEnregistréTimelineItemProps = ( + modification: Historique.ListerHistoriqueProjetReadModel['items'][number], +) => { + const { enregistréLe, enregistréPar, identifiantProjet, pièceJustificative, actionnaire } = + modification.payload as Actionnaire.ChangementActionnaireEnregistréEvent['payload']; + return { + date: enregistréLe, + title: ( +
Actionnaire modifié par {{enregistréPar}}
+ ), + content: ( +
+
+ Nouvel actionnaire : {actionnaire} +
+ + +
+ ), + }; +}; diff --git "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/AccorderChangementActionnaire.form.tsx" "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/AccorderChangementActionnaire.form.tsx" index 2ab8d93555..6fc3eedc6d 100644 --- "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/AccorderChangementActionnaire.form.tsx" +++ "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/AccorderChangementActionnaire.form.tsx" @@ -30,12 +30,12 @@ export const AccorderChangementActionnaire = ({ return ( <>

- Êtes-vous sûr de vouloir accorder cette modification de l'actionnariat ? + Êtes-vous sûr de vouloir accorder ce changement d'actionnaire(s) ?

@@ -64,7 +64,9 @@ export const AccorderChangementActionnaire = ({ diff --git "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/accorderChangementActionnaire.action.ts" "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/accorderChangementActionnaire.action.ts" index 2c4a2c3c76..227a2abd9c 100644 --- "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/accorderChangementActionnaire.action.ts" +++ "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/accorder/accorderChangementActionnaire.action.ts" @@ -37,6 +37,7 @@ const action: FormAction = async ( status: 'success', redirection: { url: Routes.Projet.details(identifiantProjet), + message: "Le changement d'actionnaire(s) a été pris en compte", }, }; }); diff --git "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/AnnulerChangementActionnaire.form.tsx" "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/AnnulerChangementActionnaire.form.tsx" index 5554d81312..1ca09233f4 100644 --- "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/AnnulerChangementActionnaire.form.tsx" +++ "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/AnnulerChangementActionnaire.form.tsx" @@ -19,12 +19,12 @@ export const AnnulerChangementActionnaire = ({ return ( <>

- Êtes-vous sûr de vouloir annuler cette modification de l’actionnariat ? + Êtes-vous sûr de vouloir annuler ce changement d'actionnaire(s) ?

diff --git "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/annulerChangementActionnaire.action.ts" "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/annulerChangementActionnaire.action.ts" index d655d37d45..bdc75a740e 100644 --- "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/annulerChangementActionnaire.action.ts" +++ "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/annuler/annulerChangementActionnaire.action.ts" @@ -28,7 +28,7 @@ const action: FormAction = async (_, { identifiantProj status: 'success', redirection: { url: Routes.Projet.details(identifiantProjet), - message: "La demande de modification de l'actionnariat a bien été annulée", + message: "La demande de changement d'actionnaire(s) a bien été annulée", }, }; }); diff --git "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/rejeter/RejeterChangementActionnaire.form.tsx" "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/rejeter/RejeterChangementActionnaire.form.tsx" index 155d00996a..b2375ad4c1 100644 --- "a/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/rejeter/RejeterChangementActionnaire.form.tsx" +++ "b/packages/applications/ssr/src/components/pages/actionnaire/changement/d\303\251tails/rejeter/RejeterChangementActionnaire.form.tsx" @@ -3,9 +3,12 @@ import { useState } from 'react'; import Button from '@codegouvfr/react-dsfr/Button'; +import { Routes } from '@potentiel-applications/routes'; + import { ModalWithForm } from '@/components/molecules/ModalWithForm'; import { ValidationErrors } from '@/utils/formAction'; import { UploadNewOrModifyExistingDocument } from '@/components/atoms/form/document/UploadNewOrModifyExistingDocument'; +import { DownloadDocument } from '@/components/atoms/form/document/DownloadDocument'; import { rejeterChangementActionnaireAction, @@ -27,12 +30,12 @@ export const RejeterChangementActionnaire = ({ return ( <>

- Êtes-vous sûr de vouloir rejeter cette modification de l’actionnariat ? + Êtes-vous sûr de vouloir rejeter ce changement d'actionnaire(s) ?

@@ -58,6 +61,14 @@ export const RejeterChangementActionnaire = ({ className="mb-4" formats={['pdf']} /> + ), }} diff --git a/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/DemanderChangementActionnaire.form.tsx b/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/DemanderChangementActionnaire.form.tsx index 9275a350cc..d31b18c536 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/DemanderChangementActionnaire.form.tsx +++ b/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/DemanderChangementActionnaire.form.tsx @@ -50,7 +50,7 @@ export const DemanderChangementActionnaireForm: FC 0 } > - Demander la modification + Confirmer la demande } @@ -66,7 +66,7 @@ export const DemanderChangementActionnaireForm: FC ( small description={
- Votre demande de modification de l'actionnariat nécessite une instruction si votre projet + Votre demande de changement d'actionnaire(s) nécessite une instruction si votre projet remplit une des conditions suivantes :
  • l'actionnariat est de type financement ou investissement participatif
  • diff --git a/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/demanderChangementActionnaire.action.ts b/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/demanderChangementActionnaire.action.ts index b265105377..d2d946460e 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/demanderChangementActionnaire.action.ts +++ b/packages/applications/ssr/src/components/pages/actionnaire/demanderChangement/demanderChangementActionnaire.action.ts @@ -44,7 +44,7 @@ const action: FormAction = async ( status: 'success', redirection: { url: Routes.Actionnaire.changement.détails(identifiantProjet, dateDemandeValue), - message: "La demande de modification de l'actionnariat a bien été enregistrée", + message: "La demande de changement d'actionnaire(s) a bien été enregistrée", }, }; }); diff --git a/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.form.tsx b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.form.tsx new file mode 100644 index 0000000000..669c199cc4 --- /dev/null +++ b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.form.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { FC, useState } from 'react'; +import Button from '@codegouvfr/react-dsfr/Button'; +import Input from '@codegouvfr/react-dsfr/Input'; + +import { Routes } from '@potentiel-applications/routes'; +import { IdentifiantProjet } from '@potentiel-domain/common'; +import { Actionnaire } from '@potentiel-domain/laureat'; +import { PlainType } from '@potentiel-domain/core'; + +import { Form } from '@/components/atoms/form/Form'; +import { SubmitButton } from '@/components/atoms/form/SubmitButton'; +import { UploadNewOrModifyExistingDocument } from '@/components/atoms/form/document/UploadNewOrModifyExistingDocument'; +import { ValidationErrors } from '@/utils/formAction'; + +import { + enregistrerChangementActionnaireAction, + ModifierActionnaireFormKeys, +} from './enregistrerChangementActionnaire.action'; + +export type EnregistrerChangementActionnaireFormProps = + PlainType; + +export const EnregistrerChangementActionnaireForm: FC< + EnregistrerChangementActionnaireFormProps +> = ({ identifiantProjet, actionnaire }) => { + const [validationErrors, setValidationErrors] = useState< + ValidationErrors + >({}); + const [piècesJustificatives, setPiècesJustificatives] = useState>([]); + + return ( +
    setValidationErrors(validationErrors)} + actions={ + <> + + + !piècesJustificatives.length || Object.keys(validationErrors).length > 0 + } + > + Modifier + + + } + > + + +
    + + + { + delete validationErrors['piecesJustificatives']; + setPiècesJustificatives(piècesJustificatives); + }} + /> +
    +
    + ); +}; diff --git a/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.page.tsx b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.page.tsx new file mode 100644 index 0000000000..259f14508b --- /dev/null +++ b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.page.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; + +import { IdentifiantProjet } from '@potentiel-domain/common'; + +import { ProjetBanner } from '@/components/molecules/projet/ProjetBanner'; +import { Heading1 } from '@/components/atoms/headings'; +import { PageTemplate } from '@/components/templates/Page.template'; + +import { + EnregistrerChangementActionnaireForm, + EnregistrerChangementActionnaireFormProps, +} from './EnregistrerChangementActionnaire.form'; + +export type EnregistrerChangementActionnairePageProps = EnregistrerChangementActionnaireFormProps; + +export const EnregistrerChangementActionnairePage: FC< + EnregistrerChangementActionnairePageProps +> = ({ identifiantProjet, actionnaire }) => ( + + } + > + Changer d'actionnaire(s) + + +); diff --git a/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.stories.tsx b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.stories.tsx new file mode 100644 index 0000000000..0b481e62c2 --- /dev/null +++ b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/EnregistrerChangementActionnaire.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { IdentifiantProjet } from '@potentiel-domain/common'; + +import { + EnregistrerChangementActionnairePage, + EnregistrerChangementActionnairePageProps, +} from './EnregistrerChangementActionnaire.page'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Pages/Actionnaire/Modifier', + component: EnregistrerChangementActionnairePage, + parameters: {}, + tags: ['autodocs'], + argTypes: {}, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + identifiantProjet: IdentifiantProjet.convertirEnValueType('PPE2 - Bâtiment#4#1#id-cre-738'), + actionnaire: 'CAC40 boy', + }, +}; + +export const Porteur: Story = { + args: { + identifiantProjet: IdentifiantProjet.convertirEnValueType('PPE2 - Bâtiment#4#1#id-cre-738'), + actionnaire: 'CAC40 boy', + }, +}; diff --git a/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/enregistrerChangementActionnaire.action.ts b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/enregistrerChangementActionnaire.action.ts new file mode 100644 index 0000000000..c2068f65d7 --- /dev/null +++ b/packages/applications/ssr/src/components/pages/actionnaire/enregistrerChangement/enregistrerChangementActionnaire.action.ts @@ -0,0 +1,48 @@ +'use server'; + +import { mediator } from 'mediateur'; +import * as zod from 'zod'; + +import { Actionnaire } from '@potentiel-domain/laureat'; +import { Routes } from '@potentiel-applications/routes'; + +import { FormAction, formAction, FormState } from '@/utils/formAction'; +import { withUtilisateur } from '@/utils/withUtilisateur'; +import { manyDocuments } from '@/utils/zod/document/manyDocuments'; + +const schema = zod.object({ + identifiantProjet: zod.string().min(1), + actionnaire: zod.string().min(1, { message: 'Champ obligatoire' }), + raison: zod.string().min(1, { message: 'Champ obligatoire' }), + piecesJustificatives: manyDocuments({ acceptedFileTypes: ['application/pdf'] }), +}); + +export type ModifierActionnaireFormKeys = keyof zod.infer; + +const action: FormAction = async ( + _, + { identifiantProjet, actionnaire, raison, piecesJustificatives }, +) => + withUtilisateur(async (utilisateur) => { + await mediator.send({ + type: 'Lauréat.Actionnaire.UseCase.EnregistrerChangement', + data: { + identifiantProjetValue: identifiantProjet, + identifiantUtilisateurValue: utilisateur.identifiantUtilisateur.formatter(), + raisonValue: raison, + actionnaireValue: actionnaire, + dateChangementValue: new Date().toISOString(), + pièceJustificativeValue: piecesJustificatives, + }, + }); + + return { + status: 'success', + redirection: { + url: Routes.Projet.details(identifiantProjet), + message: "Le changement d'actionnaire(s) a été pris en compte", + }, + }; + }); + +export const enregistrerChangementActionnaireAction = formAction(action, schema); diff --git a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.form.tsx b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.form.tsx index 4fd06532db..0b93a3aff2 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.form.tsx +++ b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.form.tsx @@ -6,6 +6,8 @@ import Input from '@codegouvfr/react-dsfr/Input'; import { Routes } from '@potentiel-applications/routes'; import { IdentifiantProjet } from '@potentiel-domain/common'; +import { Actionnaire } from '@potentiel-domain/laureat'; +import { PlainType } from '@potentiel-domain/core'; import { Form } from '@/components/atoms/form/Form'; import { SubmitButton } from '@/components/atoms/form/SubmitButton'; @@ -16,19 +18,16 @@ import { modifierActionnaireAction, ModifierActionnaireFormKeys, } from './modifierActionnaire.action'; -import { ModifierActionnairePageProps } from './ModifierActionnaire.page'; -export type ModifierActionnaireFormProps = ModifierActionnairePageProps; +export type ModifierActionnaireFormProps = PlainType; export const ModifierActionnaireForm: FC = ({ identifiantProjet, actionnaire, - hasToUploadDocument, }) => { const [validationErrors, setValidationErrors] = useState< ValidationErrors >({}); - const [piècesJustificatives, setPiècesJustificatives] = useState>([]); return (
    = ({ > Retour à la page projet - - (hasToUploadDocument && !piècesJustificatives.length) || - Object.keys(validationErrors).length > 0 - } - > - Modifier - + Modifier } > @@ -77,26 +69,21 @@ export const ModifierActionnaireForm: FC = ({ /> { - delete validationErrors['piecesJustificatives']; - setPiècesJustificatives(piècesJustificatives); - }} />
diff --git a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.page.tsx b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.page.tsx index 2f0fea15ac..66020c395a 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.page.tsx +++ b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.page.tsx @@ -1,23 +1,18 @@ import { FC } from 'react'; -import { Actionnaire } from '@potentiel-domain/laureat'; -import { PlainType } from '@potentiel-domain/core'; import { IdentifiantProjet } from '@potentiel-domain/common'; import { ProjetBanner } from '@/components/molecules/projet/ProjetBanner'; import { Heading1 } from '@/components/atoms/headings'; import { PageTemplate } from '@/components/templates/Page.template'; -import { ModifierActionnaireForm } from './ModifierActionnaire.form'; +import { ModifierActionnaireForm, ModifierActionnaireFormProps } from './ModifierActionnaire.form'; -export type ModifierActionnairePageProps = PlainType & { - hasToUploadDocument: boolean; -}; +export type ModifierActionnairePageProps = ModifierActionnaireFormProps; export const ModifierActionnairePage: FC = ({ identifiantProjet, actionnaire, - hasToUploadDocument, }) => ( = ({ } > Changer d'actionnaire(s) - + ); diff --git a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.stories.tsx b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.stories.tsx index a5bf0f15ec..034902dc2c 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.stories.tsx +++ b/packages/applications/ssr/src/components/pages/actionnaire/modifier/ModifierActionnaire.stories.tsx @@ -20,7 +20,6 @@ export const Default: Story = { args: { identifiantProjet: IdentifiantProjet.convertirEnValueType('PPE2 - Bâtiment#4#1#id-cre-738'), actionnaire: 'CAC40 boy', - hasToUploadDocument: false, }, }; @@ -28,6 +27,5 @@ export const Porteur: Story = { args: { identifiantProjet: IdentifiantProjet.convertirEnValueType('PPE2 - Bâtiment#4#1#id-cre-738'), actionnaire: 'CAC40 boy', - hasToUploadDocument: true, }, }; diff --git a/packages/applications/ssr/src/components/pages/actionnaire/modifier/modifierActionnaire.action.ts b/packages/applications/ssr/src/components/pages/actionnaire/modifier/modifierActionnaire.action.ts index 979d81ec97..072047a6ef 100644 --- a/packages/applications/ssr/src/components/pages/actionnaire/modifier/modifierActionnaire.action.ts +++ b/packages/applications/ssr/src/components/pages/actionnaire/modifier/modifierActionnaire.action.ts @@ -13,7 +13,7 @@ import { manyDocuments } from '@/utils/zod/document/manyDocuments'; const schema = zod.object({ identifiantProjet: zod.string().min(1), actionnaire: zod.string().min(1, { message: 'Champ obligatoire' }), - raison: zod.string().min(1, { message: 'Champ obligatoire' }), + raison: zod.string().optional(), piecesJustificatives: manyDocuments({ optional: true, acceptedFileTypes: ['application/pdf'] }), }); @@ -30,12 +30,11 @@ const action: FormAction = async ( identifiantProjetValue: identifiantProjet, identifiantUtilisateurValue: utilisateur.identifiantUtilisateur.formatter(), dateModificationValue: new Date().toISOString(), - raisonValue: raison, + raisonValue: raison ?? '', actionnaireValue: actionnaire, ...(piecesJustificatives && { pièceJustificativeValue: piecesJustificatives, }), - rôleValue: utilisateur.role.nom, }, }); diff --git "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/D\303\251tailsChangementRepr\303\251sentantL\303\251gal.page.tsx" "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/D\303\251tailsChangementRepr\303\251sentantL\303\251gal.page.tsx" index af95365d12..7d23602166 100644 --- "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/D\303\251tailsChangementRepr\303\251sentantL\303\251gal.page.tsx" +++ "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/D\303\251tailsChangementRepr\303\251sentantL\303\251gal.page.tsx" @@ -139,16 +139,12 @@ const mapToActionComponents = ({ {actions.includes('accorder') && ( )} {actions.includes('rejeter') && ( - + )} {actions.includes('annuler') && ( diff --git "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/accorder/AccorderChangementRepr\303\251sentantL\303\251gal.form.tsx" "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/accorder/AccorderChangementRepr\303\251sentantL\303\251gal.form.tsx" index b4f6da0df3..4d8b58536a 100644 --- "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/accorder/AccorderChangementRepr\303\251sentantL\303\251gal.form.tsx" +++ "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/accorder/AccorderChangementRepr\303\251sentantL\303\251gal.form.tsx" @@ -16,14 +16,12 @@ import { type AccorderChangementReprésentantLégalFormProps = { identifiantProjet: string; - dateDemande: string; typeReprésentantLégal: ReprésentantLégal.TypeReprésentantLégal.RawType; nomReprésentantLégal: string; }; export const AccorderChangementReprésentantLégal = ({ identifiantProjet, - dateDemande, typeReprésentantLégal, nomReprésentantLégal, }: AccorderChangementReprésentantLégalFormProps) => { @@ -55,7 +53,6 @@ export const AccorderChangementReprésentantLégal = ({ onValidationError: (validationErrors) => setValidationErrors(validationErrors), children: ( <> - = async ( _, - { identifiantProjet, nomRepresentantLegal, typeRepresentantLegal, dateDemande }, + { identifiantProjet, nomRepresentantLegal, typeRepresentantLegal }, ) => withUtilisateur(async (utilisateur) => { await mediator.send({ @@ -42,7 +42,8 @@ const action: FormAction = async ( return { status: 'success', redirection: { - url: Routes.ReprésentantLégal.changement.détail(identifiantProjet, dateDemande), + url: Routes.Projet.details(identifiantProjet), + message: 'Le changement de représentant légal a bien été pris en compte', }, }; }); diff --git "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/RejeterChangementRepr\303\251sentantL\303\251gal.form.tsx" "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/RejeterChangementRepr\303\251sentantL\303\251gal.form.tsx" index 6aae2df5c8..82c736aef8 100644 --- "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/RejeterChangementRepr\303\251sentantL\303\251gal.form.tsx" +++ "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/RejeterChangementRepr\303\251sentantL\303\251gal.form.tsx" @@ -14,12 +14,10 @@ import { type RejeterChangementReprésentantLégalFormProps = { identifiantProjet: string; - dateDemande: string; }; export const RejeterChangementReprésentantLégal = ({ identifiantProjet, - dateDemande, }: RejeterChangementReprésentantLégalFormProps) => { const [isOpen, setIsOpen] = useState(false); @@ -67,7 +65,6 @@ export const RejeterChangementReprésentantLégal = ({

Êtes-vous sûr de vouloir rejeter ce changement de représentant légal ?

- ), }} diff --git "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/rejeterChangementRepr\303\251sentantL\303\251gal.action.ts" "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/rejeterChangementRepr\303\251sentantL\303\251gal.action.ts" index 397850152d..ff78048a3b 100644 --- "a/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/rejeterChangementRepr\303\251sentantL\303\251gal.action.ts" +++ "b/packages/applications/ssr/src/components/pages/repr\303\251sentant-l\303\251gal/changement/d\303\251tails/rejeter/rejeterChangementRepr\303\251sentantL\303\251gal.action.ts" @@ -11,7 +11,6 @@ import { withUtilisateur } from '@/utils/withUtilisateur'; const schema = zod.object({ identifiantProjet: zod.string().min(1), - dateDemande: zod.string().min(1), motifRejet: zod.string().min(1), }); @@ -19,7 +18,7 @@ export type RejeterChangementReprésentantLégalFormKeys = keyof zod.infer = async ( _, - { identifiantProjet, motifRejet, dateDemande }, + { identifiantProjet, motifRejet }, ) => { return withUtilisateur(async (utilisateur) => { await mediator.send({ @@ -36,7 +35,8 @@ const action: FormAction = async ( return { status: 'success', redirection: { - url: Routes.ReprésentantLégal.changement.détail(identifiantProjet, dateDemande), + url: Routes.Projet.details(identifiantProjet), + message: 'Le changement de représentant légal a bien été rejeté', }, }; }); diff --git "a/packages/domain/laur\303\251at/src/actionnaire/actionnaire.aggregate.ts" "b/packages/domain/laur\303\251at/src/actionnaire/actionnaire.aggregate.ts" index 8d68f6caa0..d60c35c3d7 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/actionnaire.aggregate.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/actionnaire.aggregate.ts" @@ -45,10 +45,16 @@ import { ChangementActionnaireSuppriméEvent, supprimer, } from './changement/supprimer/supprimerChangementActionnaire.behavior'; +import { + applyChangementActionnaireEnregistré, + ChangementActionnaireEnregistréEvent, + enregistrerChangement, +} from './changement/enregistrerChangement/enregistrerChangement.behavior'; export type ActionnaireEvent = | ActionnaireImportéEvent | ActionnaireModifiéEvent + | ChangementActionnaireEnregistréEvent | ChangementActionnaireDemandéEvent | ChangementActionnaireAnnuléEvent | ChangementActionnaireAccordéEvent @@ -64,6 +70,7 @@ export type ActionnaireAggregate = Aggregate & { }; importer: typeof importer; modifier: typeof modifier; + enregistrerChangement: typeof enregistrerChangement; demanderChangement: typeof demanderChangement; annulerDemandeChangement: typeof annulerDemandeChangement; accorderChangementActionnaire: typeof accorderChangementActionnaire; @@ -80,6 +87,7 @@ export const getDefaultActionnaireAggregate: GetDefaultAggregateState< apply, importer, modifier, + enregistrerChangement, demanderChangement, annulerDemandeChangement, accorderChangementActionnaire, @@ -97,6 +105,10 @@ function apply(this: ActionnaireAggregate, event: ActionnaireEvent) { applyActionnaireModifié.bind(this)(event); break; + case 'ChangementActionnaireEnregistré-V1': + applyChangementActionnaireEnregistré.bind(this)(event); + break; + case 'ChangementActionnaireDemandé-V1': applyChangementActionnaireDemandé.bind(this)(event); break; diff --git "a/packages/domain/laur\303\251at/src/actionnaire/actionnaire.register.ts" "b/packages/domain/laur\303\251at/src/actionnaire/actionnaire.register.ts" index 798f227566..0c545fdd14 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/actionnaire.register.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/actionnaire.register.ts" @@ -22,6 +22,8 @@ import { registerSupprimerChangementActionnaireCommand } from './changement/supp import { registerModifierActionnaireCommand } from './modifier/modifierActionnaire.command'; import { registerModifierActionnaireUseCase } from './modifier/modifierActionnaire.usecase'; import { registerConsulterDateChangementActionnaireQuery } from './changement/consulter/consulterDateChangementActionnaire.query'; +import { registerEnregistrerChangementActionnaireUseCase } from './changement/enregistrerChangement/enregistrerChangement.usecase'; +import { registerEnregistrerChangementActionnaireCommand } from './changement/enregistrerChangement/enregistrerChangement.command'; export type ActionnaireQueryDependencies = ConsulterActionnaireDependencies & ListerChangementActionnaireDependencies; @@ -33,6 +35,7 @@ export type ActionnaireCommandDependencies = { export const registerActionnaireUseCases = ({ loadAggregate }: ActionnaireCommandDependencies) => { registerImporterActionnaireCommand(loadAggregate); registerModifierActionnaireCommand(loadAggregate); + registerEnregistrerChangementActionnaireCommand(loadAggregate); registerDemanderChangementActionnaireCommand(loadAggregate); registerAnnulerDemandeChangementCommand(loadAggregate); registerAccorderChangementActionnaireCommand(loadAggregate); @@ -40,6 +43,7 @@ export const registerActionnaireUseCases = ({ loadAggregate }: ActionnaireComman registerSupprimerChangementActionnaireCommand(loadAggregate); registerModifierActionnaireUseCase(); + registerEnregistrerChangementActionnaireUseCase(); registerDemanderChangementActionnaireUseCase(); registerAnnulerChangementActionnaireUseCase(); registerAccorderChangementActionnaireUseCase(); diff --git "a/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.behavior.ts" "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.behavior.ts" new file mode 100644 index 0000000000..a62ac255ca --- /dev/null +++ "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.behavior.ts" @@ -0,0 +1,109 @@ +import { DomainEvent } from '@potentiel-domain/core'; +import { DateTime, Email, IdentifiantProjet } from '@potentiel-domain/common'; +import { DocumentProjet } from '@potentiel-domain/document'; + +import { ActionnaireAggregate } from '../../actionnaire.aggregate'; +import { + ActionnaireNePeutPasÊtreModifiéDirectement, + DemandeDeChangementEnCoursError, + ProjetAbandonnéError, + ProjetAvecDemandeAbandonEnCoursError, + ProjetAchevéError, +} from '../../errors'; + +export type ChangementActionnaireEnregistréEvent = DomainEvent< + 'ChangementActionnaireEnregistré-V1', + { + identifiantProjet: IdentifiantProjet.RawType; + actionnaire: string; + enregistréLe: DateTime.RawType; + enregistréPar: Email.RawType; + raison: string; + pièceJustificative: { + format: string; + }; + } +>; + +export type EnregistrerChangementOptions = { + identifiantProjet: IdentifiantProjet.ValueType; + identifiantUtilisateur: Email.ValueType; + actionnaire: string; + dateChangement: DateTime.ValueType; + pièceJustificative: DocumentProjet.ValueType; + raison: string; + estAbandonné: boolean; + estAchevé: boolean; + demandeAbandonEnCours: boolean; + estParticipatif: boolean; + aDesGarantiesFinancièresConstituées: boolean; + aUnDépotEnCours: boolean; +}; + +export async function enregistrerChangement( + this: ActionnaireAggregate, + { + identifiantProjet, + actionnaire, + dateChangement, + identifiantUtilisateur, + pièceJustificative, + raison, + estAbandonné, + estAchevé, + demandeAbandonEnCours, + estParticipatif, + aUnDépotEnCours, + aDesGarantiesFinancièresConstituées, + }: EnregistrerChangementOptions, +) { + // Règle métier, spécifique à l'AO Eolien (pour lequel le type de GF est `après candidature`) pour les porteurs + // La demande doit être en "instruction" si il n'y a pas de GF validées sur le projet ou si il y a une demande de renouvellement ou de modifications des garanties financières en cours + // La demande doit être en "instruction" si le candidat a joint à son offre la lettre d’engagement (l'investissement participatif ou financement participatif) + const devraitPasserParUneDemande = + identifiantProjet.appelOffre === 'Eolien' && + (!aDesGarantiesFinancièresConstituées || aUnDépotEnCours || estParticipatif); + + if (devraitPasserParUneDemande) { + throw new ActionnaireNePeutPasÊtreModifiéDirectement(); + } + + if (this.demande?.statut.estDemandé()) { + throw new DemandeDeChangementEnCoursError(); + } + + if (estAbandonné) { + throw new ProjetAbandonnéError(); + } + + if (demandeAbandonEnCours) { + throw new ProjetAvecDemandeAbandonEnCoursError(); + } + + if (estAchevé) { + throw new ProjetAchevéError(); + } + + const event: ChangementActionnaireEnregistréEvent = { + type: 'ChangementActionnaireEnregistré-V1', + payload: { + identifiantProjet: identifiantProjet.formatter(), + actionnaire, + enregistréLe: dateChangement.formatter(), + enregistréPar: identifiantUtilisateur.formatter(), + raison, + pièceJustificative: { + format: pièceJustificative.format, + }, + }, + }; + + await this.publish(event); +} + +export function applyChangementActionnaireEnregistré( + this: ActionnaireAggregate, + { payload: { actionnaire } }: ChangementActionnaireEnregistréEvent, +) { + this.actionnaire = actionnaire; +} diff --git "a/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.command.ts" "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.command.ts" new file mode 100644 index 0000000000..d84bb60d35 --- /dev/null +++ "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.command.ts" @@ -0,0 +1,70 @@ +import { Message, MessageHandler, mediator } from 'mediateur'; + +import { IdentifiantProjet, DateTime, Email } from '@potentiel-domain/common'; +import { LoadAggregate } from '@potentiel-domain/core'; +import { DocumentProjet } from '@potentiel-domain/document'; +import { Candidature } from '@potentiel-domain/candidature'; + +import { loadAbandonFactory } from '../../../abandon'; +import { loadAchèvementFactory } from '../../../achèvement/achèvement.aggregate'; +import { loadGarantiesFinancièresFactory } from '../../../garantiesFinancières/garantiesFinancières.aggregate'; +import { loadActionnaireFactory } from '../../actionnaire.aggregate'; + +export type EnregistrerChangementActionnaireCommand = Message< + 'Lauréat.Actionnaire.Command.EnregistrerChangement', + { + identifiantProjet: IdentifiantProjet.ValueType; + identifiantUtilisateur: Email.ValueType; + actionnaire: string; + dateChangement: DateTime.ValueType; + pièceJustificative: DocumentProjet.ValueType; + raison: string; + } +>; + +export const registerEnregistrerChangementActionnaireCommand = (loadAggregate: LoadAggregate) => { + const loadActionnaire = loadActionnaireFactory(loadAggregate); + const loadAbandon = loadAbandonFactory(loadAggregate); + const loadAchèvement = loadAchèvementFactory(loadAggregate); + const loadGarantiesFinancières = loadGarantiesFinancièresFactory(loadAggregate); + const loadCandidature = Candidature.Aggregate.loadCandidatureFactory(loadAggregate); + + const handler: MessageHandler = async ({ + identifiantProjet, + identifiantUtilisateur, + actionnaire, + dateChangement, + pièceJustificative, + raison, + }) => { + const actionnaireAggrégat = await loadActionnaire(identifiantProjet); + const abandon = await loadAbandon(identifiantProjet, false); + const achèvement = await loadAchèvement(identifiantProjet, false); + const garantiesFinancières = await loadGarantiesFinancières(identifiantProjet, false); + + // quickwin : nous passons ici par un appel à l'agrégat candidature au lieu de projet + // cela devrait être repris quand les types d'actionnariat seront migrés dans l'aggregat Actionnaire + // Par ailleurs les données sont les mêmes à date (janv 2025) + const candidature = await loadCandidature(identifiantProjet); + + const estParticipatif = + candidature.typeActionnariat?.type === 'financement-participatif' || + candidature.typeActionnariat?.type === 'investissement-participatif'; + + await actionnaireAggrégat.enregistrerChangement({ + identifiantProjet, + identifiantUtilisateur, + actionnaire, + dateChangement, + pièceJustificative, + raison, + estAbandonné: abandon.statut.estAccordé(), + estAchevé: achèvement.estAchevé(), + demandeAbandonEnCours: abandon.statut.estEnCours(), + estParticipatif, + aDesGarantiesFinancièresConstituées: !!garantiesFinancières?.actuelles, + aUnDépotEnCours: !!garantiesFinancières?.dépôtsEnCours, + }); + }; + mediator.register('Lauréat.Actionnaire.Command.EnregistrerChangement', handler); +}; diff --git "a/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.usecase.ts" "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.usecase.ts" new file mode 100644 index 0000000000..e18400f38c --- /dev/null +++ "b/packages/domain/laur\303\251at/src/actionnaire/changement/enregistrerChangement/enregistrerChangement.usecase.ts" @@ -0,0 +1,62 @@ +import { Message, MessageHandler, mediator } from 'mediateur'; + +import { DateTime, Email, IdentifiantProjet } from '@potentiel-domain/common'; +import { DocumentProjet, EnregistrerDocumentProjetCommand } from '@potentiel-domain/document'; + +import { TypeDocumentActionnaire } from '../..'; + +import { EnregistrerChangementActionnaireCommand } from './enregistrerChangement.command'; + +export type EnregistrerChangementActionnaireUseCase = Message< + 'Lauréat.Actionnaire.UseCase.EnregistrerChangement', + { + identifiantProjetValue: string; + identifiantUtilisateurValue: string; + actionnaireValue: string; + dateChangementValue: string; + raisonValue: string; + pièceJustificativeValue: { + content: ReadableStream; + format: string; + }; + } +>; + +export const registerEnregistrerChangementActionnaireUseCase = () => { + const runner: MessageHandler = async ({ + identifiantProjetValue, + identifiantUtilisateurValue, + actionnaireValue, + dateChangementValue, + pièceJustificativeValue, + raisonValue, + }) => { + const pièceJustificative = DocumentProjet.convertirEnValueType( + identifiantProjetValue, + TypeDocumentActionnaire.pièceJustificative.formatter(), + dateChangementValue, + pièceJustificativeValue.format, + ); + + await mediator.send({ + type: 'Document.Command.EnregistrerDocumentProjet', + data: { + content: pièceJustificativeValue!.content, + documentProjet: pièceJustificative, + }, + }); + + await mediator.send({ + type: 'Lauréat.Actionnaire.Command.EnregistrerChangement', + data: { + identifiantProjet: IdentifiantProjet.convertirEnValueType(identifiantProjetValue), + identifiantUtilisateur: Email.convertirEnValueType(identifiantUtilisateurValue), + actionnaire: actionnaireValue, + dateChangement: DateTime.convertirEnValueType(dateChangementValue), + pièceJustificative, + raison: raisonValue, + }, + }); + }; + mediator.register('Lauréat.Actionnaire.UseCase.EnregistrerChangement', runner); +}; diff --git "a/packages/domain/laur\303\251at/src/actionnaire/errors.ts" "b/packages/domain/laur\303\251at/src/actionnaire/errors.ts" index 862de1311b..065d684ea3 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/errors.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/errors.ts" @@ -1,11 +1,5 @@ import { DomainError, InvalidOperationError } from '@potentiel-domain/core'; -export class ActionnaireIdentiqueError extends InvalidOperationError { - constructor() { - super('Le nouvel actionnaire est identique à celui associé au projet'); - } -} - export class ActionnaireDéjàTransmisError extends InvalidOperationError { constructor() { super("L'actionnaire a déjà été transmis"); diff --git "a/packages/domain/laur\303\251at/src/actionnaire/index.ts" "b/packages/domain/laur\303\251at/src/actionnaire/index.ts" index 5a0d4a3a7f..5dfd8f9faa 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/index.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/index.ts" @@ -25,6 +25,8 @@ import { ConsulterDateChangementActionnaireQuery, ConsulterDateChangementActionnaireReadModel, } from './changement/consulter/consulterDateChangementActionnaire.query'; +import { EnregistrerChangementActionnaireUseCase } from './changement/enregistrerChangement/enregistrerChangement.usecase'; +import { EnregistrerChangementActionnaireCommand } from './changement/enregistrerChangement/enregistrerChangement.command'; // Query export type ActionnaireQuery = @@ -53,13 +55,15 @@ export type ActionnaireUseCase = | DemanderChangementUseCase | AnnulerChangementActionnaireUseCase | AccorderChangementActionnaireUseCase - | RejeterChangementActionnaireUseCase; + | RejeterChangementActionnaireUseCase + | EnregistrerChangementActionnaireUseCase; export type { ModifierActionnaireUseCase, DemanderChangementUseCase, AnnulerChangementActionnaireUseCase, AccorderChangementActionnaireUseCase, RejeterChangementActionnaireUseCase, + EnregistrerChangementActionnaireUseCase, }; // Command @@ -69,7 +73,8 @@ export type ActionnaireCommand = | AnnulerChangementActionnaireCommand | AccorderChangementActionnaireCommand | RejeterChangementActionnaireCommand - | SupprimerChangementActionnaireCommand; + | SupprimerChangementActionnaireCommand + | EnregistrerChangementActionnaireCommand; export type { ImporterActionnaireCommand, ModifierActionnaireCommand, @@ -77,6 +82,7 @@ export type { AccorderChangementActionnaireCommand, RejeterChangementActionnaireCommand, SupprimerChangementActionnaireCommand, + EnregistrerChangementActionnaireCommand, }; // Event @@ -88,6 +94,7 @@ export type { ChangementActionnaireAnnuléEvent } from './changement/annuler/ann export type { ChangementActionnaireAccordéEvent } from './changement/accorder/accorderChangementActionnaire.behavior'; export type { ChangementActionnaireRejetéEvent } from './changement/rejeter/rejeterChangementActionnaire.behavior'; export type { ChangementActionnaireSuppriméEvent } from './changement/supprimer/supprimerChangementActionnaire.behavior'; +export type { ChangementActionnaireEnregistréEvent } from './changement/enregistrerChangement/enregistrerChangement.behavior'; // Saga export * as ActionnaireSaga from './saga'; diff --git "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.behavior.ts" "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.behavior.ts" index 6d8bb39483..577abe65b0 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.behavior.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.behavior.ts" @@ -3,14 +3,7 @@ import { DateTime, Email, IdentifiantProjet } from '@potentiel-domain/common'; import { DocumentProjet } from '@potentiel-domain/document'; import { ActionnaireAggregate } from '../actionnaire.aggregate'; -import { - ActionnaireNePeutPasÊtreModifiéDirectement, - ActionnaireIdentiqueError, - DemandeDeChangementEnCoursError, - ProjetAbandonnéError, - ProjetAvecDemandeAbandonEnCoursError, - ProjetAchevéError, -} from '../errors'; +import { DemandeDeChangementEnCoursError } from '../errors'; export type ActionnaireModifiéEvent = DomainEvent< 'ActionnaireModifié-V1', @@ -33,13 +26,6 @@ export type ModifierOptions = { dateModification: DateTime.ValueType; pièceJustificative?: DocumentProjet.ValueType; raison: string; - estAbandonné: boolean; - estAchevé: boolean; - demandeAbandonEnCours: boolean; - utilisateurEstPorteur: boolean; - estParticipatif: boolean; - aDesGarantiesFinancièresConstituées: boolean; - aUnDépotEnCours: boolean; }; export async function modifier( @@ -51,47 +37,12 @@ export async function modifier( identifiantUtilisateur, pièceJustificative, raison, - estAbandonné, - estAchevé, - demandeAbandonEnCours, - utilisateurEstPorteur, - estParticipatif, - aUnDépotEnCours, - aDesGarantiesFinancièresConstituées, }: ModifierOptions, ) { - // Règle métier, spécifique à l'AO Eolien (pour lequel le type de GF est `après candidature`) pour les porteurs - // La demande doit être en "instruction" si il n'y a pas de GF validées sur le projet ou si il y a une demande de renouvellement ou de modifications des garanties financières en cours - // La demande doit être en "instruction" si le candidat a joint à son offre la lettre d’engagement (l'investissement participatif ou financement participatif) - const devraitPasserParUneDemande = - utilisateurEstPorteur && - identifiantProjet.appelOffre === 'Eolien' && - (!aDesGarantiesFinancièresConstituées || aUnDépotEnCours || estParticipatif); - - if (devraitPasserParUneDemande) { - throw new ActionnaireNePeutPasÊtreModifiéDirectement(); - } - - if (this.actionnaire === actionnaire) { - throw new ActionnaireIdentiqueError(); - } - if (this.demande?.statut.estDemandé()) { throw new DemandeDeChangementEnCoursError(); } - if (utilisateurEstPorteur && estAbandonné) { - throw new ProjetAbandonnéError(); - } - - if (utilisateurEstPorteur && demandeAbandonEnCours) { - throw new ProjetAvecDemandeAbandonEnCoursError(); - } - - if (utilisateurEstPorteur && estAchevé) { - throw new ProjetAchevéError(); - } - const event: ActionnaireModifiéEvent = { type: 'ActionnaireModifié-V1', payload: { diff --git "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.command.ts" "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.command.ts" index 8aaab76d20..8ad4924a14 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.command.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.command.ts" @@ -3,12 +3,7 @@ import { Message, MessageHandler, mediator } from 'mediateur'; import { IdentifiantProjet, DateTime, Email } from '@potentiel-domain/common'; import { LoadAggregate } from '@potentiel-domain/core'; import { DocumentProjet } from '@potentiel-domain/document'; -import { Role } from '@potentiel-domain/utilisateur'; -import { Candidature } from '@potentiel-domain/candidature'; -import { loadAbandonFactory } from '../../abandon'; -import { loadAchèvementFactory } from '../../achèvement/achèvement.aggregate'; -import { loadGarantiesFinancièresFactory } from '../../garantiesFinancières/garantiesFinancières.aggregate'; import { loadActionnaireFactory } from '../actionnaire.aggregate'; export type ModifierActionnaireCommand = Message< @@ -19,17 +14,12 @@ export type ModifierActionnaireCommand = Message< actionnaire: string; dateModification: DateTime.ValueType; pièceJustificative?: DocumentProjet.ValueType; - rôle: Role.ValueType; raison: string; } >; export const registerModifierActionnaireCommand = (loadAggregate: LoadAggregate) => { const loadActionnaire = loadActionnaireFactory(loadAggregate); - const loadAbandon = loadAbandonFactory(loadAggregate); - const loadAchèvement = loadAchèvementFactory(loadAggregate); - const loadGarantiesFinancières = loadGarantiesFinancièresFactory(loadAggregate); - const loadCandidature = Candidature.Aggregate.loadCandidatureFactory(loadAggregate); const handler: MessageHandler = async ({ identifiantProjet, @@ -37,23 +27,9 @@ export const registerModifierActionnaireCommand = (loadAggregate: LoadAggregate) actionnaire, dateModification, pièceJustificative, - rôle, raison, }) => { const actionnaireAggrégat = await loadActionnaire(identifiantProjet); - const abandon = await loadAbandon(identifiantProjet, false); - const achèvement = await loadAchèvement(identifiantProjet, false); - const utilisateurEstPorteur = rôle.estÉgaleÀ(Role.porteur); - const garantiesFinancières = await loadGarantiesFinancières(identifiantProjet, false); - - // quickwin : nous passons ici par un appel à l'agrégat candidature au lieu de projet - // cela devrait être repris quand les types d'actionnariat seront migrés dans l'aggregat Actionnaire - // Par ailleurs les données sont les mêmes à date (janv 2025) - const candidature = await loadCandidature(identifiantProjet); - - const estParticipatif = - candidature.typeActionnariat?.type === 'financement-participatif' || - candidature.typeActionnariat?.type === 'investissement-participatif'; await actionnaireAggrégat.modifier({ identifiantProjet, @@ -62,13 +38,6 @@ export const registerModifierActionnaireCommand = (loadAggregate: LoadAggregate) dateModification, pièceJustificative, raison, - utilisateurEstPorteur, - estAbandonné: abandon.statut.estAccordé(), - estAchevé: achèvement.estAchevé(), - demandeAbandonEnCours: abandon.statut.estEnCours(), - estParticipatif, - aDesGarantiesFinancièresConstituées: !!garantiesFinancières?.actuelles, - aUnDépotEnCours: !!garantiesFinancières?.dépôtsEnCours, }); }; mediator.register('Lauréat.Actionnaire.Command.ModifierActionnaire', handler); diff --git "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.usecase.ts" "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.usecase.ts" index bfe7be068e..f3141dd1a6 100644 --- "a/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.usecase.ts" +++ "b/packages/domain/laur\303\251at/src/actionnaire/modifier/modifierActionnaire.usecase.ts" @@ -2,7 +2,6 @@ import { Message, MessageHandler, mediator } from 'mediateur'; import { DateTime, Email, IdentifiantProjet } from '@potentiel-domain/common'; import { DocumentProjet, EnregistrerDocumentProjetCommand } from '@potentiel-domain/document'; -import { Role } from '@potentiel-domain/utilisateur'; import { TypeDocumentActionnaire } from '..'; @@ -19,7 +18,6 @@ export type ModifierActionnaireUseCase = Message< format: string; }; dateModificationValue: string; - rôleValue: string; raisonValue: string; } >; @@ -31,7 +29,6 @@ export const registerModifierActionnaireUseCase = () => { actionnaireValue, dateModificationValue, pièceJustificativeValue, - rôleValue, raisonValue, }) => { const pièceJustificative = pièceJustificativeValue @@ -53,8 +50,6 @@ export const registerModifierActionnaireUseCase = () => { }); } - const rôle = Role.convertirEnValueType(rôleValue); - await mediator.send({ type: 'Lauréat.Actionnaire.Command.ModifierActionnaire', data: { @@ -63,7 +58,6 @@ export const registerModifierActionnaireUseCase = () => { actionnaire: actionnaireValue, dateModification: DateTime.convertirEnValueType(dateModificationValue), pièceJustificative, - rôle, raison: raisonValue, }, }); diff --git a/packages/domain/utilisateur/src/role.valueType.ts b/packages/domain/utilisateur/src/role.valueType.ts index e41498192f..0910e68228 100644 --- a/packages/domain/utilisateur/src/role.valueType.ts +++ b/packages/domain/utilisateur/src/role.valueType.ts @@ -274,6 +274,7 @@ const référencielPermissions = { }, usecase: { modifier: 'Lauréat.Actionnaire.UseCase.ModifierActionnaire', + enregistrerChangement: 'Lauréat.Actionnaire.UseCase.EnregistrerChangement', demanderChangement: 'Lauréat.Actionnaire.UseCase.DemanderChangement', accorderChangement: 'Lauréat.Actionnaire.UseCase.AccorderChangement', rejeterChangement: 'Lauréat.Actionnaire.UseCase.RejeterDemandeChangement', @@ -281,6 +282,7 @@ const référencielPermissions = { }, command: { modifier: 'Lauréat.Actionnaire.Command.ModifierActionnaire', + enregistrerChangement: 'Lauréat.Actionnaire.Command.EnregistrerChangement', demanderChangement: 'Lauréat.Actionnaire.Command.DemanderChangement', accorderChangement: 'Lauréat.Actionnaire.Command.AccorderChangement', rejeterChangement: 'Lauréat.Actionnaire.Command.RejeterDemandeChangement', @@ -914,7 +916,6 @@ const policies = { référencielPermissions.candidature.query.consulterProjet, référencielPermissions.lauréat.actionnaire.query.consulter, ], - modifier: [ référencielPermissions.candidature.query.consulterProjet, référencielPermissions.lauréat.actionnaire.usecase.modifier, @@ -924,6 +925,10 @@ const policies = { référencielPermissions.lauréat.actionnaire.query.consulterChangement, référencielPermissions.lauréat.actionnaire.query.consulterChangementEnCours, ], + enregistrerChangement: [ + référencielPermissions.lauréat.actionnaire.usecase.enregistrerChangement, + référencielPermissions.lauréat.actionnaire.command.enregistrerChangement, + ], demanderChangement: [ référencielPermissions.lauréat.actionnaire.usecase.demanderChangement, référencielPermissions.lauréat.actionnaire.command.demanderChangement, @@ -1235,9 +1240,9 @@ const porteurProjetPolicies: ReadonlyArray = [ 'représentantLégal.consulter', // Actionnaire - 'actionnaire.modifier', 'actionnaire.consulter', 'actionnaire.consulterChangement', + 'actionnaire.enregistrerChangement', 'actionnaire.demanderChangement', 'actionnaire.annulerChangement', 'actionnaire.listerChangement', diff --git "a/packages/specifications/src/projet/laur\303\251at/actionnaire/enregistrerChangementActionnaire.feature" "b/packages/specifications/src/projet/laur\303\251at/actionnaire/enregistrerChangementActionnaire.feature" new file mode 100644 index 0000000000..cbfde74253 --- /dev/null +++ "b/packages/specifications/src/projet/laur\303\251at/actionnaire/enregistrerChangementActionnaire.feature" @@ -0,0 +1,81 @@ +# language: fr +Fonctionnalité: Enregistrer un changement d'actionnaire d'un projet lauréat + + Contexte: + Etant donné le projet lauréat "Du boulodrome de Marseille" + Et le porteur "Marcel Patoulatchi" ayant accés au projet lauréat "Du boulodrome de Marseille" + Et la dreal "DREAL" associée à la région du projet + + Scénario: Enregistrer un changement d'actionnaire d'un projet lauréat + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors l'actionnaire du projet lauréat devrait être mis à jour + Et le changement enregistré de l'actionnaire devrait être consultable + Et un email a été envoyé à la dreal avec : + | sujet | Potentiel - Enregistrement d'un changement d'actionnaire pour le projet Du boulodrome de Marseille dans le département(.*) | + | nom_projet | Du boulodrome de Marseille | + | url | https://potentiel.beta.gouv.fr/projet/.*/details.html | + + Scénario: Enregistrer un changement d'actionnaire avec une valeur identique + Etant donné le projet lauréat "Du boulodrome de Marseille" + Quand le porteur enregistre un changement d'actionnaire avec la même valeur pour le projet lauréat + Alors le changement enregistré de l'actionnaire devrait être consultable + + Scénario: Impossible d'enregistrer un changement d'actionnaire si l'actionnaire est inexistant + Etant donné le projet éliminé "Du boulodrome de Lyon" + Quand le porteur enregistre un changement d'actionnaire pour le projet éliminé + Alors l'utilisateur devrait être informé que "L'actionnaire n'existe pas" + + Scénario: Impossible d'enregistrer un changement d'actionnaire alors qu'un changement d'actionnaire est en cours + Etant donné le projet lauréat "Du boulodrome de Marseille" + Et une demande de changement d'actionnaire en cours pour le projet lauréat + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors l'utilisateur devrait être informé que "Une demande de changement est déjà en cours" + + Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet lauréat abandonné + Etant donné un abandon accordé pour le projet lauréat + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire pour un projet abandonné" + + Scénario: Impossible pour le porteur de modifier l'actionnaire si une demande d'abandon est en cours + Etant donné une demande d'abandon en cours pour le projet lauréat + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire car une demande d'abandon est en cours pour le projet" + + Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet achevé + Etant donné une attestation de conformité transmise pour le projet "Du boulodrome de Marseille" + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire pour un projet achevé" + + Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet "Eolien" si l'actionnariat est 'investissement-participatif' + Etant donné le projet lauréat "Du bouchon de Lyon le retour" avec : + | appel d'offre | Eolien | + | période | 6 | + | actionnariat | investissement-participatif | + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" + + Scénario: Impossible pour le porteur d'enregistrer un changement d'actionnaire d'un projet "Eolien" si l'actionnariat est 'financement-participatif' + Etant donné le projet lauréat "Du bouchon de Lyon" avec : + | appel d'offre | Eolien | + | période | 6 | + | actionnariat | financement-participatif | + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" + + Scénario: Impossible pour le porteur d'enregistrer un changement d'actionnaire d'un projet "Eolien" si il n'y a pas de GFs en cours + Etant donné le projet lauréat "Du bouchon de Lyon" avec : + | appel d'offre | Eolien | + | période | 6 | + | actionnariat | | + | type GF | | + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" + + Scénario: Impossible pour le porteur d'enregistrer un changement d'actionnaire d'un projet "Eolien" si il y a un dépot de GFs en cours + Etant donné le projet lauréat "Du bouchon de Lyon" avec : + | appel d'offre | Eolien | + | période | 6 | + | actionnariat | | + Et un dépôt de garanties financières pour le projet "Du bouchon de Lyon" + Quand le porteur enregistre un changement d'actionnaire pour le projet lauréat + Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" diff --git "a/packages/specifications/src/projet/laur\303\251at/actionnaire/modifierActionnaire.feature" "b/packages/specifications/src/projet/laur\303\251at/actionnaire/modifierActionnaire.feature" index d3689a75ed..f1b884263d 100644 --- "a/packages/specifications/src/projet/laur\303\251at/actionnaire/modifierActionnaire.feature" +++ "b/packages/specifications/src/projet/laur\303\251at/actionnaire/modifierActionnaire.feature" @@ -6,7 +6,7 @@ Fonctionnalité: Modifier l'actionnaire d'un projet lauréat Et le porteur "Marcel Patoulatchi" ayant accés au projet lauréat "Du boulodrome de Marseille" Et la dreal "DREAL" associée à la région du projet - Scénario: Modifier l'actionnaire d'un projet lauréat + Scénario: Modifier l'actionnaire d'un projet lauréat par une dreal ou un admin Quand modifie l'actionnaire pour le projet lauréat Alors l'actionnaire du projet lauréat devrait être mis à jour Et un email a été envoyé au porteur avec : @@ -20,7 +20,6 @@ Fonctionnalité: Modifier l'actionnaire d'un projet lauréat Exemples: | l'utilisateur autorisé | - | le porteur | | le DGEC validateur | | la DREAL associée au projet | @@ -60,67 +59,18 @@ Fonctionnalité: Modifier l'actionnaire d'un projet lauréat | le DGEC validateur | | la DREAL associée au projet | + Scénario: Modifier l'actionnaire avec une valeur identique + Etant donné le projet lauréat "Du boulodrome de Marseille" + Quand le DGEC validateur modifie l'actionnaire avec la même valeur pour le projet lauréat + Alors l'actionnaire du projet lauréat devrait être mis à jour + Scénario: Impossible de modifier l'actionnaire si l'actionnaire est inexistant Etant donné le projet éliminé "Du boulodrome de Lyon" Quand le DGEC validateur modifie l'actionnaire pour le projet éliminé Alors l'utilisateur devrait être informé que "L'actionnaire n'existe pas" - Scénario: Impossible de modifier l'actionnaire avec une valeur identique - Etant donné le projet lauréat "Du boulodrome de Marseille" - Quand le DGEC validateur modifie l'actionnaire avec la même valeur pour le projet lauréat - Alors l'utilisateur devrait être informé que "Le nouvel actionnaire est identique à celui associé au projet" - Scénario: Impossible de modifier l'actionnaire d'un projet lauréat alors qu'un changement d'actionnaire est en cours Etant donné le projet lauréat "Du boulodrome de Marseille" Et une demande de changement d'actionnaire en cours pour le projet lauréat Quand le DGEC validateur modifie l'actionnaire pour le projet lauréat Alors l'utilisateur devrait être informé que "Une demande de changement est déjà en cours" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet lauréat abandonné - Etant donné un abandon accordé pour le projet lauréat - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire pour un projet abandonné" - - Scénario: Impossible pour le porteur de modifier l'actionnaire si une demande d'abandon est en cours - Etant donné une demande d'abandon en cours pour le projet lauréat - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire car une demande d'abandon est en cours pour le projet" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet achevé - Etant donné une attestation de conformité transmise pour le projet "Du boulodrome de Marseille" - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de demander le changement d'actionnaire pour un projet achevé" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet "Eolien" si l'actionnariat est 'investissement-participatif' - Etant donné le projet lauréat "Du bouchon de Lyon le retour" avec : - | appel d'offre | Eolien | - | période | 6 | - | actionnariat | investissement-participatif | - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet "Eolien" si l'actionnariat est 'financement-participatif' - Etant donné le projet lauréat "Du bouchon de Lyon" avec : - | appel d'offre | Eolien | - | période | 6 | - | actionnariat | financement-participatif | - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet "Eolien" si il n'y a pas de GFs en cours - Etant donné le projet lauréat "Du bouchon de Lyon" avec : - | appel d'offre | Eolien | - | période | 6 | - | actionnariat | | - | type GF | | - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" - - Scénario: Impossible pour le porteur de modifier l'actionnaire d'un projet "Eolien" si il y a un dépot de GFs en cours - Etant donné le projet lauréat "Du bouchon de Lyon" avec : - | appel d'offre | Eolien | - | période | 6 | - | actionnariat | | - Et un dépôt de garanties financières pour le projet "Du bouchon de Lyon" - Quand le porteur modifie l'actionnaire pour le projet lauréat - Alors le porteur devrait être informé que "Impossible de modifier directement l'actionnaire dans ces conditions" diff --git "a/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.then.ts" "b/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.then.ts" index c53559fd8a..4051d4649c 100644 --- "a/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.then.ts" +++ "b/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.then.ts" @@ -47,6 +47,17 @@ Alors( }, ); +Alors( + "le changement enregistré de l'actionnaire devrait être consultable", + async function (this: PotentielWorld) { + await vérifierChangementActionnaire.call( + this, + this.candidatureWorld.importerCandidature.identifiantProjet, + Actionnaire.StatutChangementActionnaire.informationEnregistrée, + ); + }, +); + Alors( "la nouvelle demande de changement de l'actionnaire devrait être consultable", async function (this: PotentielWorld) { diff --git "a/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.when.ts" "b/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.when.ts" index 5e2eef62c3..30a13d3d5b 100644 --- "a/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.when.ts" +++ "b/packages/specifications/src/projet/laur\303\251at/actionnaire/stepDefinitions/actionnaire.when.ts" @@ -3,7 +3,6 @@ import { mediator } from 'mediateur'; import { match } from 'ts-pattern'; import { Actionnaire } from '@potentiel-domain/laureat'; -import { Role } from '@potentiel-domain/utilisateur'; import { PotentielWorld } from '../../../../potentiel.world'; @@ -18,19 +17,18 @@ Quand("l'actionnaire est importé pour le projet", async function (this: Potenti }); Quand( - /(le DGEC validateur|la DREAL associée au projet|le porteur) modifie l'actionnaire pour le projet (lauréat|éliminé)/, + /(le DGEC validateur|la DREAL associée au projet) modifie l'actionnaire pour le projet (lauréat|éliminé)/, async function ( this: PotentielWorld, - rôle: 'le DGEC validateur' | 'la DREAL associée au projet' | 'le porteur', + rôle: 'le DGEC validateur' | 'la DREAL associée au projet', statutProjet: 'lauréat' | 'éliminé', ) { try { - const { email, role } = match(rôle) + const { email } = match(rôle) .with('le DGEC validateur', () => this.utilisateurWorld.adminFixture) .with('la DREAL associée au projet', () => this.utilisateurWorld.drealFixture) - .with('le porteur', () => this.utilisateurWorld.porteurFixture) .exhaustive(); - await modifierActionnaire.call(this, email, role, statutProjet); + await modifierActionnaire.call(this, email, statutProjet); } catch (error) { this.error = error as Error; } @@ -38,13 +36,29 @@ Quand( ); Quand( - "le DGEC validateur modifie l'actionnaire avec la même valeur pour le projet lauréat", + "le porteur enregistre un changement d'actionnaire pour le projet {lauréat-éliminé}", + async function (this: PotentielWorld, statutProjet: 'lauréat' | 'éliminé') { + try { + await enregistrerChangementActionnaire.call( + this, + this.utilisateurWorld.porteurFixture.email, + statutProjet, + ); + } catch (error) { + this.error = error as Error; + } + }, +); + +Quand( + "le porteur enregistre un changement d'actionnaire avec la même valeur pour le projet lauréat", async function (this: PotentielWorld) { try { - await modifierActionnaireSansChangement.call( + await enregistrerChangementActionnaire.call( this, - this.utilisateurWorld.adminFixture.email, - Role.dgecValidateur.nom, + this.utilisateurWorld.porteurFixture.email, + 'lauréat', + this.lauréatWorld.actionnaireWorld.importerActionnaireFixture.actionnaire, ); } catch (error) { this.error = error as Error; @@ -52,6 +66,17 @@ Quand( }, ); +Quand( + "le DGEC validateur modifie l'actionnaire avec la même valeur pour le projet lauréat", + async function (this: PotentielWorld) { + try { + await modifierActionnaireSansChangement.call(this, this.utilisateurWorld.adminFixture.email); + } catch (error) { + this.error = error as Error; + } + }, +); + Quand( "le porteur demande le changement de l'actionnaire pour le projet {lauréat-éliminé}", async function (this: PotentielWorld, statutProjet: 'lauréat' | 'éliminé') { @@ -221,7 +246,6 @@ export async function rejeterChangementActionnaire(this: PotentielWorld, utilisa async function modifierActionnaire( this: PotentielWorld, modifiéPar: string, - rôle: string, statutProjet?: 'lauréat' | 'éliminé', ) { const identifiantProjet = @@ -241,17 +265,12 @@ async function modifierActionnaire( identifiantUtilisateurValue: modifiéPar, actionnaireValue: actionnaire, dateModificationValue: dateModification, - rôleValue: rôle, raisonValue: raison, }, }); } -async function modifierActionnaireSansChangement( - this: PotentielWorld, - modifiéPar: string, - rôle: string, -) { +async function modifierActionnaireSansChangement(this: PotentielWorld, modifiéPar: string) { const identifiantProjet = this.lauréatWorld.identifiantProjet.formatter(); const { dateModification, raison } = @@ -264,7 +283,38 @@ async function modifierActionnaireSansChangement( identifiantUtilisateurValue: modifiéPar, actionnaireValue: this.lauréatWorld.actionnaireWorld.importerActionnaireFixture.actionnaire, dateModificationValue: dateModification, - rôleValue: rôle, + raisonValue: raison, + }, + }); +} + +async function enregistrerChangementActionnaire( + this: PotentielWorld, + enregistréPar: string, + statutProjet?: 'lauréat' | 'éliminé', + nouvelActionnaire?: string, +) { + const identifiantProjet = + statutProjet === 'éliminé' + ? this.eliminéWorld.identifiantProjet.formatter() + : this.lauréatWorld.identifiantProjet.formatter(); + + const { pièceJustificative, demandéLe, raison, actionnaire } = + this.lauréatWorld.actionnaireWorld.demanderChangementActionnaireFixture.créer({ + demandéPar: enregistréPar, + ...(nouvelActionnaire && { actionnaire: nouvelActionnaire }), + }); + + this.lauréatWorld.actionnaireWorld.actionnaire = actionnaire; + + await mediator.send({ + type: 'Lauréat.Actionnaire.UseCase.EnregistrerChangement', + data: { + identifiantProjetValue: identifiantProjet, + identifiantUtilisateurValue: enregistréPar, + actionnaireValue: this.lauréatWorld.actionnaireWorld.actionnaire, + dateChangementValue: demandéLe, + pièceJustificativeValue: pièceJustificative, raisonValue: raison, }, });