Skip to content

Commit

Permalink
[TRA-14724] Ajout d'un nouvel onglet "Retours" pour les transporteurs (
Browse files Browse the repository at this point in the history
…#3669)

* feat: started adding tab + implementation for BSDD

* feat: added lots of integration tests for all bsds

* feat: displaying road_control button

* feat: improved blank state

* fix: fixed typing

* feat: added cron job to clean up outdated BSDs

* feat: added tests for tab cleanup

* fix: fixed test

* feat: created new env var for cron job

* fix: fixed test

* feat: added test for PDF generation

* fix: trying to fix errors in build-test in CI

* fix: not using tabs with unicity constraint

* fix: fixed integration tests

* fix: renamed cron env var
  • Loading branch information
GaelFerrand authored Oct 17, 2024
1 parent 62f43c9 commit 480a090
Show file tree
Hide file tree
Showing 48 changed files with 1,652 additions and 54 deletions.
3 changes: 3 additions & 0 deletions .env.model
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ VERIFIED_FOREIGN_TRANSPORTER_COMPANY_TEMPLATE_ID=*********
# Prefer minutes different from 0 to avoid traffic congestion
CRON_ONBOARDING_SCHEDULE=8 8 * * *

# Daily cron job to cleanup isReturnFor tab
CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE=0 2 * * *

# SendInBlue configuration
SIB_BASE_URL=********
SIB_APIKEY=********
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ jobs:
TD_COMPANY_ELASTICSEARCH_URL: ${{ secrets.TD_COMPANY_ELASTICSEARCH_URL }}
TD_COMPANY_ELASTICSEARCH_CACERT: ${{ secrets.TD_COMPANY_ELASTICSEARCH_CACERT }}
OTEL_SDK_DISABLED: true
ELASTIC_SEARCH_URL: http://elasticsearch:9200
- run: npx nx affected -t build --parallel=3
1 change: 1 addition & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ env:

# Misc
CRON_ONBOARDING_SCHEDULE: false
CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE: false
VERIFY_COMPANY: false
ALLOW_TEST_COMPANY: true
VITE_ALLOW_TEST_COMPANY: true
Expand Down
2 changes: 1 addition & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,7 @@ et le projet suit un schéma de versionning inspiré de [Calendar Versioning](ht
#### :house: Interne

- Meilleure gestion des ré-indexations de BSD sans interruption du service avec `npm run reindex-all-bsds-bulk` et parallélisation avec la job queue avec l'option `--useQueue` [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
- Création de nouveau jobs `indexAllInBulk` et `indexChunk` pour la queue d'indexation, , création d'un groupe de workers de job spécifiques pour l'indexation `indexQueue` [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
- Création de nouveau jobs `indexAllInBulk` et `indexChunk` pour la queue d'indexation, création d'un groupe de workers de job spécifiques pour l'indexation `indexQueue` [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
- Refonte d'un script de reindexation partielle avec interruption du service `npm run reindex-partial-in-place` avec une demande de confirmation dans la console [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
- Création d'un nouveau script pour vider de force une queue par son nom `npm run queue:obliterate` [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
- Suppression du script `bin/indexElasticSearch.ts` au profit des scripts `reindex*.ts` [PR1706](https://github.com/MTES-MCT/trackdechets/pull/1706).
Expand Down
14 changes: 6 additions & 8 deletions apps/cron/src/cron.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { validateOnbardingCronSchedule } from "./main";
import { validateDailyCronSchedule } from "./main";

jest.mock("back", () => ({
initSentry: jest.fn()
Expand All @@ -8,25 +8,23 @@ jest.mock("./commands/appendix1.helpers", () => ({
cleanUnusedAppendix1ProducerBsdds: jest.fn(() => Promise.resolve())
}));

describe("validateOnbardingCronSchedule", () => {
describe("validateDailyCronSchedule", () => {
it("should throw error when invalid quartz expression", () => {
expect(() => validateOnbardingCronSchedule("80 8 * * *")).toThrow(
expect(() => validateDailyCronSchedule("80 8 * * *")).toThrow(
"Invalid CRON expression : 80 8 * * *"
);
});

it("should throw an error if valid CRON expression but not set to run once every day", () => {
// At 00:00 on day-of-month 1.
const everyFistOfMonthAtMidnight = "* * 1 * *";
expect(() =>
validateOnbardingCronSchedule(everyFistOfMonthAtMidnight)
).toThrow(
expect(() => validateDailyCronSchedule(everyFistOfMonthAtMidnight)).toThrow(
"CRON expression should be set to run once every day : {m} {h} * * *"
);
});

it("should return true for valid expression", () => {
expect(validateOnbardingCronSchedule("8 8 * * *")).toEqual(true);
expect(validateOnbardingCronSchedule("08 08 * * *")).toEqual(true);
expect(validateDailyCronSchedule("8 8 * * *")).toEqual(true);
expect(validateDailyCronSchedule("08 08 * * *")).toEqual(true);
});
});
25 changes: 21 additions & 4 deletions apps/cron/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as cron from "cron";
import cronValidator from "cron-validate";
import { initSentry } from "back";
import { cleanUpIsReturnForTab, initSentry } from "back";
import {
sendMembershipRequestDetailsEmail,
sendPendingMembershipRequestDetailsEmail,
Expand All @@ -10,7 +10,8 @@ import {
} from "./commands/onboarding.helpers";
import { cleanAppendix1 } from "./commands/appendix1.helpers";

const { CRON_ONBOARDING_SCHEDULE, TZ } = process.env;
const { CRON_ONBOARDING_SCHEDULE, CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE, TZ } =
process.env;

let jobs: cron.CronJob[] = [
new cron.CronJob({
Expand All @@ -23,7 +24,7 @@ let jobs: cron.CronJob[] = [
];

if (CRON_ONBOARDING_SCHEDULE) {
validateOnbardingCronSchedule(CRON_ONBOARDING_SCHEDULE);
validateDailyCronSchedule(CRON_ONBOARDING_SCHEDULE);

jobs = [
...jobs,
Expand Down Expand Up @@ -70,6 +71,22 @@ if (CRON_ONBOARDING_SCHEDULE) {
];
}

if (CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE) {
validateDailyCronSchedule(CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE);

jobs = [
...jobs,
// cleanup the isReturnFor tab
new cron.CronJob({
cronTime: CRON_CLEANUP_IS_RETURN_TAB_SCHEDULE,
onTick: async () => {
await cleanUpIsReturnForTab();
},
timeZone: TZ
})
];
}

const Sentry = initSentry();

if (Sentry) {
Expand All @@ -88,7 +105,7 @@ if (Sentry) {

jobs.forEach(job => job.start());

export function validateOnbardingCronSchedule(cronExp: string) {
export function validateDailyCronSchedule(cronExp: string) {
// checks it is a valid cron quartz expression
const isValid = cronValidator(cronExp).isValid();

Expand Down
103 changes: 102 additions & 1 deletion back/src/bsda/__tests__/elastic.integration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Company } from "@prisma/client";
import { BsdaStatus, Company, WasteAcceptationStatus } from "@prisma/client";
import { resetDatabase } from "../../../integration-tests/helper";
import { prisma } from "@td/prisma";
import { companyFactory } from "../../__tests__/factories";
import { getBsdaForElastic, toBsdElastic } from "../elastic";
import { BsdElastic } from "../../common/elastic";
import { bsdaFactory } from "./factories";
import { xDaysAgo } from "../../utils";

describe("toBsdElastic > companies Names & OrgIds", () => {
afterEach(resetDatabase);
Expand Down Expand Up @@ -112,4 +113,104 @@ describe("toBsdElastic > companies Names & OrgIds", () => {
expect(elasticBsda.companyOrgIds).toContain(intermediary1.siret);
expect(elasticBsda.companyOrgIds).toContain(intermediary2.siret);
});

describe("isReturnFor", () => {
it.each([
WasteAcceptationStatus.REFUSED,
WasteAcceptationStatus.PARTIALLY_REFUSED
])(
"waste acceptation status is %p > bsda should belong to tab",
async destinationReceptionAcceptationStatus => {
// Given
const transporter = await companyFactory();
const bsda = await bsdaFactory({
opt: {
destinationReceptionDate: new Date(),
destinationReceptionAcceptationStatus
},
transporterOpt: {
transporterCompanyName: transporter.name,
transporterCompanySiret: transporter.siret,
transporterCompanyVatNumber: transporter.vatNumber
}
});

// When
const bsdaForElastic = await getBsdaForElastic(bsda);
const elasticBsda = toBsdElastic(bsdaForElastic);

// Then
expect(elasticBsda.isReturnFor).toContain(transporter?.siret);
}
);

it("status is REFUSED > bsda should belong to tab", async () => {
// Given
const transporter = await companyFactory();
const bsda = await bsdaFactory({
opt: {
destinationReceptionDate: new Date(),
status: BsdaStatus.REFUSED
},
transporterOpt: {
transporterCompanyName: transporter.name,
transporterCompanySiret: transporter.siret,
transporterCompanyVatNumber: transporter.vatNumber
}
});

// When
const bsdaForElastic = await getBsdaForElastic(bsda);
const elasticBsda = toBsdElastic(bsdaForElastic);

// Then
expect(elasticBsda.isReturnFor).toContain(transporter?.siret);
});

it("waste acceptation status is ACCEPTED > bsda should not belong to tab", async () => {
// Given
const transporter = await companyFactory();
const bsda = await bsdaFactory({
opt: {
destinationReceptionDate: new Date(),
destinationReceptionAcceptationStatus: WasteAcceptationStatus.ACCEPTED
},
transporterOpt: {
transporterCompanyName: transporter.name,
transporterCompanySiret: transporter.siret,
transporterCompanyVatNumber: transporter.vatNumber
}
});

// When
const bsdaForElastic = await getBsdaForElastic(bsda);
const elasticBsda = toBsdElastic(bsdaForElastic);

// Then
expect(elasticBsda.isReturnFor).toStrictEqual([]);
});

it("bsda has been received too long ago > should not belong to tab", async () => {
// Given
const transporter = await companyFactory();
const bsda = await bsdaFactory({
opt: {
destinationReceptionDate: xDaysAgo(new Date(), 10),
destinationReceptionAcceptationStatus: WasteAcceptationStatus.REFUSED
},
transporterOpt: {
transporterCompanyName: transporter.name,
transporterCompanySiret: transporter.siret,
transporterCompanyVatNumber: transporter.vatNumber
}
});

// When
const bsdaForElastic = await getBsdaForElastic(bsda);
const elasticBsda = toBsdElastic(bsdaForElastic);

// Then
expect(elasticBsda.isReturnFor).toStrictEqual([]);
});
});
});
9 changes: 9 additions & 0 deletions back/src/bsda/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ export function getFirstTransporterSync(bsda: {
return firstTransporter ?? null;
}

export function getLastTransporterSync(bsda: {
transporters: BsdaTransporter[] | null;
}): BsdaTransporter | null {
const transporters = getTransportersSync(bsda);
const greatestNumber = Math.max(...transporters.map(t => t.number));
const lastTransporter = transporters.find(t => t.number === greatestNumber);
return lastTransporter ?? null;
}

export function getNthTransporterSync(
bsda: BsdaWithTransporters,
n: number
Expand Down
45 changes: 44 additions & 1 deletion back/src/bsda/elastic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Bsda, BsdaStatus, BsdaTransporter, BsdType } from "@prisma/client";
import {
Bsda,
BsdaStatus,
BsdaTransporter,
BsdType,
WasteAcceptationStatus
} from "@prisma/client";
import { getTransporterCompanyOrgId } from "@td/constants";
import { BsdElastic, indexBsd, transportPlateFilter } from "../common/elastic";
import { buildAddress } from "../companies/sirene/utils";
Expand All @@ -20,10 +26,13 @@ import { prisma } from "@td/prisma";
import { getRevisionOrgIds } from "../common/elasticHelpers";
import {
getFirstTransporterSync,
getLastTransporterSync,
getNextTransporterSync,
getTransportersSync
} from "./database";
import { getBsdaSubType } from "../common/subTypes";
import { isDefined } from "../common/helpers";
import { xDaysAgo } from "../utils";

export type BsdaForElastic = Bsda &
BsdaWithTransporters &
Expand Down Expand Up @@ -380,6 +389,7 @@ export function toBsdElastic(bsda: BsdaForElastic): BsdElastic {
destinationOperationDate: bsda.destinationOperationDate?.getTime(),
...where,
...getBsdaRevisionOrgIds(bsda),
...getBsdaReturnOrgIds(bsda),
revisionRequests: bsda.bsdaRevisionRequests,
sirets: Object.values(where).flat(),
...getRegistryFields(bsda),
Expand Down Expand Up @@ -428,3 +438,36 @@ export function getBsdaRevisionOrgIds(
): Pick<BsdElastic, "isInRevisionFor" | "isRevisedFor"> {
return getRevisionOrgIds(bsda.bsdaRevisionRequests);
}

/**
* BSDA belongs to isReturnFor tab if:
* - waste has been received in the last 48 hours
* - waste hasn't been fully accepted
*/
export const belongsToIsReturnForTab = (bsda: BsdaForElastic) => {
const hasBeenReceivedLately =
isDefined(bsda.destinationReceptionDate) &&
bsda.destinationReceptionDate! > xDaysAgo(new Date(), 2);

if (!hasBeenReceivedLately) return false;

const hasNotBeenFullyAccepted =
bsda.status === BsdaStatus.REFUSED ||
bsda.destinationReceptionAcceptationStatus !==
WasteAcceptationStatus.ACCEPTED;

return hasNotBeenFullyAccepted;
};

function getBsdaReturnOrgIds(bsda: BsdaForElastic): { isReturnFor: string[] } {
// Return tab
if (belongsToIsReturnForTab(bsda)) {
const lastTransporter = getLastTransporterSync(bsda);

return {
isReturnFor: [lastTransporter?.transporterCompanySiret].filter(Boolean)
};
}

return { isReturnFor: [] };
}
Loading

0 comments on commit 480a090

Please sign in to comment.