Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Formulaire de modification d'une candidature #2300

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ export const register = () => {
// la correction d'une candidature ne peut pas modifier le champs notification ou validateur
// on peut donc sans crainte utiliser ces 2 champs
if (!candidature.notification?.notifiéeLe) {
logger.info(`L'attestation ne sera pas regénérée car la candidature n'est pas notifiée`, {
logger.info(`L'attestation ne sera pas régénérée car la candidature n'est pas notifiée`, {
identifiantProjet,
});
return;
} else if (event.payload.doitRégénérerAttestation !== true) {
logger.info(`L'attestation ne sera pas regénérée`, {
logger.info(`L'attestation ne sera pas régénérée`, {
identifiantProjet,
});
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ const MenuAdmin = (currentPage?: string) => (
>
Notifier des candidats
</DropdownMenu.DropdownItem>
<DropdownMenu.DropdownItem href={Routes.Candidature.corriger}>
VioMrqs marked this conversation as resolved.
Show resolved Hide resolved
Corriger des candidats
</DropdownMenu.DropdownItem>
<DropdownMenu.DropdownItem href={Routes.Candidature.lister()}>
<DropdownMenu.DropdownItem
href={Routes.Candidature.lister({
estNotifié: false,
})}
>
Tous les candidats
</DropdownMenu.DropdownItem>
</DropdownMenu>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Request } from 'express';
import React, { useState } from 'react';
import React from 'react';
import { appelsOffreStatic } from '../../../../dataAccess/inMemory';
import { ProjectDataForProjectPage } from '../../../../modules/project/queries';
import ROUTES from '../../../../routes';
Expand All @@ -10,13 +10,11 @@ import {
Label,
Section,
Select,
TextArea,
ErrorBox,
Radio,
InfoBox,
Checkbox,
Form,
} from '../../../components';
import { afficherDate } from '../../../helpers';
import { Routes } from '@potentiel-applications/routes';

type EditProjectDataProps = {
project: ProjectDataForProjectPage;
Expand All @@ -30,14 +28,16 @@ export const EditProjectData = ({ project, request }: EditProjectDataProps) => {
return null;
}

const [uploadIsDisabled, disableUpload] = useState(true);

const handleCertificateTypeChange = (e) => {
disableUpload(e.target.value !== 'custom');
};
const identifiantProjet = `${project.appelOffreId}#${project.periodeId}#${project.familleId}#${project.numeroCRE}`;

return (
<Section title="Modifier le projet" icon={<BuildingIcon />} className="print:hidden">
<InfoBox className="mb-5">
Ce formulaire permet de modifier des informations qui ont changé. Pour corriger des données
à la candidature et régénérer l'attestation, utiliser le{' '}
<a href={Routes.Candidature.corriger(identifiantProjet)}>formulaire de modification</a> de
la candidature
</InfoBox>
<Form
action={ROUTES.ADMIN_CORRECT_PROJECT_DATA_ACTION}
method="post"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Message, MessageHandler, mediator } from 'mediateur';
import { Candidature } from '@potentiel-domain/candidature';
import { RebuildTriggered, Event } from '@potentiel-infrastructure/pg-event-sourcing';
import { DateTime, IdentifiantProjet } from '@potentiel-domain/common';
import { findProjection } from '@potentiel-infrastructure/pg-projections';
import { Option } from '@potentiel-libraries/monads';

import { removeProjection } from '../../infrastructure/removeProjection';
import { upsertProjection } from '../../infrastructure/upsertProjection';
Expand All @@ -25,8 +27,10 @@ export const register = () => {
const identifiantProjet = IdentifiantProjet.convertirEnValueType(
payload.identifiantProjet,
);

const candidature: Omit<Candidature.CandidatureEntity, 'type'> = {
const candidature = await findProjection<Candidature.CandidatureEntity>(
`candidature|${payload.identifiantProjet}`,
);
const candidatureToUpsert: Omit<Candidature.CandidatureEntity, 'type'> = {
identifiantProjet: payload.identifiantProjet,
appelOffre: identifiantProjet.appelOffre,
période: identifiantProjet.période,
Expand Down Expand Up @@ -58,12 +62,13 @@ export const register = () => {
: undefined,
technologie: Candidature.TypeTechnologie.convertirEnValueType(payload.technologie).type,
misÀJourLe: type === 'CandidatureCorrigée-V1' ? payload.corrigéLe : payload.importéLe,
estNotifiée: false,
estNotifiée: Option.isSome(candidature) ? candidature.estNotifiée : false,
notification: Option.isSome(candidature) ? candidature.notification : undefined,
};

await upsertProjection<Candidature.CandidatureEntity>(
`candidature|${payload.identifiantProjet}`,
candidature,
candidatureToUpsert,
);
break;
case 'CandidatureNotifiée-V1':
Expand Down
16 changes: 11 additions & 5 deletions packages/applications/routes/src/candidature/candidature.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { encodeParameter } from '../encodeParameter';

export const importer = '/candidatures/importer';
export const corriger = '/candidatures/corriger';
export const corrigerEnMasse = '/candidatures/corriger';

type ListerFilters = {
appelOffre?: string;
Expand Down Expand Up @@ -31,9 +31,15 @@ export const lister = (filters?: ListerFilters) => {
return `/candidatures${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
};

export const prévisualiserAttestation = (identifiantProjet: string) =>
`/candidatures/${encodeParameter(identifiantProjet)}/previsualiser-attestation`;
const _avecIdentifiant =
(path = '') =>
(identifiantProjet: string) =>
`/candidatures/${encodeParameter(identifiantProjet)}${path}`;

export const détails = _avecIdentifiant();
export const corriger = _avecIdentifiant('/corriger');

export const prévisualiserAttestation = _avecIdentifiant('/previsualiser-attestation');
// TODO: à supprimer pour utiliser directement Routes.Document.télécharger dans le front
// une fois qu'on aura migré la page Projet
export const téléchargerAttestation = (identifiantProjet: string) =>
`/candidatures/${encodeParameter(identifiantProjet)}/telecharger-attestation`;
export const téléchargerAttestation = _avecIdentifiant('/telecharger-attestation');
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Metadata, ResolvingMetadata } from 'next';
import { ComponentProps } from 'react';

import { Candidature } from '@potentiel-domain/candidature';

import { IdentifiantParameter } from '@/utils/identifiantParameter';
import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling';
import { decodeParameter } from '@/utils/decodeParameter';
import { CorrigerCandidaturePage } from '@/components/pages/candidature/corriger/CorrigerCandidature.page';

import { getCandidature } from '../../_helpers/getCandidature';

type PageProps = IdentifiantParameter;

export async function generateMetadata(
{ params }: IdentifiantParameter,
_: ResolvingMetadata,
): Promise<Metadata> {
const identifiantProjet = decodeParameter(params.identifiant);
const candidature = await getCandidature(identifiantProjet);

return {
title: `Candidature ${candidature.nomProjet} - Potentiel`,
description: 'Corriger la candidature',
};
}

export default async function Page({ params }: PageProps) {
return PageWithErrorHandling(async () => {
const identifiantProjet = decodeParameter(params.identifiant);
const candidature = await getCandidature(identifiantProjet);
return <CorrigerCandidaturePage {...mapToProps(candidature)} />;
});
}

const mapToProps = (
candidature: Candidature.ConsulterCandidatureReadModel,
): ComponentProps<typeof CorrigerCandidaturePage> => ({
candidature: {
identifiantProjet: candidature.identifiantProjet.formatter(),
statut: candidature.statut.formatter(),
nomProjet: candidature.nomProjet,
nomCandidat: candidature.nomCandidat,
nomRepresentantLegal: candidature.nomReprésentantLégal,
emailContact: candidature.emailContact,
puissanceProductionAnnuelle: candidature.puissanceProductionAnnuelle,
prixReference: candidature.prixReference,
societeMere: candidature.sociétéMère,
noteTotale: candidature.noteTotale,
motifElimination: candidature.motifÉlimination,
puissanceALaPointe: candidature.puissanceALaPointe,
evaluationCarboneSimplifiee: candidature.evaluationCarboneSimplifiée,
actionnariat: candidature.actionnariat?.formatter(),
adresse1: candidature.localité.adresse1,
adresse2: candidature.localité.adresse2,
codePostal: candidature.localité.codePostal,
commune: candidature.localité.commune,
},
estNotifiée: !!candidature.notification,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Metadata, ResolvingMetadata } from 'next';
import { match } from 'ts-pattern';

import { mapToPlainObject } from '@potentiel-domain/core';
import { Role } from '@potentiel-domain/utilisateur';

import { IdentifiantParameter } from '@/utils/identifiantParameter';
import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling';
import { decodeParameter } from '@/utils/decodeParameter';
import {
DétailsCandidaturePage,
DétailsCandidaturePageProps,
} from '@/components/pages/candidature/détails/DétailsCandidature.page';
import { withUtilisateur } from '@/utils/withUtilisateur';

import { getCandidature } from '../_helpers/getCandidature';

type PageProps = IdentifiantParameter;

export async function generateMetadata(
{ params }: IdentifiantParameter,
_: ResolvingMetadata,
): Promise<Metadata> {
const identifiantProjet = decodeParameter(params.identifiant);
const candidature = await getCandidature(identifiantProjet);

return {
title: `Candidature ${candidature.nomProjet} - Potentiel`,
description: 'Détail de la candidature',
};
}

export default async function Page({ params }: PageProps) {
return PageWithErrorHandling(async () =>
withUtilisateur(async (utilisateur) => {
const identifiantProjet = decodeParameter(params.identifiant);
const candidature = await getCandidature(identifiantProjet);
return (
<DétailsCandidaturePage
candidature={mapToPlainObject(candidature)}
actions={mapToActions({ estNotifiée: !!candidature.notification }, utilisateur.role)}
/>
);
}),
);
}

const mapToActions = (props: { estNotifiée: boolean }, role: Role.ValueType) => {
const defaultActions = {
corriger: role.aLaPermission('candidature.corriger'),
};
return match(props)
.returnType<DétailsCandidaturePageProps['actions']>()
.with({ estNotifiée: true }, () => ({
...defaultActions,
prévisualiserAttestation: false,
téléchargerAttestation: true,
}))
.otherwise(() => ({
...defaultActions,
prévisualiserAttestation: role.aLaPermission('candidature.attestation.prévisualiser'),
téléchargerAttestation: false,
}));
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { notFound } from 'next/navigation';

import { Candidature } from '@potentiel-domain/candidature';
import { Option } from '@potentiel-libraries/monads';
import { getLogger } from '@potentiel-libraries/monitoring';

export const getCandidature = cache(async (identifiantProjet: string) => {
const candidature = await mediator.send<Candidature.ConsulterCandidatureQuery>({
Expand All @@ -14,7 +13,6 @@ export const getCandidature = cache(async (identifiantProjet: string) => {
},
});
if (Option.isNone(candidature)) {
getLogger().warn('Candidature non trouvée', { identifiantProjet });
notFound();
}
return candidature;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CorrigerCandidaturesPage } from '@/components/pages/candidature/corriger/CorrigerCandidatures.page';
import { CorrigerCandidaturesPage } from '@/components/pages/candidature/corriger-en-masse/CorrigerCandidatures.page';
import { PageWithErrorHandling } from '@/utils/PageWithErrorHandling';

export default async function Page() {
Expand Down
6 changes: 3 additions & 3 deletions packages/applications/ssr/src/app/periodes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ export default async function Page({ searchParams }: PageProps) {

const props = await mapToProps({
utilisateur,
périodes: périodes.items
.concat(périodesPartiellementNotifiées)
périodes: périodesPartiellementNotifiées
.concat(périodes.items)
.filter(
(val, i, self) =>
self.findIndex((x) => x.identifiantPériode === val.identifiantPériode) === i,
Expand All @@ -94,7 +94,7 @@ export default async function Page({ searchParams }: PageProps) {
filters={filters}
périodes={props}
range={périodes.range}
total={périodes.total || props.length}
total={périodes.total + périodesPartiellementNotifiées.length}
/>
);
}),
Expand Down
18 changes: 8 additions & 10 deletions packages/applications/ssr/src/components/atoms/form/InputDate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@ type InputDateProps = InputProps.RegularInput & {
export const InputDate: FC<InputDateProps> = (props) => {
return (
<Input
{...{
...props,
nativeInputProps: {
...props.nativeInputProps,
min: formatDateForInput(props.nativeInputProps.min),
max: formatDateForInput(props.nativeInputProps.max),
defaultValue: formatDateForInput(props.nativeInputProps.defaultValue),
value: formatDateForInput(props.nativeInputProps.value),
},
{...props}
nativeInputProps={{
...props.nativeInputProps,
min: formatDateForInput(props.nativeInputProps.min),
max: formatDateForInput(props.nativeInputProps.max),
defaultValue: formatDateForInput(props.nativeInputProps.defaultValue),
value: formatDateForInput(props.nativeInputProps.value),
}}
></Input>
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,10 @@ const getNavigationItemsBasedOnRole = (
}),
},
},
{
text: 'Corriger des candidats',
linkProps: {
href: Routes.Candidature.corriger,
},
},
{
text: 'Tous les candidats',
linkProps: {
href: Routes.Candidature.lister(),
href: Routes.Candidature.lister({ estNotifié: false }),
},
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Badge from '@codegouvfr/react-dsfr/Badge';
import React from 'react';

type NotificationBadgeProps = {
estNotifié: boolean;
};

export const NotificationBadge: React.FC<NotificationBadgeProps> = ({ estNotifié }) => {
return (
<Badge small noIcon severity={estNotifié ? 'info' : 'new'}>
{estNotifié ? 'Notifié' : 'À Notifier'}
</Badge>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { FC } from 'react';
import Badge from '@codegouvfr/react-dsfr/Badge';

import { IdentifiantProjet, StatutProjet } from '@potentiel-domain/common';
import { Iso8601DateTime } from '@potentiel-libraries/iso8601-datetime';
import { PlainType } from '@potentiel-domain/core';

import { FormattedDate } from '../../atoms/FormattedDate';
import { NotificationBadge } from '../candidature/NotificationBadge';

import { StatutProjetBadge } from './StatutProjetBadge';

Expand Down Expand Up @@ -61,11 +61,3 @@ const FormattedIdentifiantProjet: FC<{
{famille ? `-F${famille}` : ''}-{numéroCRE}
</div>
);

const NotificationBadge = ({ estNotifié }: Pick<ProjectListItemHeadingProps, 'estNotifié'>) => {
return (
<Badge small noIcon severity={estNotifié ? 'info' : 'new'}>
{estNotifié ? 'Notifié' : 'À Notifier'}
</Badge>
);
};
Loading
Loading