Skip to content

Commit

Permalink
feat: améliorations du rapport de transmissions (#3922)
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Gaucher <[email protected]>
  • Loading branch information
Pomarom and Pomarom authored Dec 12, 2024
1 parent 69f1956 commit 0ee7946
Show file tree
Hide file tree
Showing 16 changed files with 104 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { DossierApprenantSchemaV3BaseWithApiDataType } from "shared/models/parts/dossierApprenantSchemaV3";
import { z, ZodIssueCode } from "zod";

import { DossierApprenantSchemaV3BaseWithApiDataType } from "./dossierApprenantSchemaV3";

export const validateContrat = (
contrat: DossierApprenantSchemaV3BaseWithApiDataType,
suffix: string,
Expand Down
3 changes: 1 addition & 2 deletions server/src/common/validation/dossierApprenantSchemaV1V2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { primitivesV1, primitivesV3 } from "shared/models/parts/zodPrimitives";
import { z } from "zod";

import { primitivesV1, primitivesV3 } from "@/common/validation/utils/zodPrimitives";

/**
* Note: ce schema est seulement utilisé pour générer la documentation OpenAPI pour l'API v1.
* Les données entrantes de l'API V1 sont validées par dossierApprenantSchema (Joi).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import express from "express";
import { extensions } from "shared/models/parts/zodPrimitives";
import { z } from "zod";

import {
getAllTransmissionStatusGroupedByDate,
getAllErrorsTransmissionStatusGroupedByOrganismeForAGivenDay,
} from "@/common/actions/indicateurs/transmissions/transmission.action";
import paginationSchema from "@/common/validation/paginationSchema";
import { extensions } from "@/common/validation/utils/zodPrimitives";
import { returnResult } from "@/http/middlewares/helpers";
import validateRequestMiddleware from "@/http/middlewares/validateRequestMiddleware";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { captureException } from "@sentry/node";
import express from "express";
import Joi from "joi";
import { ObjectId } from "mongodb";
import { dossierApprenantSchemaV3Input, stripModelAdditionalKeys } from "shared/models/parts/dossierApprenantSchemaV3";

import { updateOrganisme } from "@/common/actions/organismes/organismes.actions";
import logger from "@/common/logger";
Expand All @@ -10,7 +11,6 @@ import { defaultValuesEffectifQueue } from "@/common/model/effectifsQueue.model"
import { formatError } from "@/common/utils/errorUtils";
import stripNullProperties from "@/common/utils/stripNullProperties";
import dossierApprenantSchemaV1V2 from "@/common/validation/dossierApprenantSchemaV1V2";
import { dossierApprenantSchemaV3Input, stripModelAdditionalKeys } from "@/common/validation/dossierApprenantSchemaV3";

const POST_DOSSIERS_APPRENANTS_MAX_INPUT_LENGTH = 2000;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ObjectId } from "bson";
import express from "express";
import { extensions } from "shared/models/parts/zodPrimitives";
import { z } from "zod";

import {
Expand All @@ -9,7 +10,6 @@ import {
} from "@/common/actions/indicateurs/transmissions/transmission.action";
import { updateOrganisme } from "@/common/actions/organismes/organismes.actions";
import paginationSchema from "@/common/validation/paginationSchema";
import { extensions } from "@/common/validation/utils/zodPrimitives";
import { returnResult, requireOrganismePermission } from "@/http/middlewares/helpers";
import validateRequestMiddleware from "@/http/middlewares/validateRequestMiddleware";

Expand Down
10 changes: 5 additions & 5 deletions server/src/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import {
typesOrganismesIndicateurs,
zEffectifArchive,
} from "shared";
import {
computeWarningsForDossierApprenantSchemaV3,
dossierApprenantSchemaV3WithMoreRequiredFieldsValidatingUAISiret,
} from "shared/models/parts/dossierApprenantSchemaV3";
import { extensions, primitivesV1, primitivesV3 } from "shared/models/parts/zodPrimitives";
import swaggerUi from "swagger-ui-express";
import { z } from "zod";

Expand Down Expand Up @@ -114,15 +119,10 @@ import stripNullProperties from "@/common/utils/stripNullProperties";
import { passwordSchema, validateFullObjectSchema, validateFullZodObjectSchema } from "@/common/utils/validationUtils";
import { SReqPostVerifyUser } from "@/common/validation/ApiERPSchema";
import { configurationERPSchema } from "@/common/validation/configurationERPSchema";
import {
computeWarningsForDossierApprenantSchemaV3,
dossierApprenantSchemaV3WithMoreRequiredFieldsValidatingUAISiret,
} from "@/common/validation/dossierApprenantSchemaV3";
import loginSchemaLegacy from "@/common/validation/loginSchemaLegacy";
import objectIdSchema from "@/common/validation/objectIdSchema";
import { registrationSchema, registrationUnknownNetworkSchema } from "@/common/validation/registrationSchema";
import userProfileSchema from "@/common/validation/userProfileSchema";
import { extensions, primitivesV1, primitivesV3 } from "@/common/validation/utils/zodPrimitives";
import config from "@/config";

import { authMiddleware, checkActivationToken, checkPasswordToken } from "./helpers/passport-handlers";
Expand Down
2 changes: 1 addition & 1 deletion server/src/jobs/hydrate/open-api/schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { OpenAPIRegistry, OpenApiGeneratorV3, RouteConfig } from "@asteasolutions/zod-to-openapi";
import { SourceApprenantEnum, TD_API_ELEMENT_LINK } from "shared/constants";
import { dossierApprenantSchemaV3Base } from "shared/models/parts/dossierApprenantSchemaV3";
import { z } from "zod";

import dossierApprenantSchema from "@/common/validation/dossierApprenantSchemaV1V2";
import { dossierApprenantSchemaV3Base } from "@/common/validation/dossierApprenantSchemaV3";
import loginSchemaLegacy from "@/common/validation/loginSchemaLegacy";

const dossierApprenantSchemaWithErrors = dossierApprenantSchema().extend({
Expand Down
6 changes: 3 additions & 3 deletions server/src/jobs/ingestion/process-ingestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
} from "shared/models/data/effectifs.model";
import { IEffectifQueue } from "shared/models/data/effectifsQueue.model";
import { IOrganisme } from "shared/models/data/organismes.model";
import dossierApprenantSchemaV3, {
DossierApprenantSchemaV3ZodType,
} from "shared/models/parts/dossierApprenantSchemaV3";
import { NEVER, SafeParseReturnType, ZodIssueCode } from "zod";

import { updateVoeuxAffelnetEffectif } from "@/common/actions/affelnet.actions";
Expand All @@ -36,9 +39,6 @@ import { validateContrat } from "@/common/validation/contratsDossierApprenantSch
import dossierApprenantSchemaV1V2, {
DossierApprenantSchemaV1V2ZodType,
} from "@/common/validation/dossierApprenantSchemaV1V2";
import dossierApprenantSchemaV3, {
DossierApprenantSchemaV3ZodType,
} from "@/common/validation/dossierApprenantSchemaV3";

import {
fiabilisationEffectifFormation,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { primitivesV3 } from "shared/models/parts/zodPrimitives";
import { it, expect, describe } from "vitest";

import { primitivesV3 } from "@/common/validation/utils/zodPrimitives";

describe("Regex primitivesV3", () => {
describe("derniere_situation", () => {
it("should validate", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { z } from "zod";

import { primitivesV1, primitivesV3 } from "@/common/validation/utils/zodPrimitives";
import { primitivesV1, primitivesV3 } from "./zodPrimitives";

export const stripModelAdditionalKeys = (validationSchema, data) => {
export const stripModelAdditionalKeys = (validationSchema: any, data: any) => {
const strippedData = Object.keys(validationSchema.shape).reduce((acc, curr) => {
return data[curr] !== undefined
? {
Expand Down Expand Up @@ -195,13 +195,13 @@ export function dossierApprenantSchemaV3WithMoreRequiredFieldsValidatingUAISiret
);
}

export function computeWarningsForDossierApprenantSchemaV3(data) {
export function computeWarningsForDossierApprenantSchemaV3(data: Array<DossierApprenantSchemaV3ZodType>) {
return {
contratCount: countContratWarning(data),
};
}

const countContratWarning = (data) => {
const countContratWarning = (data: Array<DossierApprenantSchemaV3ZodType>) => {
return data.reduce(
(acc: number, { contrat_date_debut, contrat_date_debut_2, contrat_date_debut_3, contrat_date_debut_4 }) => {
return !contrat_date_debut && !contrat_date_debut_2 && !contrat_date_debut_3 && !contrat_date_debut_4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
import { subDays } from "date-fns";
import { capitalize } from "lodash-es";
import { z } from "zod";

import {
CODES_STATUT_APPRENANT_ENUM,
EFFECTIF_DERNIER_SITUATION,
Expand All @@ -13,9 +15,8 @@ import {
DERNIER_ORGANISME_UAI_REGEX,
PHONE_REGEX_PATTERN,
} from "shared";
import { z } from "zod";

import { telephoneConverter } from "./frenchTelephoneNumber";
import { telephoneConverter } from "../../../server/src/common/validation/utils/frenchTelephoneNumber";

extendZodWithOpenApi(z);

Expand Down
1 change: 1 addition & 0 deletions shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"api-alternance-sdk": "^2.1.0",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.1",
"lodash-es": "^4.17.21",
"type-fest": "^4.26.1",
"zod": "^3.23.8",
"zod-mongodb-schema": "^1.0.2"
Expand Down
1 change: 0 additions & 1 deletion ui/common/utils/dateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,5 @@ export const formatDateHourMinutesSecondsMs = (date: string) => {
hour: "numeric",
minute: "numeric",
second: "numeric",
fractionalSecondDigits: 3,
}).format(d);
};
79 changes: 45 additions & 34 deletions ui/components/Effectif/EffectifQueueItemView.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
import { WarningTwoIcon, InfoIcon } from "@chakra-ui/icons";
import { Box, Table, Tbody, Text, Tr, Td, UnorderedList, TableContainer, ListItem, Link } from "@chakra-ui/react";
import { SOURCE_APPRENANT, TD_MANUEL_ELEMENT_LINK } from "shared";
import { dossierApprenantSchemaV3Base } from "shared/models/parts/dossierApprenantSchemaV3";
import { z } from "zod";

import { InfoTooltip } from "../Tooltip/InfoTooltip";

import { ErrorMessages } from "./EffectifErrorsMessage";

const attributes = [
{ label: "Identifant ERP", value: "id_erp_apprenant" },
{ label: "Nom de naissance", value: "nom_apprenant" },
{ label: "Prénom", value: "prenom_apprenant" },
{ label: "Date de naissance", value: "date_de_naissance_apprenant" },
{ label: "Année scolaire", value: "annee_scolaire" },
{ label: "Statut apprenant", value: "statut_apprenant" },
{ label: "Date de changement de statut", value: "date_metier_mise_a_jour_statut" },
{ label: "Identifant ERP", value: "id_erp_apprenant" },
{ label: "INE de l'apprenant", value: "ine_apprenant" },
{ label: "Sexe", value: "sexe_apprenant" },
{ label: "Code postal de naissance", value: "code_postal_de_naissance_apprenant" },
{ label: "Courriel", value: "email_contact" },
{ label: "Téléphone", value: "tel_apprenant" },
{ label: "Libellé court formation", value: "libelle_court_formation" },
{ label: "Année de la formation", value: "annee_formation" },
{ label: "Code RNCP", value: "formation_rncp" },
{ label: "Date de début de contrat", value: "contrat_date_debut" },
{ label: "Date de fin du contrat", value: "contrat_date_fin" },
{ label: "Date de rupture du contrat", value: "contrat_date_rupture" },
{ label: "Adresse", value: "adresse_apprenant" },
{ label: "Code postal de résidence", value: "code_postal_apprenant" },
{ label: "Code postal de naissance", value: "code_postal_de_naissance_apprenant" },
{ label: "Sexe", value: "sexe_apprenant" },
{ label: "INE de l'apprenant", value: "ine_apprenant" },
{ label: "Téléphone", value: "tel_apprenant" },
{ label: "RQTH", value: "rqth_apprenant" },
{ label: "Date de reconnaisance RQTH", value: "date_rqth_apprenant" },
{ label: "Email du responsable 1", value: "responsable_apprenant_mail1" },
{ label: "Email du responsable 2", value: "responsable_apprenant_mail2" },
{ label: "Diplôme de la formation obtenu", value: "obtention_diplome_formation" },
{ label: "Date d’obtention du diplôme", value: "date_obtention_diplome_formation" },
{ label: "Date d’exclusion de la formation", value: "date_exclusion_formation" },
{ label: "Cause d’exclusion de la formation", value: "cause_exclusion_formation" },
{ label: "Nom du référent handicap de la formation", value: "nom_referent_handicap_formation" },
{ label: "Prénom du référent handicap de la formation", value: "prenom_referent_handicap_formation" },
{ label: "Courriel du référent handicap de la formation", value: "email_referent_handicap_formation" },
{ label: "UAI du dernier organisme", value: "dernier_organisme_uai" },
{ label: "Dernière situation", value: "derniere_situation" },
{ label: "Type de CFA", value: "type_cfa" },
{ label: "Date de début de contrat", value: "contrat_date_debut" },
{ label: "Date de fin du contrat", value: "contrat_date_fin" },
{ label: "Date de rupture du contrat", value: "contrat_date_rupture" },
{ label: "Cause de la rupture du contrat", value: "cause_rupture_contrat" },
{ label: "SIRET de l’employeur ", value: "siret_employeur" },
{ label: "Date de début du contrat 2", value: "contrat_date_debut_2" },
{ label: "Date de fin du contrat 2", value: "contrat_date_fin_2" },
{ label: "Date de rupture du contrat 2", value: "contrat_date_rupture_2" },
{ label: "Cause de rupture du contrat 2", value: "cause_rupture_contrat_2" },
{ label: "SIRET de l’employeur 2", value: "siret_employeur_2" },
{ label: "Date de début du contrat 3", value: "contrat_date_debut_3" },
{ label: "Date de fin du contrat 3", value: "contrat_date_fin_3" },
{ label: "Date de rupture du contrat 3", value: "contrat_date_rupture_3" },
{ label: "Cause de rupture du contrat 3", value: "cause_rupture_contrat_3" },
{ label: "SIRET de l’employeur 3", value: "siret_employeur_3" },
{ label: "Date de début du contrat 4", value: "contrat_date_debut_4" },
{ label: "Date de fin du contrat 4", value: "contrat_date_fin_4" },
{ label: "Date de rupture du contrat 4", value: "contrat_date_rupture_4" },
{ label: "Cause de rupture du contrat 4", value: "cause_rupture_contrat_4" },
{ label: "SIRET de l’employeur ", value: "siret_employeur" },
{ label: "SIRET de l’employeur 2", value: "siret_employeur_2" },
{ label: "SIRET de l’employeur 3", value: "siret_employeur_3" },
{ label: "SIRET de l’employeur 4", value: "siret_employeur_4" },
{ label: "Formation présentielle", value: "formation_presentielle" },
{ label: "Durée théorique de la formation ( années )", value: "duree_theorique_formation" },
{ label: "Durée théorique de la formation ( mois )", value: "duree_theorique_formation_mois" },
{ label: "Année scolaire", value: "annee_scolaire" },
{ label: "Année de la formation", value: "annee_formation" },
{ label: "Code RNCP", value: "formation_rncp" },
{ label: "Code CFD de la formation", value: "formation_cfd" },
{ label: "Date inscription dans la formation", value: "date_inscription_formation" },
{ label: "Date d'entrée dans la formation", value: "date_entree_formation" },
{ label: "Date de fin de la formation", value: "date_fin_formation" },
{ label: "Durée théorique de la formation ( années )", value: "duree_theorique_formation" },
{ label: "Durée théorique de la formation ( mois )", value: "duree_theorique_formation_mois" },
{ label: "Libellé court formation", value: "libelle_court_formation" },
{ label: "Diplôme de la formation obtenu", value: "obtention_diplome_formation" },
{ label: "Date d’obtention du diplôme", value: "date_obtention_diplome_formation" },
{ label: "Date d’exclusion de la formation", value: "date_exclusion_formation" },
{ label: "Cause d’exclusion de la formation", value: "cause_exclusion_formation" },
{ label: "Formation présentielle", value: "formation_presentielle" },
{ label: "Nom du référent handicap de la formation", value: "nom_referent_handicap_formation" },
{ label: "Prénom du référent handicap de la formation", value: "prenom_referent_handicap_formation" },
{ label: "Courriel du référent handicap de la formation", value: "email_referent_handicap_formation" },
{ label: "UAI de l'établissement responsable", value: "etablissement_responsable_uai" },
{ label: "SIRET de l'établissement responsable", value: "etablissement_responsable_siret" },
{ label: "UAI de l'établissement formateur", value: "etablissement_formateur_uai" },
Expand All @@ -72,10 +76,6 @@ const attributes = [
label: "Code postal de l'établissement du lieu de formation",
value: "etablissement_lieu_de_formation_code_postal",
},
{ label: "Code CFD de la formation", value: "formation_cfd" },
{ label: "Dernière situation", value: "derniere_situation" },
{ label: "UAI du dernier organisme", value: "dernier_organisme_uai" },
{ label: "Type de CFA", value: "type_cfa" },
];
interface EffectifQueueItemViewProps {
effectifQueueItem: any; // use zod typings
Expand Down Expand Up @@ -108,7 +108,13 @@ const DescriptionErrorListComponent = ({ errorList }) => (

const EffectifQueueItemView = ({ effectifQueueItem }: EffectifQueueItemViewProps) => {
const validationErrorFormated = buildValidationError(effectifQueueItem.validation_errors);

const computeRequired = (value) => {
return !(dossierApprenantSchemaV3Base().shape[value] instanceof z.ZodOptional) ? (
<Box as="span" role="presentation" aria-hidden="true" color="red.500" ml={1}>
*
</Box>
) : null;
};
return (
<Box>
{effectifQueueItem.source !== SOURCE_APPRENANT.FICHIER ? (
Expand All @@ -131,8 +137,13 @@ const EffectifQueueItemView = ({ effectifQueueItem }: EffectifQueueItemViewProps
<Tbody>
{attributes.map((rowItem, index) => (
<Tr key={index}>
<Td fontStyle="italic">{rowItem.label}</Td>
<Td>{rowItem.value}</Td>
<Td fontStyle="italic">
{rowItem.label}
{computeRequired(rowItem.value)}
</Td>
<Td>
{rowItem.value} {computeRequired(rowItem.value)}
</Td>
<Td fontWeight="bold">
{validationErrorFormated[rowItem.value] ? <WarningTwoIcon color="#CE0500" mr={1} /> : null}
{effectifQueueItem[rowItem.value]}
Expand Down
36 changes: 35 additions & 1 deletion ui/modules/transmissions/TransmissionsErrorSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Box, Text, UnorderedList, ListItem, HStack } from "@chakra-ui/react";
import { Box, Text, UnorderedList, ListItem, HStack, Link } from "@chakra-ui/react";
import { REFERENTIEL_ONISEP } from "shared";

import Ribbons from "@/components/Ribbons/Ribbons";
import { InfoTooltip } from "@/components/Tooltip/InfoTooltip";

interface TransmissionsErrorSummaryProps {
summary: any;
isLoading: boolean;
}

const TransmissionsErrorSummary = (props: TransmissionsErrorSummaryProps) => {
const hasUaiSiretErrors = Boolean(
props.summary.lieu?.length || props.summary.formateur?.length || props.summary.responsable?.length
);

if (!props.summary.numberErrors || props.isLoading) {
return;
}
Expand All @@ -17,7 +23,35 @@ const TransmissionsErrorSummary = (props: TransmissionsErrorSummaryProps) => {
<Box color="black">
<HStack mb={2}>
<Text fontWeight={"bold"}>{props.summary.numberErrors?.total} erreurs ont été détectées.</Text>
{hasUaiSiretErrors && <Text fontWeight={"bold"}>Voici les erreurs les plus récurrentes.</Text>}
</HStack>
{hasUaiSiretErrors && (
<Text fontWeight={"bold"}>
{" "}
Erreurs sur les couples UAI/SIRET{" "}
<InfoTooltip
contentComponent={() => (
<Box>
<Text>
Cette erreur signifie que vous envoyez certains effectifs vers un organisme (UAI-SIRET) qui n’existe
pas chez nous.
</Text>
<Text>
Vérifiez l’UAI-SIRET de votre organisme sur le{" "}
<Link isExternal href={REFERENTIEL_ONISEP} textDecoration="underline">
Référentiel UAI-SIRET des OFA-CFA
</Link>{" "}
et corrigez-les dans votre ERP.
</Text>
<Text>
Si vous ne trouvez pas votre organisme sur le Référentiel UAI-SIRET, c’est qu’il n’est pas reconnu
OFA. Si c’est le cas, laissez l’erreur, nous travaillons dessus actuellement. Merci.
</Text>
</Box>
)}
/>
</Text>
)}
<UnorderedList p={2}>
{props.summary.lieu?.map(({ uai, siret, effectifCount }) => (
<ListItem key={`lieu${uai}${siret}`}>
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16328,6 +16328,7 @@ __metadata:
bson: ^5.5.1
date-fns: ^2.30.0
date-fns-tz: ^2.0.1
lodash-es: ^4.17.21
mongodb: ^5.9.2
type-fest: ^4.26.1
typescript: ^5.6.3
Expand Down

0 comments on commit 0ee7946

Please sign in to comment.