diff --git a/Changelog.md b/Changelog.md index 2332848c4f..f1e8d9f2ea 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ et le projet suit un schéma de versionning inspiré de [Calendar Versioning](ht #### :nail_care: Améliorations - Permettre au transporteur étranger d'avoir les mêmes droits qu'un transporteur FR concernant la révision sur une Annexe 1 [PR 3770](https://github.com/MTES-MCT/trackdechets/pull/3770) +- Remonter le VHU en situation irrégulière (sans émetteur TD) dans l'onglet À collecter du transporteur [PR 3792](https://github.com/MTES-MCT/trackdechets/pull/3792) #### :bug: Corrections de bugs diff --git a/back/src/bsds/resolvers/mutations/utils/clone.utils.ts b/back/src/bsds/resolvers/mutations/utils/clone.utils.ts index d77a9c5e34..3dd7920740 100644 --- a/back/src/bsds/resolvers/mutations/utils/clone.utils.ts +++ b/back/src/bsds/resolvers/mutations/utils/clone.utils.ts @@ -602,6 +602,7 @@ export const cloneBsvhu = async (user: Express.User, id: string) => { emitterEmissionSignatureDate: bsvhu.emitterEmissionSignatureDate, emitterIrregularSituation: bsvhu.emitterIrregularSituation, emitterNoSiret: bsvhu.emitterNoSiret, + emitterNotOnTD: bsvhu.emitterNotOnTD, identificationNumbers: bsvhu.identificationNumbers, identificationType: bsvhu.identificationType, intermediaries: bsvhu.intermediaries.length diff --git a/back/src/bsvhu/elastic.ts b/back/src/bsvhu/elastic.ts index d522a15ce2..1a8c83ff9d 100644 --- a/back/src/bsvhu/elastic.ts +++ b/back/src/bsvhu/elastic.ts @@ -131,6 +131,14 @@ export function getWhere(bsvhu: BsvhuForElastic): Pick { for (const fieldName of siretsFilters.keys()) { setTab(siretsFilters, fieldName, "isDraftFor"); } + } else if (bsvhu.emitterNotOnTD) { + // even though the emitter is not on TD, we still add the SIRET to the index + // so if the account is created after the BSVHU, it will still appear in the + // emitter's dashboard. + setTab(siretsFilters, "emitterCompanySiret", "isForActionFor"); + setTab(siretsFilters, "transporterCompanySiret", "isToCollectFor"); + } else if (bsvhu.emitterNoSiret) { + setTab(siretsFilters, "transporterCompanySiret", "isToCollectFor"); } else { setTab(siretsFilters, "emitterCompanySiret", "isForActionFor"); setTab(siretsFilters, "transporterCompanySiret", "isFollowFor"); diff --git a/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts b/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts index 84f488ff01..06df622764 100644 --- a/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts +++ b/back/src/bsvhu/resolvers/mutations/__tests__/duplicateBsvhu.integration.ts @@ -123,6 +123,7 @@ describe("mutaion.duplicateBsvhu", () => { opt: { emitterIrregularSituation: false, emitterNoSiret: false, + emitterNotOnTD: false, emitterCompanySiret: emitter.company.siret, emitterCompanyName: emitter.company.name, emitterCompanyAddress: emitter.company.address, @@ -185,6 +186,14 @@ describe("mutaion.duplicateBsvhu", () => { traderRecepisseValidityLimit: traderReceipt.validityLimit } }); + + const searchResults = { + [emitter.company.siret!]: emitter.company + }; + + (searchCompany as jest.Mock).mockImplementation((clue: string) => { + return Promise.resolve(searchResults[clue]); + }); const { mutate } = makeClient(emitter.user); const { errors, data } = await mutate>( @@ -202,6 +211,7 @@ describe("mutaion.duplicateBsvhu", () => { const { emitterIrregularSituation, emitterNoSiret, + emitterNotOnTD, emitterAgrementNumber, emitterCompanyName, emitterCompanySiret, @@ -313,6 +323,7 @@ describe("mutaion.duplicateBsvhu", () => { expect(duplicatedBsvhu).toMatchObject({ emitterIrregularSituation, emitterNoSiret, + emitterNotOnTD, emitterAgrementNumber, emitterCompanyName, emitterCompanySiret, diff --git a/back/src/bsvhu/validation/rules.ts b/back/src/bsvhu/validation/rules.ts index 9e3eee7b22..b1b07e6df2 100644 --- a/back/src/bsvhu/validation/rules.ts +++ b/back/src/bsvhu/validation/rules.ts @@ -21,6 +21,7 @@ export type BsvhuEditableFields = Required< | "isDraft" | "isDeleted" | "emitterCustomInfo" + | "emitterNotOnTD" | "destinationCustomInfo" | "transporterCustomInfo" | "transporterTransportPlates" diff --git a/back/src/bsvhu/validation/schema.ts b/back/src/bsvhu/validation/schema.ts index f1bb6560a8..8fc01b8498 100644 --- a/back/src/bsvhu/validation/schema.ts +++ b/back/src/bsvhu/validation/schema.ts @@ -88,6 +88,10 @@ const rawBsvhuSchema = z.object({ .boolean() .nullish() .transform(v => Boolean(v)), + emitterNotOnTD: z.coerce + .boolean() + .nullish() + .transform(v => Boolean(v)), emitterCompanyName: z.string().nullish(), emitterCompanySiret: siretSchema(CompanyRole.Emitter).nullish(), emitterCompanyAddress: z.string().nullish(), diff --git a/back/src/bsvhu/validation/sirenify.ts b/back/src/bsvhu/validation/sirenify.ts index 32a800981f..1d0ed82256 100644 --- a/back/src/bsvhu/validation/sirenify.ts +++ b/back/src/bsvhu/validation/sirenify.ts @@ -12,6 +12,9 @@ const sirenifyBsvhuAccessors = ( { siret: bsvhu?.emitterCompanySiret, skip: sealedFields.includes("emitterCompanySiret") || bsvhu.emitterNoSiret, + setterIfNotFound: input => { + input.emitterNotOnTD = true; + }, setter: (input, companyInput) => { input.emitterCompanyName = companyInput.name; input.emitterCompanyAddress = companyInput.address; diff --git a/back/src/companies/sirenify.ts b/back/src/companies/sirenify.ts index 92fdb87777..7820e75861 100644 --- a/back/src/companies/sirenify.ts +++ b/back/src/companies/sirenify.ts @@ -117,6 +117,7 @@ export default function buildSirenify( export type NextCompanyInputAccessor = { siret: string | null | undefined; skip: boolean; + setterIfNotFound?: (input: T) => void; setter: ( input: T, data: { @@ -152,8 +153,11 @@ export function nextBuildSirenify( const sirenifiedInput = { ...input }; for (const [idx, companySearchResult] of companySearchResults.entries()) { - const { setter } = accessors[idx]; + const { setter, setterIfNotFound } = accessors[idx]; if (!companySearchResult) { + if (setterIfNotFound) { + setterIfNotFound(sirenifiedInput); + } continue; } const company = companySearchResult as CompanySearchResult; diff --git a/libs/back/prisma/src/migrations/20241128180833_bsvhu_emitter_not_on_td/migration.sql b/libs/back/prisma/src/migrations/20241128180833_bsvhu_emitter_not_on_td/migration.sql new file mode 100644 index 0000000000..844881bef2 --- /dev/null +++ b/libs/back/prisma/src/migrations/20241128180833_bsvhu_emitter_not_on_td/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Bsvhu" ADD COLUMN "emitterNotOnTD" BOOLEAN DEFAULT false; diff --git a/libs/back/prisma/src/schema.prisma b/libs/back/prisma/src/schema.prisma index 16de65cd49..6a66f631c5 100644 --- a/libs/back/prisma/src/schema.prisma +++ b/libs/back/prisma/src/schema.prisma @@ -1012,6 +1012,7 @@ model Bsvhu { emitterIrregularSituation Boolean? @default(false) emitterNoSiret Boolean? @default(false) + emitterNotOnTD Boolean? @default(false) emitterAgrementNumber String? @db.VarChar(100) emitterCompanyName String? emitterCompanySiret String? @db.VarChar(17) diff --git a/libs/back/scripts/src/scripts/20241128182942148_bsvhu_emitter_not_on_td.ts b/libs/back/scripts/src/scripts/20241128182942148_bsvhu_emitter_not_on_td.ts new file mode 100644 index 0000000000..44ee022c87 --- /dev/null +++ b/libs/back/scripts/src/scripts/20241128182942148_bsvhu_emitter_not_on_td.ts @@ -0,0 +1,108 @@ +import { Bsvhu, BsvhuStatus } from "@prisma/client"; +import { prisma } from "@td/prisma"; +import { logger } from "@td/logger"; +import Queue, { JobOptions } from "bull"; +import { searchCompanyFailFast } from "back/src/companies/sirenify"; + +const { REDIS_URL, NODE_ENV } = process.env; + +const INDEX_QUEUE_NAME = `queue_index_elastic_${NODE_ENV}`; +const indexQueue = new Queue(INDEX_QUEUE_NAME, REDIS_URL!, { + defaultJobOptions: { + attempts: 3, + backoff: { type: "fixed", delay: 100 }, + removeOnComplete: 10_000, + timeout: 10000 + } +}); + +async function enqueueUpdatedBsdToIndex( + bsdId: string, + options?: JobOptions +): Promise { + logger.info(`Enqueuing BSD ${bsdId} for indexation`); + await indexQueue.add("index_updated", bsdId, options); +} + +export async function run() { + logger.info("starting BSVHU emitter not on TD script"); + let finished = false; + let lastId: string | null = null; + + while (!finished) { + let bsvhus: Bsvhu[] = []; + try { + bsvhus = await prisma.bsvhu.findMany({ + take: 10, + ...(lastId + ? { + cursor: { + id: lastId + }, + skip: 1 // Skip the cursor + } + : {}), + where: { + AND: [ + { + isDraft: false + }, + { + emitterIrregularSituation: true + }, + { + status: BsvhuStatus.INITIAL + }, + { + NOT: { + isDeleted: true + } + }, + { + NOT: { + emitterNoSiret: true + } + } + ] + }, + orderBy: { + id: "asc" + } + }); + } catch (error) { + logger.error(`failed to fetch bsvhus from cursor ${lastId}`); + logger.error(error); + break; + } + logger.info(`got BSVHUs ${bsvhus.map(bsvhu => bsvhu.id).join(", ")}`); + if (bsvhus.length < 10) { + finished = true; + } + if (bsvhus.length === 0) { + break; + } + lastId = bsvhus[bsvhus.length - 1].id; + for (const bsvhu of bsvhus) { + logger.info(`handling ${bsvhu.id}`); + if (bsvhu.emitterCompanySiret) { + const emitterCompany = await searchCompanyFailFast( + bsvhu.emitterCompanySiret + ); + if (!emitterCompany) { + logger.info(`updating ${bsvhu.id}`); + await prisma.bsvhu.update({ + where: { id: bsvhu.id }, + data: { + emitterNotOnTD: true + }, + select: { + id: true + } + }); + enqueueUpdatedBsdToIndex(bsvhu.id); + } + logger.info(`handled ${bsvhu.id}`); + } + } + } +}