From 789ef6985cb83e3417a21ce14229d3e277be5128 Mon Sep 17 00:00:00 2001 From: silto Date: Tue, 8 Oct 2024 18:26:54 +0200 Subject: [PATCH] [TRA 14644] - Ajout d'Eco-organisme sur BSVHU (#3619) * feat(BSVHU): add support for eco organisme on BSVHU * fix(BSVHU/BSDA/BSDD/BSDASRI): add/fix ecoorganisme sirenification * test(BSVHU): add tests for eco-organisme, remove changes on sirenify that where getting infos from EcoOrganisme table (useless) * fix(BSVHU): refine paths on refinements * fix(Refinements): make not dormant refinement explicitly for emitters * feat(Libs): add object creation script * fix(BSVHU/BSDASRI): fix tests * fix(BSDs): fix test, make sirenify not overwrite eco organisme name if the company doesn't exist * fix(BSVHU): fix tests * fix(Object-creator): change object creator to compile * docs(Changelog): update Changelog --- CONTRIBUTING.md | 6 ++ Changelog.md | 4 + back/src/__tests__/factories.ts | 24 +++++- back/src/__tests__/testWorkflow.ts | 5 +- back/src/bsda/validation/refinements.ts | 42 +++-------- back/src/bsda/validation/sirenify.ts | 74 +++++++++++++------ .../createBsdasriEcoOrganisme.integration.ts | 12 ++- .../__tests__/updateBsdasri.integration.ts | 12 ++- back/src/bsdasris/sirenify.ts | 22 ++++-- back/src/bsffs/validation/bsff/refinements.ts | 4 +- back/src/bsvhu/__tests__/bsvhuEdition.test.ts | 12 ++- .../bsvhu/__tests__/elastic.integration.ts | 6 ++ back/src/bsvhu/__tests__/factories.vhu.ts | 16 +++- .../bsvhu/__tests__/registry.integration.ts | 28 ++++++- back/src/bsvhu/converter.ts | 17 ++++- back/src/bsvhu/elastic.ts | 8 +- back/src/bsvhu/pdf/components/BsvhuPdf.tsx | 10 +++ back/src/bsvhu/permissions.ts | 9 +++ back/src/bsvhu/registry.ts | 8 +- .../__tests__/createBsvhu.integration.ts | 72 +++++++++++++++++- .../__tests__/duplicateBsvhu.integration.ts | 15 +++- .../queries/__tests__/bsvhu.integration.ts | 4 + .../__tests__/bsvhumetadata.integration.ts | 2 +- back/src/bsvhu/typeDefs/bsvhu.inputs.graphql | 8 ++ back/src/bsvhu/typeDefs/bsvhu.objects.graphql | 9 +++ .../__tests__/validation.integration.ts | 40 +++++++++- back/src/bsvhu/validation/helpers.ts | 5 +- back/src/bsvhu/validation/refinements.ts | 12 ++- back/src/bsvhu/validation/rules.ts | 14 ++++ back/src/bsvhu/validation/schema.ts | 2 + back/src/bsvhu/validation/sirenify.ts | 9 +++ back/src/bsvhu/validation/types.ts | 1 + back/src/common/validation/zod/refinement.ts | 50 ++++++++++++- back/src/common/validation/zod/schema.ts | 2 +- back/src/companies/sirenify.ts | 42 ++++++----- .../typeDefs/company.objects.graphql | 1 + back/src/forms/sirenify.ts | 23 ++++-- libs/back/object-creator/.eslintrc.json | 18 +++++ libs/back/object-creator/project.json | 54 ++++++++++++++ libs/back/object-creator/src/main.ts | 54 ++++++++++++++ libs/back/object-creator/src/objects.ts | 26 +++++++ libs/back/object-creator/tsconfig.app.json | 9 +++ libs/back/object-creator/tsconfig.json | 14 ++++ .../migration.sql | 9 +++ .../migration.sql | 2 + libs/back/prisma/src/schema.prisma | 64 ++++++++-------- 46 files changed, 727 insertions(+), 153 deletions(-) create mode 100644 libs/back/object-creator/.eslintrc.json create mode 100644 libs/back/object-creator/project.json create mode 100644 libs/back/object-creator/src/main.ts create mode 100644 libs/back/object-creator/src/objects.ts create mode 100644 libs/back/object-creator/tsconfig.app.json create mode 100644 libs/back/object-creator/tsconfig.json create mode 100644 libs/back/prisma/src/migrations/20240924172106_bsvhu_ecoorganisme/migration.sql create mode 100644 libs/back/prisma/src/migrations/20240924183924_ecoorganisme_handle_bsvhu/migration.sql diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d59b3eb1b4..3fe9285591 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -740,6 +740,12 @@ Pour palier à ce problème, il est possible de nourrir la base de donnée Prism 3.2 Dans le container `td-api`: `npx prisma db push --preview-feature` pour recréer les tables 4. Dans le container `td-api`: `npx prisma db seed --preview-feature` pour nourrir la base de données. +### Ajouter un objet spécifique dans la base de données + +Au cas où il serait nécessaire d'ajouter un objet à la base de données, vous pouvez utiliser le script "object-creator". Pour celà, modifiez le fichier `libs/back/object-creator/src/objects.ts` en ajoutant des objets en respectant le format démontré en exemple. + +Vous pouvez ensuite utiliser `npx nx run object-creator:run` et si tout se passe bien, les objets seront créés dans la base de donnée spécifiée dans la variable d'environnement "DATABASE_URL". + ### Ajouter une nouvelle icône Les icônes utilisées dans l'application front viennent de https://streamlineicons.com/. diff --git a/Changelog.md b/Changelog.md index 12114dc5a5..c1499e108c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,10 @@ et le projet suit un schéma de versionning inspiré de [Calendar Versioning](ht # [2024.10.1] 22/10/2024 +#### :rocket: Nouvelles fonctionnalités + +- Ajout d'Eco-organisme sur BSVHU [PR 3619](https://github.com/MTES-MCT/trackdechets/pull/3619) + #### :bug: Corrections de bugs - Documentation API Developers : Page Not Found, si on n'y accède pas via l'arborescence [PR 3621](https://github.com/MTES-MCT/trackdechets/pull/3621) diff --git a/back/src/__tests__/factories.ts b/back/src/__tests__/factories.ts index 742e666601..e88bf2f82e 100644 --- a/back/src/__tests__/factories.ts +++ b/back/src/__tests__/factories.ts @@ -521,20 +521,36 @@ export const applicationFactory = async (openIdEnabled?: boolean) => { export const ecoOrganismeFactory = async ({ siret, - handleBsdasri = false + handle, + createAssociatedCompany }: { siret?: string; - handleBsdasri?: boolean; + handle?: { + handleBsdasri?: boolean; + handleBsda?: boolean; + handleBsvhu?: boolean; + }; + createAssociatedCompany?: boolean; }) => { + const { handleBsdasri, handleBsda, handleBsvhu } = handle ?? {}; const ecoOrganismeIndex = (await prisma.ecoOrganisme.count()) + 1; const ecoOrganisme = await prisma.ecoOrganisme.create({ data: { address: "", name: `Eco-Organisme ${ecoOrganismeIndex}`, - siret: siret ?? siretify(ecoOrganismeIndex), - handleBsdasri + siret: siret ?? siretify(), + handleBsdasri, + handleBsda, + handleBsvhu } }); + if (createAssociatedCompany) { + // create the related company so sirenify works as expected + await companyFactory({ + siret: ecoOrganisme.siret, + name: `Eco-Organisme ${ecoOrganismeIndex}` + }); + } return ecoOrganisme; }; diff --git a/back/src/__tests__/testWorkflow.ts b/back/src/__tests__/testWorkflow.ts index 2678586d3f..4317e28461 100644 --- a/back/src/__tests__/testWorkflow.ts +++ b/back/src/__tests__/testWorkflow.ts @@ -19,7 +19,10 @@ async function testWorkflow(workflow: Workflow) { }); if (workflowCompany.companyTypes.includes("ECO_ORGANISME")) { // create ecoOrganisme to allow its user to perform api calls - await ecoOrganismeFactory({ siret: company.siret!, handleBsdasri: true }); + await ecoOrganismeFactory({ + siret: company.siret!, + handle: { handleBsdasri: true } + }); } if ( workflowCompany.companyTypes.includes("TRANSPORTER") && diff --git a/back/src/bsda/validation/refinements.ts b/back/src/bsda/validation/refinements.ts index cc0364f0d1..5398f9e8f0 100644 --- a/back/src/bsda/validation/refinements.ts +++ b/back/src/bsda/validation/refinements.ts @@ -15,6 +15,7 @@ import { Bsda, BsdaStatus, BsdaType, + BsdType, Company, CompanyType } from "@prisma/client"; @@ -26,7 +27,8 @@ import { prisma } from "@td/prisma"; import { isWorker } from "../../companies/validation"; import { isDestinationRefinement, - isNotDormantRefinement, + isEcoOrganismeRefinement, + isEmitterNotDormantRefinement, isRegisteredVatNumberRefinement, isTransporterRefinement, refineSiretAndGetCompany @@ -259,36 +261,6 @@ export const checkRequiredFields: ( }; }; -async function refineAndGetEcoOrganisme(siret: string | null | undefined, ctx) { - if (!siret) return null; - const ecoOrganisme = await prisma.ecoOrganisme.findUnique({ - where: { siret } - }); - - if (ecoOrganisme === null) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `L'éco-organisme avec le SIRET ${siret} n'est pas référencé sur Trackdéchets` - }); - } - - return ecoOrganisme; -} - -async function isBsdaEcoOrganismeRefinement( - siret: string | null | undefined, - ctx: RefinementCtx -) { - const ecoOrganisme = await refineAndGetEcoOrganisme(siret, ctx); - - if (ecoOrganisme && !ecoOrganisme?.handleBsda) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: `L'éco-organisme avec le SIRET ${siret} n'est pas autorisé à apparaitre sur un BSDA` - }); - } -} - async function checkEmitterIsNotEcoOrganisme( siret: string | null | undefined, ctx: RefinementCtx @@ -327,7 +299,7 @@ export const checkCompanies: Refinement = async ( ); }; - await isNotDormantRefinement(bsda.emitterCompanySiret, zodContext); + await isEmitterNotDormantRefinement(bsda.emitterCompanySiret, zodContext); await isDestinationRefinement( bsda.destinationCompanySiret, zodContext, @@ -355,7 +327,11 @@ export const checkCompanies: Refinement = async ( ); } await isWorkerRefinement(bsda.workerCompanySiret, zodContext); - await isBsdaEcoOrganismeRefinement(bsda.ecoOrganismeSiret, zodContext); + await isEcoOrganismeRefinement( + bsda.ecoOrganismeSiret, + BsdType.BSDA, + zodContext + ); await checkEmitterIsNotEcoOrganisme(bsda.emitterCompanySiret, zodContext); }; diff --git a/back/src/bsda/validation/sirenify.ts b/back/src/bsda/validation/sirenify.ts index 2711bb483f..fcbc0b56b0 100644 --- a/back/src/bsda/validation/sirenify.ts +++ b/back/src/bsda/validation/sirenify.ts @@ -1,6 +1,9 @@ import { ParsedZodBsda, ParsedZodBsdaTransporter } from "./schema"; import { CompanyInput } from "../../generated/graphql/types"; -import { nextBuildSirenify } from "../../companies/sirenify"; +import { + nextBuildSirenify, + NextCompanyInputAccessor +} from "../../companies/sirenify"; import { getSealedFields } from "./rules"; import { BsdaValidationContext, @@ -11,11 +14,11 @@ import { const sirenifyBsdaAccessors = ( bsda: ParsedZodBsda, sealedFields: string[] // Tranformations should not be run on sealed fields -) => [ +): NextCompanyInputAccessor[] => [ { siret: bsda?.emitterCompanySiret, skip: sealedFields.includes("emitterCompanySiret"), - setter: (input, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.emitterCompanyName = companyInput.name; input.emitterCompanyAddress = companyInput.address; } @@ -23,7 +26,7 @@ const sirenifyBsdaAccessors = ( { siret: bsda?.destinationCompanySiret, skip: sealedFields.includes("destinationCompanySiret"), - setter: (input, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.destinationCompanyName = companyInput.name; input.destinationCompanyAddress = companyInput.address; } @@ -31,7 +34,7 @@ const sirenifyBsdaAccessors = ( { siret: bsda?.workerCompanySiret, skip: sealedFields.includes("workerCompanySiret"), - setter: (input, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.workerCompanyName = companyInput.name; input.workerCompanyAddress = companyInput.address; } @@ -39,30 +42,53 @@ const sirenifyBsdaAccessors = ( { siret: bsda?.brokerCompanySiret, skip: sealedFields.includes("brokerCompanySiret"), - setter: (input, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.brokerCompanyName = companyInput.name; input.brokerCompanyAddress = companyInput.address; } }, - ...(bsda.intermediaries ?? []).map((_, idx) => ({ - siret: bsda.intermediaries![idx].siret, - skip: sealedFields.includes("intermediaries"), - setter: (input, companyInput: CompanyInput) => { - const intermediary = input.intermediaries[idx]; - intermediary.name = companyInput.name; - intermediary.address = companyInput.address; - } - })), - ...(bsda.transporters ?? []).map((_, idx) => ({ - siret: bsda.transporters![idx].transporterCompanySiret, - // FIXME skip conditionnaly based on transporter signatures - skip: false, - setter: (input, companyInput: CompanyInput) => { - const transporter = input.transporters[idx]; - transporter.transporterCompanyName = companyInput.name; - transporter.transporterCompanyAddress = companyInput.address; + { + siret: bsda?.ecoOrganismeSiret, + skip: sealedFields.includes("ecoOrganismeSiret"), + setter: (input, companyInput) => { + if (companyInput.name) { + input.ecoOrganismeName = companyInput.name; + } } - })) + }, + ...(bsda.intermediaries ?? []).map( + (_, idx) => + ({ + siret: bsda.intermediaries![idx].siret, + skip: sealedFields.includes("intermediaries"), + setter: (input, companyInput) => { + const intermediary = input.intermediaries![idx]; + if (companyInput.name) { + intermediary!.name = companyInput.name; + } + if (companyInput.address) { + intermediary!.address = companyInput.address; + } + } + } as NextCompanyInputAccessor) + ), + ...(bsda.transporters ?? []).map( + (_, idx) => + ({ + siret: bsda.transporters![idx].transporterCompanySiret, + // FIXME skip conditionnaly based on transporter signatures + skip: false, + setter: (input, companyInput) => { + const transporter = input.transporters![idx]; + if (companyInput.name) { + transporter!.transporterCompanyName = companyInput.name; + } + if (companyInput.address) { + transporter!.transporterCompanyAddress = companyInput.address; + } + } + } as NextCompanyInputAccessor) + ) ]; export const sirenifyBsda: ( diff --git a/back/src/bsdasris/resolvers/mutations/__tests__/createBsdasriEcoOrganisme.integration.ts b/back/src/bsdasris/resolvers/mutations/__tests__/createBsdasriEcoOrganisme.integration.ts index 9a5493f98e..163261ffa0 100644 --- a/back/src/bsdasris/resolvers/mutations/__tests__/createBsdasriEcoOrganisme.integration.ts +++ b/back/src/bsdasris/resolvers/mutations/__tests__/createBsdasriEcoOrganisme.integration.ts @@ -129,7 +129,9 @@ describe("Mutation.createDasri", () => { ]); }); it("create a dasri with an eco-organisme (eco-org user)", async () => { - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const { user } = await userWithCompanyFactory("MEMBER", { siret: ecoOrg.siret }); @@ -178,7 +180,9 @@ describe("Mutation.createDasri", () => { expect(data.createBsdasri.ecoOrganisme?.siret).toEqual(ecoOrg.siret); }); it("create a dasri with an eco-organisme and an unregistered emitter(eco-org user)", async () => { - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const { user } = await userWithCompanyFactory("MEMBER", { siret: ecoOrg.siret }); @@ -229,7 +233,9 @@ describe("Mutation.createDasri", () => { expect(data.createBsdasri.emitter?.company?.siret).toEqual(siret); }); it("create a dasri with an eco-organism (emitter user)", async () => { - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const { company: ecoOrgCompany } = await userWithCompanyFactory("MEMBER", { siret: ecoOrg.siret }); diff --git a/back/src/bsdasris/resolvers/mutations/__tests__/updateBsdasri.integration.ts b/back/src/bsdasris/resolvers/mutations/__tests__/updateBsdasri.integration.ts index 2d9cb7ca88..f5855a8e93 100644 --- a/back/src/bsdasris/resolvers/mutations/__tests__/updateBsdasri.integration.ts +++ b/back/src/bsdasris/resolvers/mutations/__tests__/updateBsdasri.integration.ts @@ -323,7 +323,9 @@ describe("Mutation.updateBsdasri", () => { it("should allow eco organisme fields update for INITIAL bsdasris", async () => { const { user, company } = await userWithCompanyFactory("MEMBER"); - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const { company: ecoOrgCompany } = await userWithCompanyFactory("MEMBER", { siret: ecoOrg.siret }); @@ -355,7 +357,9 @@ describe("Mutation.updateBsdasri", () => { it("should allow eco organisme fields nulling for INITIAL bsdasris", async () => { const { user, company } = await userWithCompanyFactory("MEMBER"); - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const { company: ecoOrgCompany } = await userWithCompanyFactory("MEMBER", { siret: ecoOrg.siret }); @@ -430,7 +434,9 @@ describe("Mutation.updateBsdasri", () => { }); it("should disallow eco organisme fields update after emission signature", async () => { const { user, company } = await userWithCompanyFactory("MEMBER"); - const ecoOrg = await ecoOrganismeFactory({ handleBsdasri: true }); + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsdasri: true } + }); const destination = await userWithCompanyFactory("MEMBER"); await userWithCompanyFactory("MEMBER", { diff --git a/back/src/bsdasris/sirenify.ts b/back/src/bsdasris/sirenify.ts index 24abc56877..37478152cd 100644 --- a/back/src/bsdasris/sirenify.ts +++ b/back/src/bsdasris/sirenify.ts @@ -1,5 +1,8 @@ import { Prisma } from "@prisma/client"; -import buildSirenify, { nextBuildSirenify } from "../companies/sirenify"; +import buildSirenify, { + nextBuildSirenify, + NextCompanyInputAccessor +} from "../companies/sirenify"; import { BsdasriInput, CompanyInput } from "../generated/graphql/types"; const accessors = (input: BsdasriInput) => [ @@ -31,11 +34,11 @@ export const sirenify = buildSirenify(accessors); const bsdasriCreateInputAccessors = ( input: Prisma.BsdasriCreateInput, sealedFields: string[] = [] // Tranformations should not be run on sealed fields -) => [ +): NextCompanyInputAccessor[] => [ { siret: input?.emitterCompanySiret, skip: sealedFields.includes("emitterCompanySiret"), - setter: (input: Prisma.BsdasriCreateInput, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.emitterCompanyName = companyInput.name; input.emitterCompanyAddress = companyInput.address; } @@ -43,7 +46,7 @@ const bsdasriCreateInputAccessors = ( { siret: input?.transporterCompanySiret, skip: sealedFields.includes("transporterCompanySiret"), - setter: (input: Prisma.BsdasriCreateInput, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.transporterCompanyName = companyInput.name; input.transporterCompanyAddress = companyInput.address; } @@ -51,10 +54,19 @@ const bsdasriCreateInputAccessors = ( { siret: input?.destinationCompanySiret, skip: sealedFields.includes("destinationCompanySiret"), - setter: (input: Prisma.BsdasriCreateInput, companyInput: CompanyInput) => { + setter: (input, companyInput) => { input.destinationCompanyName = companyInput.name; input.destinationCompanyAddress = companyInput.address; } + }, + { + siret: input?.ecoOrganismeSiret, + skip: sealedFields.includes("ecoOrganismeSiret"), + setter: (input, companyInput) => { + if (companyInput.name) { + input.ecoOrganismeName = companyInput.name; + } + } } ]; diff --git a/back/src/bsffs/validation/bsff/refinements.ts b/back/src/bsffs/validation/bsff/refinements.ts index 97e461a51c..6e5dcfecab 100644 --- a/back/src/bsffs/validation/bsff/refinements.ts +++ b/back/src/bsffs/validation/bsff/refinements.ts @@ -32,7 +32,7 @@ import { OPERATION } from "../../constants"; import { BsffOperationCode } from "../../../generated/graphql/types"; import { isDestinationRefinement, - isNotDormantRefinement, + isEmitterNotDormantRefinement, isRegisteredVatNumberRefinement, isTransporterRefinement } from "../../../common/validation/zod/refinement"; @@ -55,7 +55,7 @@ export const checkCompanies: Refinement = async ( bsff, zodContext ) => { - await isNotDormantRefinement(bsff.emitterCompanySiret, zodContext); + await isEmitterNotDormantRefinement(bsff.emitterCompanySiret, zodContext); await isDestinationRefinement(bsff.destinationCompanySiret, zodContext); for (const transporter of bsff.transporters ?? []) { diff --git a/back/src/bsvhu/__tests__/bsvhuEdition.test.ts b/back/src/bsvhu/__tests__/bsvhuEdition.test.ts index 482f6fd42f..29bc55ac76 100644 --- a/back/src/bsvhu/__tests__/bsvhuEdition.test.ts +++ b/back/src/bsvhu/__tests__/bsvhuEdition.test.ts @@ -1,6 +1,7 @@ import { BsvhuDestinationInput, BsvhuDestinationType, + BsvhuEcoOrganismeInput, BsvhuEmitterInput, BsvhuIdentificationInput, BsvhuInput, @@ -17,7 +18,7 @@ import { bsvhuEditionRules } from "../validation/rules"; import { graphQlInputToZodBsvhu } from "../validation/helpers"; describe("edition", () => { - test("an edition rule should be defined for every key in BsdaInput", () => { + test("an edition rule should be defined for every key in BsvhuInput", () => { // Create a dummy BSDVHU input where every possible key is present // The typing will break whenever a field is added or modified // to BsvhuInput so that we think of adding an entry to the edition rules @@ -96,6 +97,11 @@ describe("edition", () => { operation }; + const ecoOrganisme: Required = { + siret: "xxx", + name: "yyy" + }; + const input: Required = { emitter, wasteCode: "", @@ -104,7 +110,9 @@ describe("edition", () => { quantity: 1, weight, transporter, - destination + destination, + intermediaries: [company], + ecoOrganisme }; const flatInput = graphQlInputToZodBsvhu(input); for (const key of Object.keys(flatInput)) { diff --git a/back/src/bsvhu/__tests__/elastic.integration.ts b/back/src/bsvhu/__tests__/elastic.integration.ts index 4ab671918c..c622cc5391 100644 --- a/back/src/bsvhu/__tests__/elastic.integration.ts +++ b/back/src/bsvhu/__tests__/elastic.integration.ts @@ -30,6 +30,7 @@ describe("toBsdElastic > companies Names & OrgIds", () => { let elasticBsvhu: BsdElastic; let intermediary1: Company; let intermediary2: Company; + let ecoOrganisme: Company; beforeAll(async () => { // Given @@ -43,6 +44,7 @@ describe("toBsdElastic > companies Names & OrgIds", () => { }); intermediary1 = await companyFactory({ name: "Intermediaire 1" }); intermediary2 = await companyFactory({ name: "Intermediaire 2" }); + ecoOrganisme = await companyFactory({ name: "Eco organisme" }); bsvhu = await bsvhuFactory({ opt: { @@ -53,6 +55,8 @@ describe("toBsdElastic > companies Names & OrgIds", () => { transporterCompanyVatNumber: transporter.vatNumber, destinationCompanyName: destination.name, destinationCompanySiret: destination.siret, + ecoOrganismeName: ecoOrganisme.name, + ecoOrganismeSiret: ecoOrganisme.siret, intermediaries: { createMany: { data: [ @@ -75,6 +79,7 @@ describe("toBsdElastic > companies Names & OrgIds", () => { expect(elasticBsvhu.companyNames).toContain(destination.name); expect(elasticBsvhu.companyNames).toContain(intermediary1.name); expect(elasticBsvhu.companyNames).toContain(intermediary2.name); + expect(elasticBsvhu.companyNames).toContain(ecoOrganisme.name); }); test("companyOrgIds > should contain the orgIds of ALL BSVHU companies", async () => { @@ -84,5 +89,6 @@ describe("toBsdElastic > companies Names & OrgIds", () => { expect(elasticBsvhu.companyOrgIds).toContain(destination.siret); expect(elasticBsvhu.companyOrgIds).toContain(intermediary1.siret); expect(elasticBsvhu.companyOrgIds).toContain(intermediary2.siret); + expect(elasticBsvhu.companyOrgIds).toContain(ecoOrganisme.siret); }); }); diff --git a/back/src/bsvhu/__tests__/factories.vhu.ts b/back/src/bsvhu/__tests__/factories.vhu.ts index f77a861a64..c205ef7466 100644 --- a/back/src/bsvhu/__tests__/factories.vhu.ts +++ b/back/src/bsvhu/__tests__/factories.vhu.ts @@ -6,7 +6,11 @@ import { } from "@prisma/client"; import getReadableId, { ReadableIdPrefix } from "../../forms/readableId"; import { prisma } from "@td/prisma"; -import { companyFactory, siretify } from "../../__tests__/factories"; +import { + companyFactory, + ecoOrganismeFactory, + siretify +} from "../../__tests__/factories"; import { BsvhuForElastic, BsvhuForElasticInclude } from "../elastic"; export const bsvhuFactory = async ({ @@ -20,11 +24,16 @@ export const bsvhuFactory = async ({ const destinationCompany = await companyFactory({ companyTypes: ["WASTE_VEHICLES"] }); + const ecoOrganisme = await ecoOrganismeFactory({ + handle: { handleBsvhu: true }, + createAssociatedCompany: true + }); const created = await prisma.bsvhu.create({ data: { ...getVhuFormdata(), transporterCompanySiret: transporterCompany.siret, destinationCompanySiret: destinationCompany.siret, + ecoOrganismeSiret: ecoOrganisme.siret, ...opt }, include: { @@ -85,7 +94,10 @@ const getVhuFormdata = (): Prisma.BsvhuCreateInput => ({ destinationReceptionWeight: null, destinationReceptionAcceptationStatus: null, destinationReceptionRefusalReason: null, - destinationOperationCode: null + destinationOperationCode: null, + + ecoOrganismeSiret: siretify(4), + ecoOrganismeName: "Eco-Organisme" }); export const toIntermediaryCompany = (company: Company, contact = "toto") => ({ diff --git a/back/src/bsvhu/__tests__/registry.integration.ts b/back/src/bsvhu/__tests__/registry.integration.ts index e57cb1e192..46aa51a1f3 100644 --- a/back/src/bsvhu/__tests__/registry.integration.ts +++ b/back/src/bsvhu/__tests__/registry.integration.ts @@ -9,7 +9,7 @@ import { } from "../registry"; import { bsvhuFactory, toIntermediaryCompany } from "./factories.vhu"; import { resetDatabase } from "../../../integration-tests/helper"; -import { companyFactory } from "../../__tests__/factories"; +import { companyFactory, ecoOrganismeFactory } from "../../__tests__/factories"; import { RegistryBsvhuInclude } from "../../registry/elastic"; describe("toGenericWaste", () => { @@ -559,6 +559,32 @@ describe("toAllWaste", () => { expect(waste.intermediary3CompanyName).toBe(null); expect(waste.intermediary3CompanySiret).toBe(null); }); + + it("should contain ecoOrganisme infos", async () => { + // Given + const ecoOrganisme = await ecoOrganismeFactory({ + handle: { handleBsvhu: true } + }); + + const bsvhu = await bsvhuFactory({ + opt: { + ecoOrganismeSiret: ecoOrganisme.siret, + ecoOrganismeName: ecoOrganisme.name + } + }); + + // When + const bsvhuForRegistry = await prisma.bsvhu.findUniqueOrThrow({ + where: { id: bsvhu.id }, + include: RegistryBsvhuInclude + }); + const waste = toAllWaste(bsvhuForRegistry); + + // Then + expect(waste).not.toBeUndefined(); + expect(waste.ecoOrganismeSiren).toBe(ecoOrganisme.siret.substring(0, 9)); + expect(waste.ecoOrganismeName).toBe(ecoOrganisme.name); + }); }); describe("getTransportersData", () => { diff --git a/back/src/bsvhu/converter.ts b/back/src/bsvhu/converter.ts index 6909954f48..38d6b11d2a 100644 --- a/back/src/bsvhu/converter.ts +++ b/back/src/bsvhu/converter.ts @@ -22,7 +22,8 @@ import { BsvhuNextDestination, BsvhuTransport, BsvhuTransportInput, - CompanyInput + CompanyInput, + BsvhuEcoOrganisme } from "../generated/graphql/types"; import { Prisma, @@ -160,6 +161,10 @@ export function expandVhuFormFromDb(form: PrismaVhuForm): GraphqlVhuForm { takenOverAt: processDate(form.transporterTransportTakenOverAt) }) }), + ecoOrganisme: nullIfNoValues({ + name: form.ecoOrganismeName, + siret: form.ecoOrganismeSiret + }), metadata: null as any }; } @@ -169,6 +174,7 @@ export function flattenVhuInput(formInput: BsvhuInput) { ...flattenVhuEmitterInput(formInput), ...flattenVhuDestinationInput(formInput), ...flattenVhuTransporterInput(formInput), + ...flattenVhuEcoOrganismeInput(formInput), packaging: chain(formInput, f => f.packaging), wasteCode: chain(formInput, f => f.wasteCode), quantity: chain(formInput, f => f.quantity), @@ -341,6 +347,15 @@ function flattenVhuTransporterInput({ }; } +function flattenVhuEcoOrganismeInput({ + ecoOrganisme +}: Pick) { + return { + ecoOrganismeName: chain(ecoOrganisme, e => e.name), + ecoOrganismeSiret: chain(ecoOrganisme, e => e.siret) + }; +} + function flattenTransporterTransportInput( input: | { diff --git a/back/src/bsvhu/elastic.ts b/back/src/bsvhu/elastic.ts index 60745af50a..0ff61c929c 100644 --- a/back/src/bsvhu/elastic.ts +++ b/back/src/bsvhu/elastic.ts @@ -29,6 +29,7 @@ export async function getBsvhuForElastic( type ElasticSirets = { emitterCompanySiret: string | null | undefined; + ecoOrganismeSiret: string | null | undefined; destinationCompanySiret: string | null | undefined; transporterCompanySiret: string | null | undefined; intermediarySiret1?: string | null | undefined; @@ -54,6 +55,7 @@ const getBsvhuSirets = (bsvhu: BsvhuForElastic): ElasticSirets => { const bsvhuSirets: ElasticSirets = { emitterCompanySiret: bsvhu.emitterCompanySiret, destinationCompanySiret: bsvhu.destinationCompanySiret, + ecoOrganismeSiret: bsvhu.ecoOrganismeSiret, transporterCompanySiret: getTransporterCompanyOrgId(bsvhu), ...intermediarySirets }; @@ -210,8 +212,8 @@ export function toBsdElastic(bsvhu: BsvhuForElastic): BsdElastic { traderCompanySiret: "", traderCompanyAddress: "", - ecoOrganismeName: "", - ecoOrganismeSiret: "", + ecoOrganismeName: bsvhu.ecoOrganismeName ?? "", + ecoOrganismeSiret: bsvhu.ecoOrganismeSiret ?? "", nextDestinationCompanyName: bsvhu.destinationOperationNextDestinationCompanyName ?? "", @@ -247,6 +249,7 @@ export function toBsdElastic(bsvhu: BsvhuForElastic): BsdElastic { bsvhu.emitterCompanyName, bsvhu.transporterCompanyName, bsvhu.destinationCompanyName, + bsvhu.ecoOrganismeName, ...bsvhu.intermediaries.map(intermediary => intermediary.name) ] .filter(Boolean) @@ -256,6 +259,7 @@ export function toBsdElastic(bsvhu: BsvhuForElastic): BsdElastic { bsvhu.transporterCompanySiret, bsvhu.transporterCompanyVatNumber, bsvhu.destinationCompanySiret, + bsvhu.ecoOrganismeSiret, ...bsvhu.intermediaries.map(intermediary => intermediary.siret), ...bsvhu.intermediaries.map(intermediary => intermediary.vatNumber) ].filter(Boolean) diff --git a/back/src/bsvhu/pdf/components/BsvhuPdf.tsx b/back/src/bsvhu/pdf/components/BsvhuPdf.tsx index 00f8027adb..de28bfb96a 100644 --- a/back/src/bsvhu/pdf/components/BsvhuPdf.tsx +++ b/back/src/bsvhu/pdf/components/BsvhuPdf.tsx @@ -107,6 +107,16 @@ export function BsvhuPdf({ bsvhu, qrCode, renderEmpty }: Props) {

Nom de la personne à contacter : {bsvhu.emitter?.company?.contact}

+ {bsvhu?.ecoOrganisme?.siret && ( +

+ Eco-organisme désigné :{" "} +

+ Raison sociale : {bsvhu.ecoOrganisme?.name} +
+ SIREN : {bsvhu.ecoOrganisme?.siret?.substring(0, 9)} +

+

+ )} {/* End Emitter */} {/* Recipient */} diff --git a/back/src/bsvhu/permissions.ts b/back/src/bsvhu/permissions.ts index 852009b779..b73d8f1dc7 100644 --- a/back/src/bsvhu/permissions.ts +++ b/back/src/bsvhu/permissions.ts @@ -11,6 +11,7 @@ function readers(bsvhu: Bsvhu): string[] { bsvhu.destinationCompanySiret, bsvhu.transporterCompanySiret, bsvhu.transporterCompanyVatNumber, + bsvhu.ecoOrganismeSiret, ...bsvhu.intermediariesOrgIds ].filter(Boolean); } @@ -27,6 +28,8 @@ function contributors(bsvhu: Bsvhu, input?: BsvhuInput): string[] { const updateTransporterCompanySiret = input?.transporter?.company?.siret; const updateTransporterCompanyVatNumber = input?.transporter?.company?.vatNumber; + const updateEcoOrganismeCompanySiret = input?.ecoOrganisme?.siret; + const updateIntermediaries = (input?.intermediaries ?? []).flatMap(i => [ i.siret, i.vatNumber @@ -52,6 +55,10 @@ function contributors(bsvhu: Bsvhu, input?: BsvhuInput): string[] { ? updateTransporterCompanyVatNumber : bsvhu.transporterCompanyVatNumber; + const ecoOrganismeCompanySiret = + updateEcoOrganismeCompanySiret !== undefined + ? updateEcoOrganismeCompanySiret + : bsvhu.ecoOrganismeSiret; const intermediariesOrgIds = input?.intermediaries !== undefined ? updateIntermediaries @@ -62,6 +69,7 @@ function contributors(bsvhu: Bsvhu, input?: BsvhuInput): string[] { destinationCompanySiret, transporterCompanySiret, transporterCompanyVatNumber, + ecoOrganismeCompanySiret, ...intermediariesOrgIds ].filter(Boolean); } @@ -72,6 +80,7 @@ function contributors(bsvhu: Bsvhu, input?: BsvhuInput): string[] { function creators(input: BsvhuInput) { return [ input.emitter?.company?.siret, + input.ecoOrganisme?.siret, input.transporter?.company?.siret, input.transporter?.company?.vatNumber, input.destination?.company?.siret diff --git a/back/src/bsvhu/registry.ts b/back/src/bsvhu/registry.ts index fc6b3cc5e4..eb795ee62d 100644 --- a/back/src/bsvhu/registry.ts +++ b/back/src/bsvhu/registry.ts @@ -119,6 +119,10 @@ export function getRegistryFields( registryFields.isTransportedWasteFor.push(bsvhu.transporterCompanySiret); registryFields.isAllWasteFor.push(bsvhu.transporterCompanySiret); } + if (bsvhu.ecoOrganismeSiret) { + registryFields.isOutgoingWasteFor.push(bsvhu.ecoOrganismeSiret); + registryFields.isAllWasteFor.push(bsvhu.ecoOrganismeSiret); + } if (bsvhu.intermediaries?.length) { for (const intermediary of bsvhu.intermediaries) { const intermediaryOrgId = getIntermediaryCompanyOrgId(intermediary); @@ -172,8 +176,8 @@ export function toGenericWaste(bsvhu: RegistryBsvhu): GenericWaste { id: bsvhu.id, createdAt: bsvhu.createdAt, updatedAt: bsvhu.createdAt, - ecoOrganismeName: null, - ecoOrganismeSiren: null, + ecoOrganismeName: bsvhu.ecoOrganismeName, + ecoOrganismeSiren: bsvhu.ecoOrganismeSiret?.slice(0, 9), bsdType: "BSVHU", bsdSubType: getBsvhuSubType(bsvhu), status: bsvhu.status, diff --git a/back/src/bsvhu/resolvers/mutations/__tests__/createBsvhu.integration.ts b/back/src/bsvhu/resolvers/mutations/__tests__/createBsvhu.integration.ts index 3314fe114f..c28fb8d839 100644 --- a/back/src/bsvhu/resolvers/mutations/__tests__/createBsvhu.integration.ts +++ b/back/src/bsvhu/resolvers/mutations/__tests__/createBsvhu.integration.ts @@ -6,7 +6,8 @@ import { siretify, userFactory, userWithCompanyFactory, - transporterReceiptFactory + transporterReceiptFactory, + ecoOrganismeFactory } from "../../../../__tests__/factories"; import makeClient from "../../../../__tests__/testClient"; import gql from "graphql-tag"; @@ -46,6 +47,10 @@ const CREATE_VHU_FORM = gql` intermediaries { siret } + ecoOrganisme { + siret + name + } weight { value } @@ -311,6 +316,71 @@ describe("Mutation.Vhu.create", () => { ); }); + it("should create a bsvhu with eco-organisme", async () => { + const { user, company } = await userWithCompanyFactory("MEMBER"); + const destinationCompany = await companyFactory({ + companyTypes: ["WASTE_VEHICLES"] + }); + + const ecoOrganisme = await ecoOrganismeFactory({ + handle: { handleBsvhu: true }, + createAssociatedCompany: true + }); + + const input = { + emitter: { + company: { + siret: company.siret, + name: "The crusher", + address: "Rue de la carcasse", + contact: "Un centre VHU", + phone: "0101010101", + mail: "emitter@mail.com" + }, + agrementNumber: "1234" + }, + wasteCode: "16 01 06", + packaging: "UNITE", + identification: { + numbers: ["123", "456"], + type: "NUMERO_ORDRE_REGISTRE_POLICE" + }, + quantity: 2, + weight: { + isEstimate: false, + value: 1.3 + }, + destination: { + type: "BROYEUR", + plannedOperationCode: "R 12", + company: { + siret: destinationCompany.siret, + name: "destination", + address: "address", + contact: "contactEmail", + phone: "contactPhone", + mail: "contactEmail@mail.com" + }, + agrementNumber: "9876" + }, + ecoOrganisme: { + siret: ecoOrganisme.siret, + name: ecoOrganisme.name + } + }; + const { mutate } = makeClient(user); + const { data } = await mutate>( + CREATE_VHU_FORM, + { + variables: { + input + } + } + ); + expect(data.createBsvhu.id).toBeDefined(); + expect(data.createBsvhu.ecoOrganisme!.siret).toBe(ecoOrganisme.siret); + }); + it("should create a bsvhu with intermediary", async () => { const { user, company } = await userWithCompanyFactory("MEMBER"); const destinationCompany = await companyFactory({ diff --git a/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts b/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts index 832656319c..7065d80925 100644 --- a/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts +++ b/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts @@ -3,6 +3,7 @@ import { xDaysAgo } from "../../../../utils"; import { resetDatabase } from "../../../../../integration-tests/helper"; import { companyFactory, + ecoOrganismeFactory, transporterReceiptFactory, userWithCompanyFactory } from "../../../../__tests__/factories"; @@ -82,6 +83,10 @@ describe("mutaion.duplicateBsvhu", () => { const intermediary = await companyFactory(); + const ecoOrganisme = await ecoOrganismeFactory({ + handle: { handleBsvhu: true } + }); + const bsvhu = await bsvhuFactory({ opt: { emitterIrregularSituation: false, @@ -125,7 +130,9 @@ describe("mutaion.duplicateBsvhu", () => { } ] } - } + }, + ecoOrganismeSiret: ecoOrganisme.siret, + ecoOrganismeName: ecoOrganisme.name } }); const { mutate } = makeClient(emitter.user); @@ -195,6 +202,8 @@ describe("mutaion.duplicateBsvhu", () => { transporterCustomInfo, transporterTransportPlates, transporterRecepisseIsExempted, + ecoOrganismeSiret, + ecoOrganismeName, ...rest } = bsvhu; @@ -283,7 +292,9 @@ describe("mutaion.duplicateBsvhu", () => { transporterTransportTakenOverAt, transporterCustomInfo, transporterTransportPlates, - transporterRecepisseIsExempted + transporterRecepisseIsExempted, + ecoOrganismeSiret, + ecoOrganismeName }); // make sure this test breaks when a new field is added to the Bsvhu model diff --git a/back/src/bsvhu/resolvers/queries/__tests__/bsvhu.integration.ts b/back/src/bsvhu/resolvers/queries/__tests__/bsvhu.integration.ts index 9a4c4fe2d3..c3684d4e4f 100644 --- a/back/src/bsvhu/resolvers/queries/__tests__/bsvhu.integration.ts +++ b/back/src/bsvhu/resolvers/queries/__tests__/bsvhu.integration.ts @@ -40,6 +40,10 @@ query GetBsvhu($id: ID!) { number } } + ecoOrganisme { + name + siret + } weight { value } diff --git a/back/src/bsvhu/resolvers/queries/__tests__/bsvhumetadata.integration.ts b/back/src/bsvhu/resolvers/queries/__tests__/bsvhumetadata.integration.ts index 8d344bb4ea..e0d2ef78c2 100644 --- a/back/src/bsvhu/resolvers/queries/__tests__/bsvhumetadata.integration.ts +++ b/back/src/bsvhu/resolvers/queries/__tests__/bsvhumetadata.integration.ts @@ -161,6 +161,6 @@ describe("Query.Bsvhu", () => { variables: { id: bsd.id } }); - expect(data.bsvhu.metadata?.fields?.sealed?.length).toBe(58); + expect(data.bsvhu.metadata?.fields?.sealed?.length).toBe(60); }); }); diff --git a/back/src/bsvhu/typeDefs/bsvhu.inputs.graphql b/back/src/bsvhu/typeDefs/bsvhu.inputs.graphql index 172c0b01ff..619723b271 100644 --- a/back/src/bsvhu/typeDefs/bsvhu.inputs.graphql +++ b/back/src/bsvhu/typeDefs/bsvhu.inputs.graphql @@ -96,6 +96,9 @@ input BsvhuInput { Le nombre maximal d'intermédiaires sur un bordereau est de 3. """ intermediaries: [CompanyInput!] + + "Eco-organisme" + ecoOrganisme: BsvhuEcoOrganismeInput } input BsvhuEmitterInput { @@ -109,6 +112,11 @@ input BsvhuEmitterInput { company: BsvhuCompanyInput } +input BsvhuEcoOrganismeInput { + name: String! + siret: String! +} + "Extension de CompanyInput ajoutant des champs d'adresse séparés" input BsvhuCompanyInput { """ diff --git a/back/src/bsvhu/typeDefs/bsvhu.objects.graphql b/back/src/bsvhu/typeDefs/bsvhu.objects.graphql index aef6bc68d6..3106fb537a 100644 --- a/back/src/bsvhu/typeDefs/bsvhu.objects.graphql +++ b/back/src/bsvhu/typeDefs/bsvhu.objects.graphql @@ -62,6 +62,9 @@ type Bsvhu { """ intermediaries: [FormCompany!] + "Eco-organisme" + ecoOrganisme: BsvhuEcoOrganisme + metadata: BsvhuMetadata! } @@ -82,6 +85,12 @@ type BsvhuEmission { signature: Signature } +"Information sur l'éco-organisme responsable du BSVHU" +type BsvhuEcoOrganisme { + name: String! + siret: String! +} + type BsvhuTransporter { "Coordonnées de l'entreprise de transport" company: FormCompany diff --git a/back/src/bsvhu/validation/__tests__/validation.integration.ts b/back/src/bsvhu/validation/__tests__/validation.integration.ts index e8a7ca65b8..7cee23b741 100644 --- a/back/src/bsvhu/validation/__tests__/validation.integration.ts +++ b/back/src/bsvhu/validation/__tests__/validation.integration.ts @@ -1,7 +1,8 @@ -import { Company, OperationMode } from "@prisma/client"; +import { Company, EcoOrganisme, OperationMode } from "@prisma/client"; import { resetDatabase } from "../../../../integration-tests/helper"; import { companyFactory, + ecoOrganismeFactory, transporterReceiptFactory } from "../../../__tests__/factories"; import { CompanySearchResult } from "../../../companies/types"; @@ -35,6 +36,7 @@ describe("BSVHU validation", () => { let foreignTransporter: Company; let transporterCompany: Company; let intermediaryCompany: Company; + let ecoOrganisme: EcoOrganisme; beforeAll(async () => { const emitterCompany = await companyFactory({ companyTypes: ["PRODUCER"] }); transporterCompany = await companyFactory({ @@ -51,12 +53,16 @@ describe("BSVHU validation", () => { intermediaryCompany = await companyFactory({ companyTypes: ["INTERMEDIARY"] }); - + ecoOrganisme = await ecoOrganismeFactory({ + handle: { handleBsvhu: true }, + createAssociatedCompany: true + }); const prismaBsvhu = await bsvhuFactory({ opt: { emitterCompanySiret: emitterCompany.siret, transporterCompanySiret: transporterCompany.siret, destinationCompanySiret: destinationCompany.siret, + ecoOrganismeSiret: ecoOrganisme.siret, intermediaries: { create: [toIntermediaryCompany(intermediaryCompany)] } @@ -408,6 +414,30 @@ describe("BSVHU validation", () => { } }); + test("when ecoOrganisme isn't authorized for BSVHU", async () => { + const ecoOrg = await ecoOrganismeFactory({ + handle: { handleBsda: true } + }); + const data: ZodBsvhu = { + ...bsvhu, + ecoOrganismeSiret: ecoOrg.siret + }; + expect.assertions(1); + + try { + await parseBsvhuAsync(data, { + ...context, + currentSignatureType: "TRANSPORT" + }); + } catch (err) { + expect((err as ZodError).issues).toEqual([ + expect.objectContaining({ + message: `L'éco-organisme avec le SIRET ${ecoOrg.siret} n'est pas autorisé à apparaitre sur un BSVHU` + }) + ]); + } + }); + describe("Emitter transports own waste", () => { it("allowed if exemption", async () => { const data: ZodBsvhu = { @@ -577,7 +607,8 @@ describe("BSVHU validation", () => { [bsvhu.emitterCompanySiret!]: searchResult("émetteur"), [bsvhu.transporterCompanySiret!]: searchResult("transporteur"), [bsvhu.destinationCompanySiret!]: searchResult("destinataire"), - [intermediaryCompany.siret!]: searchResult("intermédiaire") + [intermediaryCompany.siret!]: searchResult("intermédiaire"), + [ecoOrganisme.siret!]: searchResult("ecoOrganisme") }; (searchCompany as jest.Mock).mockImplementation((clue: string) => { return Promise.resolve(searchResults[clue]); @@ -611,6 +642,9 @@ describe("BSVHU validation", () => { expect(sirenified.intermediaries![0].address).toEqual( searchResults[intermediaryCompany.siret!].address ); + expect(sirenified.ecoOrganismeName).toEqual( + searchResults[ecoOrganisme.siret!].name + ); }); it("should not overwrite `name` and `address` based on SIRENE data for sealed fields", async () => { const searchResults = { diff --git a/back/src/bsvhu/validation/helpers.ts b/back/src/bsvhu/validation/helpers.ts index c641503d78..46ca506bcb 100644 --- a/back/src/bsvhu/validation/helpers.ts +++ b/back/src/bsvhu/validation/helpers.ts @@ -113,7 +113,10 @@ export async function getBsvhuUserFunctions( (bsvhu.transporterCompanySiret != null && orgIds.includes(bsvhu.transporterCompanySiret)) || (bsvhu.transporterCompanyVatNumber != null && - orgIds.includes(bsvhu.transporterCompanyVatNumber)) + orgIds.includes(bsvhu.transporterCompanyVatNumber)), + isEcoOrganisme: + bsvhu.ecoOrganismeSiret != null && + orgIds.includes(bsvhu.ecoOrganismeSiret) }; } diff --git a/back/src/bsvhu/validation/refinements.ts b/back/src/bsvhu/validation/refinements.ts index acd1373602..57b4c9c75c 100644 --- a/back/src/bsvhu/validation/refinements.ts +++ b/back/src/bsvhu/validation/refinements.ts @@ -10,11 +10,12 @@ import { import { getSignatureAncestors } from "./helpers"; import { isArray } from "../../common/dataTypes"; import { capitalize } from "../../common/strings"; -import { WasteAcceptationStatus } from "@prisma/client"; +import { BsdType, WasteAcceptationStatus } from "@prisma/client"; import { destinationOperationModeRefinement, isDestinationRefinement, - isNotDormantRefinement, + isEcoOrganismeRefinement, + isEmitterNotDormantRefinement, isRegisteredVatNumberRefinement, isTransporterRefinement } from "../../common/validation/zod/refinement"; @@ -33,7 +34,7 @@ export const checkCompanies: Refinement = async ( bsvhu, zodContext ) => { - await isNotDormantRefinement(bsvhu.emitterCompanySiret, zodContext); + await isEmitterNotDormantRefinement(bsvhu.emitterCompanySiret, zodContext); await isDestinationRefinement( bsvhu.destinationCompanySiret, zodContext, @@ -51,6 +52,11 @@ export const checkCompanies: Refinement = async ( bsvhu.transporterCompanyVatNumber, zodContext ); + await isEcoOrganismeRefinement( + bsvhu.ecoOrganismeSiret, + BsdType.BSVHU, + zodContext + ); }; export const checkWeights: Refinement = ( diff --git a/back/src/bsvhu/validation/rules.ts b/back/src/bsvhu/validation/rules.ts index 1b6e5bafde..16157624e3 100644 --- a/back/src/bsvhu/validation/rules.ts +++ b/back/src/bsvhu/validation/rules.ts @@ -511,6 +511,20 @@ export const bsvhuEditionRules: BsvhuEditionRules = { // from: "TRANSPORT" // } }, + ecoOrganismeName: { + readableFieldName: "le nom de l'éco-organisme", + sealed: { from: "OPERATION" }, + path: ["ecoOrganisme", "name"], + required: { + from: "TRANSPORT", + when: bsvhu => !!bsvhu.ecoOrganismeSiret + } + }, + ecoOrganismeSiret: { + readableFieldName: "le SIRET de l'éco-organisme", + sealed: { from: "OPERATION" }, + path: ["ecoOrganisme", "siret"] + }, intermediaries: { readableFieldName: "les intermédiaires", sealed: { from: "TRANSPORT" }, diff --git a/back/src/bsvhu/validation/schema.ts b/back/src/bsvhu/validation/schema.ts index 018b1fbef8..aa6f80c9f9 100644 --- a/back/src/bsvhu/validation/schema.ts +++ b/back/src/bsvhu/validation/schema.ts @@ -182,6 +182,8 @@ const rawBsvhuSchema = z.object({ .boolean() .nullish() .transform(v => Boolean(v)), + ecoOrganismeName: z.string().nullish(), + ecoOrganismeSiret: siretSchema(CompanyRole.EcoOrganisme).nullish(), intermediaries: z .array(intermediarySchema) .nullish() diff --git a/back/src/bsvhu/validation/sirenify.ts b/back/src/bsvhu/validation/sirenify.ts index ddd01cfb7a..06bb4783ed 100644 --- a/back/src/bsvhu/validation/sirenify.ts +++ b/back/src/bsvhu/validation/sirenify.ts @@ -49,6 +49,15 @@ const sirenifyBsvhuAccessors = ( input.transporterCompanyAddress = companyInput.address; } }, + { + siret: bsvhu?.ecoOrganismeSiret, + skip: sealedFields.includes("ecoOrganismeSiret"), + setter: (input, companyInput) => { + if (companyInput.name) { + input.ecoOrganismeName = companyInput.name; + } + } + }, ...(bsvhu.intermediaries ?? []).map( (_, idx) => ({ diff --git a/back/src/bsvhu/validation/types.ts b/back/src/bsvhu/validation/types.ts index a2dfb7ecf6..c26110df02 100644 --- a/back/src/bsvhu/validation/types.ts +++ b/back/src/bsvhu/validation/types.ts @@ -7,6 +7,7 @@ export type BsvhuUserFunctions = { isEmitter: boolean; isDestination: boolean; isTransporter: boolean; + isEcoOrganisme: boolean; }; export type BsvhuValidationContext = { diff --git a/back/src/common/validation/zod/refinement.ts b/back/src/common/validation/zod/refinement.ts index f34986fd7f..6a7d5abc3c 100644 --- a/back/src/common/validation/zod/refinement.ts +++ b/back/src/common/validation/zod/refinement.ts @@ -7,7 +7,7 @@ import { isWasteVehicles } from "../../../companies/validation"; import { prisma } from "@td/prisma"; -import { Company, CompanyVerificationStatus } from "@prisma/client"; +import { BsdType, Company, CompanyVerificationStatus } from "@prisma/client"; import { getOperationModesFromOperationCode } from "../../operationModes"; import { CompanyRole, pathFromCompanyRole } from "./schema"; @@ -73,6 +73,27 @@ export async function refineSiretAndGetCompany( return company; } + +export async function refineAndGetEcoOrganisme( + siret: string | null | undefined, + ctx: RefinementCtx +) { + if (!siret) return null; + const ecoOrganisme = await prisma.ecoOrganisme.findUnique({ + where: { siret } + }); + + if (ecoOrganisme === null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["ecoOrganisme", "siret"], + message: `L'éco-organisme avec le SIRET ${siret} n'est pas référencé sur Trackdéchets` + }); + } + + return ecoOrganisme; +} + export const isRegisteredVatNumberRefinement = async ( vatNumber: string | null | undefined, ctx: RefinementCtx @@ -159,7 +180,7 @@ export async function isDestinationRefinement( } } -export async function isNotDormantRefinement( +export async function isEmitterNotDormantRefinement( siret: string | null | undefined, ctx: RefinementCtx ) { @@ -171,6 +192,7 @@ export async function isNotDormantRefinement( if (company?.isDormantSince) { ctx.addIssue({ code: z.ZodIssueCode.custom, + path: pathFromCompanyRole(CompanyRole.Emitter), message: `L'établissement avec le SIRET ${siret} est en sommeil sur Trackdéchets, il n'est pas possible de le mentionner sur un bordereau` }); } @@ -205,3 +227,27 @@ export function destinationOperationModeRefinement( } } } + +export async function isEcoOrganismeRefinement( + siret: string | null | undefined, + bsdType: BsdType, + ctx: RefinementCtx +) { + const ecoOrganisme = await refineAndGetEcoOrganisme(siret, ctx); + + if (ecoOrganisme) { + if (bsdType === BsdType.BSDA && !ecoOrganisme.handleBsda) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: pathFromCompanyRole(CompanyRole.EcoOrganisme), + message: `L'éco-organisme avec le SIRET ${siret} n'est pas autorisé à apparaitre sur un BSDA` + }); + } else if (bsdType === BsdType.BSVHU && !ecoOrganisme.handleBsvhu) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: pathFromCompanyRole(CompanyRole.EcoOrganisme), + message: `L'éco-organisme avec le SIRET ${siret} n'est pas autorisé à apparaitre sur un BSVHU` + }); + } + } +} diff --git a/back/src/common/validation/zod/schema.ts b/back/src/common/validation/zod/schema.ts index b853a23e27..35510c29e5 100644 --- a/back/src/common/validation/zod/schema.ts +++ b/back/src/common/validation/zod/schema.ts @@ -22,7 +22,7 @@ export const pathFromCompanyRole = (companyRole?: CompanyRole): string[] => { case CompanyRole.Destination: return ["destination", "company", "siret"]; case CompanyRole.EcoOrganisme: - return ["ecoOrganisme", "company", "siret"]; + return ["ecoOrganisme", "siret"]; case CompanyRole.Broker: return ["broker", "company", "siret"]; case CompanyRole.Worker: diff --git a/back/src/companies/sirenify.ts b/back/src/companies/sirenify.ts index cce4905734..92fdb87777 100644 --- a/back/src/companies/sirenify.ts +++ b/back/src/companies/sirenify.ts @@ -140,41 +140,47 @@ export function nextBuildSirenify( // check if we found a corresponding companySearchResult based on siret const companySearchResults = await Promise.all( - accessors.map(({ siret, skip }) => - !skip && siret ? searchCompanyFailFast(siret) : null - ) + accessors.map(({ siret, skip }) => { + if (skip || !siret) { + return null; + } + return searchCompanyFailFast(siret); + }) ); // make a copy to avoid mutating initial data const sirenifiedInput = { ...input }; for (const [idx, companySearchResult] of companySearchResults.entries()) { + const { setter } = accessors[idx]; + if (!companySearchResult) { + continue; + } + const company = companySearchResult as CompanySearchResult; if ( - !companySearchResult || - companySearchResult.statutDiffusionEtablissement === - ("P" as StatutDiffusionEtablissement) - ) + company.statutDiffusionEtablissement === + ("P" as StatutDiffusionEtablissement) + ) { continue; - if (companySearchResult.etatAdministratif === "F") { + } + if (company.etatAdministratif === "F") { throw new UserInputError( - `L'établissement ${companySearchResult.siret} est fermé selon le répertoire SIRENE` + `L'établissement ${company.siret} est fermé selon le répertoire SIRENE` ); } - if (companySearchResult.isDormant) { + if (company.isDormant) { throw new UserInputError( - `L'établissement ${companySearchResult.siret} est en sommeil sur Trackdéchets. Il n'est pas possible de le mentionner dans un BSD.` + `L'établissement ${company.siret} est en sommeil sur Trackdéchets. Il n'est pas possible de le mentionner dans un BSD.` ); } - const { setter } = accessors[idx]; - setter(sirenifiedInput, { - name: companySearchResult.name, - address: companySearchResult.address, - city: companySearchResult.addressCity, - postalCode: companySearchResult.addressPostalCode, - street: companySearchResult.addressVoie + name: company.name, + address: company.address, + city: company.addressCity, + postalCode: company.addressPostalCode, + street: company.addressVoie }); } diff --git a/back/src/companies/typeDefs/company.objects.graphql b/back/src/companies/typeDefs/company.objects.graphql index bb52547b40..6fce74cd92 100644 --- a/back/src/companies/typeDefs/company.objects.graphql +++ b/back/src/companies/typeDefs/company.objects.graphql @@ -496,6 +496,7 @@ type EcoOrganisme { handleBsdasri: Boolean handleBsda: Boolean + handleBsvhu: Boolean } """ diff --git a/back/src/forms/sirenify.ts b/back/src/forms/sirenify.ts index 92d98d0f48..c62846e064 100644 --- a/back/src/forms/sirenify.ts +++ b/back/src/forms/sirenify.ts @@ -1,5 +1,8 @@ import { Prisma } from "@prisma/client"; -import buildSirenify, { nextBuildSirenify } from "../companies/sirenify"; +import buildSirenify, { + nextBuildSirenify, + NextCompanyInputAccessor +} from "../companies/sirenify"; import { CompanyInput, CreateFormInput, @@ -127,7 +130,7 @@ export const sirenifyTransporterInput = buildSirenify( const formCreateInputAccessors = ( formCreateInput: Prisma.FormCreateInput, sealedFields: string[] = [] // Tranformations should not be run on sealed fields -) => [ +): NextCompanyInputAccessor[] => [ { siret: formCreateInput?.emitterCompanySiret, skip: sealedFields.includes("emitterCompanySiret"), @@ -164,7 +167,9 @@ const formCreateInputAccessors = ( siret: formCreateInput?.ecoOrganismeSiret, skip: sealedFields.includes("ecoOrganismeSiret"), setter: (formCreateInput, companyInput: CompanyInput) => { - formCreateInput.ecoOrganismeName = companyInput.name; + if (companyInput.name) { + formCreateInput.ecoOrganismeName = companyInput.name; + } } }, ...( @@ -194,10 +199,14 @@ const formCreateInputAccessors = ( )?.transporterCompanySiret || sealedFields.includes("transporterCompanySiret"), setter: (formCreateInput, companyInput: CompanyInput) => { - formCreateInput.transporters.create.transporterCompanyName = - companyInput.name; - formCreateInput.transporters.create.transporterCompanyAddress = - companyInput.address; + ( + formCreateInput.transporters + ?.create as Prisma.BsddTransporterCreateWithoutFormInput + ).transporterCompanyName = companyInput.name; + ( + formCreateInput.transporters + ?.create as Prisma.BsddTransporterCreateWithoutFormInput + ).transporterCompanyAddress = companyInput.address; } } ]; diff --git a/libs/back/object-creator/.eslintrc.json b/libs/back/object-creator/.eslintrc.json new file mode 100644 index 0000000000..3456be9b90 --- /dev/null +++ b/libs/back/object-creator/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/back/object-creator/project.json b/libs/back/object-creator/project.json new file mode 100644 index 0000000000..48f86d059a --- /dev/null +++ b/libs/back/object-creator/project.json @@ -0,0 +1,54 @@ +{ + "name": "object-creator", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/back/object-creator/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "platform": "node", + "outputPath": "dist/libs/back/object-creator", + "format": ["cjs"], + "bundle": false, + "main": "libs/back/object-creator/src/main.ts", + "tsConfig": "libs/back/object-creator/tsconfig.app.json", + "assets": ["libs/back/object-creator/src/assets"], + "generatePackageJson": true, + "esbuildOptions": { + "sourcemap": true, + "outExtension": { + ".js": ".js" + } + } + }, + "configurations": { + "development": {}, + "production": { + "esbuildOptions": { + "sourcemap": false, + "outExtension": { + ".js": ".js" + } + } + } + } + }, + "run": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "options": { + "buildTarget": "object-creator:build", + "watch": false + }, + "configurations": { + "development": { + "buildTarget": "object-creator:build:development" + } + } + } + }, + "tags": [] +} diff --git a/libs/back/object-creator/src/main.ts b/libs/back/object-creator/src/main.ts new file mode 100644 index 0000000000..d8cd53a8e9 --- /dev/null +++ b/libs/back/object-creator/src/main.ts @@ -0,0 +1,54 @@ +import { unescape } from "node:querystring"; +import objects from "./objects"; +import { PrismaClient } from "@prisma/client"; + +const { DATABASE_URL } = process.env; + +/* + Database clients init +*/ + +if (!DATABASE_URL) { + throw new Error("DATABASE_URL is not defined"); +} + +function getDbUrlWithSchema(rawDatabaseUrl: string) { + try { + const dbUrl = new URL(rawDatabaseUrl); + dbUrl.searchParams.set("schema", "default$default"); + + return unescape(dbUrl.href); // unescape needed because of the `$` + } catch (err) { + return ""; + } +} + +const prisma = new PrismaClient({ + datasources: { + db: { url: getDbUrlWithSchema(DATABASE_URL) } + }, + log: [] +}); + +/* + The main Run method +*/ +const run = async () => { + for (let index = 0; index < objects.length; index++) { + try { + const newObj = objects[index]; + await prisma[newObj.type].create({ + data: newObj.object + }); + console.log(`saved object ${index + 1}`); + } catch (error) { + console.error(error); + } + } + + console.log( + "ALL DONE ! remember to reindex to elastic if needed ( > npx nx run back:reindex-all-bsds-bulk -- -f )" + ); +}; + +run(); diff --git a/libs/back/object-creator/src/objects.ts b/libs/back/object-creator/src/objects.ts new file mode 100644 index 0000000000..a42bc99a84 --- /dev/null +++ b/libs/back/object-creator/src/objects.ts @@ -0,0 +1,26 @@ +import { PrismaClient, Prisma } from "@prisma/client"; + +type DataType = Prisma.Args< + PrismaClient[M], + "create" +>["data"]; + +type CreationObject = { + type: M; + object: DataType; +}; +const objects = [ + { + type: "ecoOrganisme", + object: { + siret: "00000000000013", + name: "Mon éco-organisme", + address: "12 RUE DES PINSONS 75012 PARIS", + handleBsdasri: false, + handleBsda: false, + handleBsvhu: true + } + } as CreationObject<"ecoOrganisme"> +]; + +export default objects; diff --git a/libs/back/object-creator/tsconfig.app.json b/libs/back/object-creator/tsconfig.app.json new file mode 100644 index 0000000000..762205a8de --- /dev/null +++ b/libs/back/object-creator/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/libs/back/object-creator/tsconfig.json b/libs/back/object-creator/tsconfig.json new file mode 100644 index 0000000000..089e304e98 --- /dev/null +++ b/libs/back/object-creator/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ], + "compilerOptions": { + "esModuleInterop": true, + "noErrorTruncation": true + } +} diff --git a/libs/back/prisma/src/migrations/20240924172106_bsvhu_ecoorganisme/migration.sql b/libs/back/prisma/src/migrations/20240924172106_bsvhu_ecoorganisme/migration.sql new file mode 100644 index 0000000000..5b431af16e --- /dev/null +++ b/libs/back/prisma/src/migrations/20240924172106_bsvhu_ecoorganisme/migration.sql @@ -0,0 +1,9 @@ +-- AlterTable +ALTER TABLE "Bsvhu" ADD COLUMN "ecoOrganismeName" TEXT, +ADD COLUMN "ecoOrganismeSiret" TEXT; + +-- CreateIndex +CREATE INDEX "_BsdaEcoOrganismeSiretIdx" ON "Bsda"("ecoOrganismeSiret"); + +-- CreateIndex +CREATE INDEX "_BsvhuEcoOrganismeSiretIdx" ON "Bsvhu"("ecoOrganismeSiret"); diff --git a/libs/back/prisma/src/migrations/20240924183924_ecoorganisme_handle_bsvhu/migration.sql b/libs/back/prisma/src/migrations/20240924183924_ecoorganisme_handle_bsvhu/migration.sql new file mode 100644 index 0000000000..acb15efba7 --- /dev/null +++ b/libs/back/prisma/src/migrations/20240924183924_ecoorganisme_handle_bsvhu/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EcoOrganisme" ADD COLUMN "handleBsvhu" BOOLEAN NOT NULL DEFAULT false; diff --git a/libs/back/prisma/src/schema.prisma b/libs/back/prisma/src/schema.prisma index 81a46eda36..89c5e6859a 100644 --- a/libs/back/prisma/src/schema.prisma +++ b/libs/back/prisma/src/schema.prisma @@ -392,6 +392,7 @@ model EcoOrganisme { address String handleBsdasri Boolean @default(false) handleBsda Boolean @default(false) + handleBsvhu Boolean @default(false) } // Permet de faire le lien entre un bordereau "initial" et le ou @@ -422,14 +423,14 @@ model BsddFinalOperation { } model Form { - id String @id @default(cuid()) @db.VarChar(30) - rowNumber Int @unique(map: "Form_rowNumber_ukey") @default(autoincrement()) - createdAt DateTime @default(now()) @db.Timestamptz(6) - updatedAt DateTime @updatedAt @db.Timestamptz(6) - readableId String @unique(map: "Form.readableId._UNIQUE") + id String @id @default(cuid()) @db.VarChar(30) + rowNumber Int @unique(map: "Form_rowNumber_ukey") @default(autoincrement()) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + readableId String @unique(map: "Form.readableId._UNIQUE") customId String? - isDeleted Boolean? @default(false) - status Status @default(DRAFT) + isDeleted Boolean? @default(false) + status Status @default(DRAFT) emitterType EmitterType? emitterPickupSite String? emitterCompanyName String? @@ -443,8 +444,8 @@ model Form { emitterWorkSiteCity String? emitterWorkSitePostalCode String? emitterWorkSiteInfos String? - emitterIsPrivateIndividual Boolean? @default(false) - emitterIsForeignShip Boolean? @default(false) + emitterIsPrivateIndividual Boolean? @default(false) + emitterIsForeignShip Boolean? @default(false) emitterCompanyOmiNumber String? recipientCap String? recipientProcessingOperation String? @@ -454,18 +455,18 @@ model Form { recipientCompanyContact String? recipientCompanyPhone String? recipientCompanyMail String? - recipientIsTempStorage Boolean? @default(false) + recipientIsTempStorage Boolean? @default(false) wasteDetailsCode String? wasteDetailsName String? wasteDetailsOnuCode String? - wasteDetailsQuantity Decimal? @db.Decimal(65, 30) + wasteDetailsQuantity Decimal? @db.Decimal(65, 30) wasteDetailsQuantityType QuantityType? wasteDetailsConsistence Consistence? - wasteDetailsPackagingInfos Json @default("[]") - wasteDetailsPop Boolean @default(false) + wasteDetailsPackagingInfos Json @default("[]") + wasteDetailsPop Boolean @default(false) wasteDetailsSampleNumber String? - wasteDetailsIsDangerous Boolean @default(false) - wasteDetailsParcelNumbers Json? @default("[]") + wasteDetailsIsDangerous Boolean @default(false) + wasteDetailsParcelNumbers Json? @default("[]") wasteDetailsAnalysisReferences String[] wasteDetailsLandIdentifiers String[] traderCompanyName String? @@ -476,7 +477,7 @@ model Form { traderCompanyMail String? traderReceipt String? traderDepartment String? - traderValidityLimit DateTime? @db.Timestamptz(6) + traderValidityLimit DateTime? @db.Timestamptz(6) ecoOrganismeName String? ecoOrganismeSiret String? brokerCompanyName String? @@ -487,7 +488,7 @@ model Form { brokerCompanyMail String? brokerReceipt String? brokerDepartment String? - brokerValidityLimit DateTime? @db.Timestamptz(6) + brokerValidityLimit DateTime? @db.Timestamptz(6) nextDestinationProcessingOperation String? nextDestinationCompanyName String? nextDestinationCompanySiret String? @@ -496,31 +497,31 @@ model Form { nextDestinationCompanyPhone String? nextDestinationCompanyMail String? nextDestinationCompanyCountry String? - nextDestinationCompanyVatNumber String? @db.VarChar(30) + nextDestinationCompanyVatNumber String? @db.VarChar(30) nextDestinationNotificationNumber String? nextDestinationCompanyExtraEuropeanId String? nextTransporterOrgId String? - emittedAt DateTime? @db.Timestamptz(6) + emittedAt DateTime? @db.Timestamptz(6) emittedBy String? emittedByEcoOrganisme Boolean? - takenOverAt DateTime? @db.Timestamptz(6) + takenOverAt DateTime? @db.Timestamptz(6) takenOverBy String? - signedAt DateTime? @db.Timestamptz(6) + signedAt DateTime? @db.Timestamptz(6) signedBy String? - isImportedFromPaper Boolean @default(false) + isImportedFromPaper Boolean @default(false) quantityReceivedType QuantityType? signedByTransporter Boolean? - sentAt DateTime? @db.Timestamptz(6) + sentAt DateTime? @db.Timestamptz(6) sentBy String? - isAccepted Boolean? @default(false) + isAccepted Boolean? @default(false) wasteAcceptationStatus WasteAcceptationStatus? wasteRefusalReason String? receivedBy String? - receivedAt DateTime? @db.Timestamptz(6) - quantityReceived Decimal? @db.Decimal(65, 30) - quantityRefused Decimal? @db.Decimal(65, 30) + receivedAt DateTime? @db.Timestamptz(6) + quantityReceived Decimal? @db.Decimal(65, 30) + quantityRefused Decimal? @db.Decimal(65, 30) processedBy String? - processedAt DateTime? @db.Timestamptz(6) + processedAt DateTime? @db.Timestamptz(6) processingOperationDone String? processingOperationDescription String? noTraceability Boolean? @@ -1064,6 +1065,9 @@ model Bsvhu { transporterTransportPlates String[] transporterRecepisseIsExempted Boolean? + ecoOrganismeName String? + ecoOrganismeSiret String? + intermediaries IntermediaryBsvhuAssociation[] // Denormalized fields, storing sirets to speed up queries and avoid expensive joins @@ -1075,6 +1079,7 @@ model Bsvhu { @@index([destinationCompanySiret], map: "_BsvhuDestinationCompanySiretIdx") @@index([transporterCompanySiret], map: "_BsvhuTransporterCompanySiretIdx") @@index([transporterCompanyVatNumber], map: "_BsvhuTransporterCompanyVatNumberIdx") + @@index([ecoOrganismeSiret], map: "_BsvhuEcoOrganismeSiretIdx") @@index([intermediariesOrgIds], map: "_BsvhuIntermediariesOrgIdsIdx", type: Gin) @@index([status], map: "_BsvhuStatusIdx") @@index([updatedAt], map: "_BsvhuUpdatedAtIdx") @@ -1645,6 +1650,7 @@ model Bsda { @@index([brokerCompanySiret], map: "_BsdaBrokerCompanySiretIdx") @@index([destinationCompanySiret], map: "_BsdaDestinationCompanySiretIdx") @@index([workerCompanySiret], map: "_BsdaWorkerCompanySiretIdx") + @@index([ecoOrganismeSiret], map: "_BsdaEcoOrganismeSiretIdx") @@index([destinationOperationNextDestinationCompanySiret], map: "_BsdaDestinationOperationNextDestinationCompanySiretIdx") @@index([status], map: "_BsdaStatusIdx") @@index([groupedInId], map: "_BsdaGroupedInIdIdx") @@ -2141,4 +2147,4 @@ enum EmptyReturnADR { EMPTY_RETURN_NOT_WASHED EMPTY_VEHICLE EMPTY_CITERNE -} \ No newline at end of file +}