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

feat: améliorations du rapport de transmissions #3922

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -115,15 +120,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 @@ -37,9 +40,6 @@ import { validateContrat } from "@/common/validation/contratsDossierApprenantSch
import dossierApprenantSchemaV1V2, {
DossierApprenantSchemaV1V2ZodType,
} from "@/common/validation/dossierApprenantSchemaV1V2";
import dossierApprenantSchemaV3, {
DossierApprenantSchemaV3ZodType,
} from "@/common/validation/dossierApprenantSchemaV3";

import { fiabilisationUaiSiret } from "../fiabilisation/uai-siret/updateFiabilisation";

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
Loading