Skip to content

Commit

Permalink
fix: bug 1777 population favorable (#1846)
Browse files Browse the repository at this point in the history
  • Loading branch information
pom421 authored Nov 20, 2023
1 parent 166b440 commit 24b9520
Show file tree
Hide file tree
Showing 23 changed files with 658 additions and 448 deletions.
42 changes: 29 additions & 13 deletions packages/app/src/api/core-domain/useCases/SaveDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DeclarationSource } from "@common/core-domain/domain/valueObjects/decla
import { Siren } from "@common/core-domain/domain/valueObjects/Siren";
import { type CreateDeclarationDTO } from "@common/core-domain/dtos/DeclarationDTO";
import { companyMap } from "@common/core-domain/mappers/companyMap";
import { AppError, type EntityPropsToJson, type UseCase } from "@common/shared-domain";
import { AppError, type EntityPropsToJson, type UseCase, ValidationError } from "@common/shared-domain";
import { PositiveInteger, PositiveNumber } from "@common/shared-domain/domain/valueObjects";
import { keepEntryBy } from "@common/utils/object";
import { add, isAfter } from "date-fns";
Expand Down Expand Up @@ -102,7 +102,10 @@ export class SaveDeclaration implements UseCase<Input, void> {
remunerations: {
cseConsultationDate:
dto.remunerations?.estCalculable === "oui" ? dto.remunerations.dateConsultationCSE : undefined,
favorablePopulation: dto["remunerations-resultat"]?.populationFavorable,
favorablePopulation:
dto["remunerations"]?.estCalculable === "oui"
? dto["remunerations-resultat"]?.populationFavorable ?? "egalite"
: undefined,
mode: remunerationsMode,
notComputableReason:
dto.remunerations?.estCalculable === "non" ? dto.remunerations.motifNonCalculabilité : undefined,
Expand All @@ -113,21 +116,21 @@ export class SaveDeclaration implements UseCase<Input, void> {
? dto["remunerations-csp"]?.catégories.length
? dto["remunerations-csp"].catégories.map(({ nom, tranches }) => ({
name: nom,
ranges: keepEntryBy(tranches, val => val !== null),
ranges: keepEntryBy(tranches, val => val !== ""),
}))
: []
: remunerationsMode === "niveau_autre"
? dto["remunerations-coefficient-autre"]?.catégories.length
? dto["remunerations-coefficient-autre"].catégories.map(({ nom, tranches }) => ({
name: nom,
ranges: keepEntryBy(tranches, val => val !== null),
ranges: keepEntryBy(tranches, val => val !== ""),
}))
: []
: remunerationsMode === "niveau_branche"
? dto["remunerations-coefficient-branche"]?.catégories.length
? dto["remunerations-coefficient-branche"].catégories.map(({ nom, tranches }) => ({
name: nom,
ranges: keepEntryBy(tranches, val => val !== null),
ranges: keepEntryBy(tranches, val => val !== ""),
}))
: []
: ([] satisfies Categorie[]),
Expand All @@ -136,10 +139,15 @@ export class SaveDeclaration implements UseCase<Input, void> {
salaryRaises: {
categories:
dto.augmentations?.estCalculable === "oui"
? dto.augmentations.catégories.map(category => category.écarts)
? [
dto.augmentations.catégories.ouv === "" ? null : dto.augmentations.catégories.ouv,
dto.augmentations.catégories.emp === "" ? null : dto.augmentations.catégories.emp,
dto.augmentations.catégories.tam === "" ? null : dto.augmentations.catégories.tam,
dto.augmentations.catégories.ic === "" ? null : dto.augmentations.catégories.ic,
]
: [null, null, null, null],
favorablePopulation:
dto.augmentations?.estCalculable === "oui" ? dto.augmentations.populationFavorable : undefined,
dto.augmentations?.estCalculable === "oui" ? dto.augmentations.populationFavorable ?? "egalite" : undefined,
notComputableReason:
dto.augmentations?.estCalculable == "non" ? dto.augmentations.motifNonCalculabilité : undefined,
result: dto.augmentations?.estCalculable === "oui" ? dto.augmentations.résultat : undefined,
Expand All @@ -148,12 +156,18 @@ export class SaveDeclaration implements UseCase<Input, void> {
promotions: {
notComputableReason:
dto.promotions?.estCalculable === "non" ? dto.promotions.motifNonCalculabilité : undefined,
favorablePopulation: dto.promotions?.estCalculable === "oui" ? dto.promotions.populationFavorable : undefined,
favorablePopulation:
dto.promotions?.estCalculable === "oui" ? dto.promotions.populationFavorable ?? "egalite" : undefined,
result: dto.promotions?.estCalculable === "oui" ? dto.promotions.résultat : undefined,
score: dto.promotions?.estCalculable === "oui" ? dto.promotions.note : undefined,
categories:
dto.promotions?.estCalculable === "oui"
? dto.promotions.catégories.map(category => category.écarts)
? [
dto.promotions.catégories.ouv === "" ? null : dto.promotions.catégories.ouv,
dto.promotions.catégories.emp === "" ? null : dto.promotions.catégories.emp,
dto.promotions.catégories.tam === "" ? null : dto.promotions.catégories.tam,
dto.promotions.catégories.ic === "" ? null : dto.promotions.catégories.ic,
]
: [null, null, null, null],
},
}),
Expand All @@ -165,7 +179,7 @@ export class SaveDeclaration implements UseCase<Input, void> {
: undefined,
favorablePopulation:
dto["augmentations-et-promotions"]?.estCalculable === "oui"
? dto["augmentations-et-promotions"].populationFavorable
? dto["augmentations-et-promotions"].populationFavorable ?? "egalite"
: undefined,
employeesCountResult:
dto["augmentations-et-promotions"]?.estCalculable === "oui"
Expand Down Expand Up @@ -199,11 +213,13 @@ export class SaveDeclaration implements UseCase<Input, void> {
? undefined
: {
result: dto["hautes-remunerations"].résultat,
favorablePopulation: dto["hautes-remunerations"].populationFavorable,
favorablePopulation: dto["hautes-remunerations"].populationFavorable ?? "egalite",
score: dto["hautes-remunerations"].note,
},
} satisfies EntityPropsToJson<DeclarationProps>;

console.log("partialDeclaration", JSON.stringify(partialDeclaration, null, 2));

let declaration: Declaration;

try {
Expand Down Expand Up @@ -272,8 +288,8 @@ export class SaveDeclaration implements UseCase<Input, void> {
throw specification.lastError;
}
} catch (error: unknown) {
if (error instanceof DeclarationSpecificationError) {
console.error(error.message);
if (error instanceof DeclarationSpecificationError || error instanceof ValidationError) {
console.error(error);
throw error;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
"use client";

import { fr } from "@codegouvfr/react-dsfr";
import Alert from "@codegouvfr/react-dsfr/Alert";
import { Button } from "@codegouvfr/react-dsfr/Button";
import { cx } from "@codegouvfr/react-dsfr/tools/cx";
import { CSP } from "@common/core-domain/domain/valueObjects/CSP";
import { AgeRange } from "@common/core-domain/domain/valueObjects/declaration/AgeRange";
import { type Catégorie, type DeclarationDTO } from "@common/core-domain/dtos/DeclarationDTO";
import { type DeclarationDTO } from "@common/core-domain/dtos/DeclarationDTO";
import { type Remunerations } from "@common/models/generated";
import { zodNumberOrNaNOrNull } from "@common/utils/form";
import { zodNumberOrEmptyString } from "@common/utils/form";
import { zodFr } from "@common/utils/zod";
import { PercentageInput } from "@components/RHF/PercentageInput";
import { ClientOnly } from "@components/utils/ClientOnly";
import { SkeletonForm } from "@components/utils/skeleton/SkeletonForm";
import { AlertMessage } from "@design-system/client";
import { ClientAnimate } from "@design-system/utils/client/ClientAnimate";
import { zodResolver } from "@hookform/resolvers/zod";
import { useDeclarationFormManager } from "@services/apiClient/useDeclarationFormManager";
import { useRouter } from "next/navigation";
import { FormProvider, useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";

import { NOT_ALL_EMPTY_CATEGORIES } from "../../../messages";
import { BackNextButtons } from "../BackNextButtons";
import { assertOrRedirectCommencerStep, funnelConfig, type FunnelKey } from "../declarationFunnelConfiguration";
import { funnelConfig, type FunnelKey } from "../declarationFunnelConfiguration";
import style from "./RemunerationGenericForm.module.scss";

const formSchema = zodFr.object({
Expand All @@ -30,36 +31,43 @@ const formSchema = zodFr.object({
z.object({
nom: z.string(),
tranches: z.object({
[AgeRange.Enum.LESS_THAN_30]: zodNumberOrNaNOrNull,
[AgeRange.Enum.FROM_30_TO_39]: zodNumberOrNaNOrNull,
[AgeRange.Enum.FROM_40_TO_49]: zodNumberOrNaNOrNull,
[AgeRange.Enum.FROM_50_TO_MORE]: zodNumberOrNaNOrNull,
[AgeRange.Enum.LESS_THAN_30]: zodNumberOrEmptyString,
[AgeRange.Enum.FROM_30_TO_39]: zodNumberOrEmptyString,
[AgeRange.Enum.FROM_40_TO_49]: zodNumberOrEmptyString,
[AgeRange.Enum.FROM_50_TO_MORE]: zodNumberOrEmptyString,
}),
}),
)
.superRefine((val, ctx) => {
if (notFilled(val)) {
.superRefine((catégories, ctx) => {
if (notFilled(catégories)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Vous devez renseigner au moins un écart si votre indicateur est calculable",
message: NOT_ALL_EMPTY_CATEGORIES,
});
}
}),
});

// Infer the TS type according to the zod schema.
type FormType = z.infer<typeof formSchema>;

const notFilled = (catégories: FormType["catégories"]) =>
catégories.every(
catégorie =>
catégorie.tranches[AgeRange.Enum.LESS_THAN_30] === null &&
catégorie.tranches[AgeRange.Enum.FROM_30_TO_39] === null &&
catégorie.tranches[AgeRange.Enum.FROM_40_TO_49] === null &&
catégorie.tranches[AgeRange.Enum.FROM_50_TO_MORE] === null,
catégorie.tranches[AgeRange.Enum.LESS_THAN_30] === "" &&
catégorie.tranches[AgeRange.Enum.FROM_30_TO_39] === "" &&
catégorie.tranches[AgeRange.Enum.FROM_40_TO_49] === "" &&
catégorie.tranches[AgeRange.Enum.FROM_50_TO_MORE] === "",
);

const defaultTranch = { ":29": null, "30:39": null, "40:49": null, "50:": null };
// Infer the TS type according to the zod schema.
type FormType = z.infer<typeof formSchema>;

type NumberOrEmptyString = number | "";

const defaultTranch = {
":29": "" as NumberOrEmptyString,
"30:39": "" as NumberOrEmptyString,
"40:49": "" as NumberOrEmptyString,
"50:": "" as NumberOrEmptyString,
};

const buildDefaultCategories = (mode: Remunerations["mode"]) =>
mode === "csp"
Expand All @@ -84,18 +92,20 @@ export const RemunerationGenericForm = ({ mode }: { mode: Remunerations["mode"]
const router = useRouter();
const { formData, savePageData } = useDeclarationFormManager();

assertOrRedirectCommencerStep(formData);
// assertOrRedirectCommencerStep(formData);

const methods = useForm<FormType>({
mode: "onChange",
shouldUnregister: true,
resolver: zodResolver(formSchema),
defaultValues: formData[stepName] || buildDefaultCategories(mode),
});
const {
control,
register,
handleSubmit,
getValues,
formState: { errors, isValid },
setError,
} = methods;

const {
Expand All @@ -111,26 +121,16 @@ export const RemunerationGenericForm = ({ mode }: { mode: Remunerations["mode"]
});

const onSubmit = async (data: FormType) => {
if (notFilled(data.catégories)) {
return setError("root.catégories", {
message:
mode === "csp"
? "Vous devez renseigner les écarts de rémunération pour les CSP et tranches d'âge concernés."
: "Vous devez renseigner les écarts de rémunération pour les tranches d'âge concernées.",
});
}

savePageData(stepName, data as DeclarationDTO[typeof stepName]);

router.push(funnelConfig(formData)[stepName].next().url);
};

const getCSPTitle = (catégorie: Catégorie) =>
mode === "csp" ? new CSP(catégorie.nom as CSP.Enum).getLabel() : undefined;

return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<AlertMessage title="Erreur" message={errors.catégories?.root?.message} />

<ClientOnly fallback={<SkeletonForm fields={2} />}>
<div className={fr.cx("fr-mb-8w")}></div>

Expand All @@ -140,7 +140,9 @@ export const RemunerationGenericForm = ({ mode }: { mode: Remunerations["mode"]
<div className={cx(fr.cx("fr-col"), style["category-title"])}>
{/* Name of catégorie doesn't matter when mode is coef, so don't bother with inconsistent name between storage & UI */}
<span className={fr.cx("fr-text--bold")}>
{getCSPTitle(catégorie) || `Niveau ou coefficient ${index + 1}`}
{mode === "csp"
? new CSP(catégorie.nom as CSP.Enum).getLabel()
: `Niveau ou coefficient ${index + 1}`}
</span>
{mode !== "csp" && (
<Button
Expand Down Expand Up @@ -175,6 +177,10 @@ export const RemunerationGenericForm = ({ mode }: { mode: Remunerations["mode"]
AgeRange.Enum.FROM_50_TO_MORE,
].map(key => (
<td key={key}>
<input
{...register(`catégories.${index}.nom`, { value: getValues(`catégories.${index}.nom`) })}
type="hidden"
/>
<PercentageInput<FormType> name={`catégories.${index}.tranches.${key}`} />
</td>
))}
Expand All @@ -201,11 +207,6 @@ export const RemunerationGenericForm = ({ mode }: { mode: Remunerations["mode"]
)}
</ClientOnly>

<ClientAnimate>
{errors.root?.catégories && (
<Alert severity="error" title="Informations manquantes" description={errors.root.catégories.message} />
)}
</ClientAnimate>
<BackNextButtons stepName={stepName} disabled={!isValid} />
</form>
</FormProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { jsxPdfService } from "@api/shared-domain/infra/pdf";
import { assertServerSession } from "@api/utils/auth";
import { DeclarationSpecificationError } from "@common/core-domain/domain/specification/DeclarationSpecification";
import { type CreateDeclarationDTO } from "@common/core-domain/dtos/DeclarationDTO";
import { ValidationError } from "@common/shared-domain";
import { type ServerActionResponse } from "@common/utils/next";
import assert from "assert";

Expand Down Expand Up @@ -69,9 +70,7 @@ export async function saveDeclaration(
ok: true,
};
} catch (error: unknown) {
console.error(error);

if (error instanceof DeclarationSpecificationError) {
if (error instanceof DeclarationSpecificationError || error instanceof ValidationError) {
return {
ok: false,
error: error.message ?? error.previousError,
Expand Down
Loading

0 comments on commit 24b9520

Please sign in to comment.