fix: require BAP before original PDF exports
This commit is contained in:
40
server/bapExportGuard.test.ts
Normal file
40
server/bapExportGuard.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { requiresBapBeforeOriginalPdfExport } from "../shared/bapExportGuard";
|
||||
|
||||
describe("garde-fou d’export avant BAP", () => {
|
||||
it("bloque l’export PDF original d’une facture hors abonnement non validée BAP", () => {
|
||||
expect(requiresBapBeforeOriginalPdfExport({ isSubscription: 0, bapValidated: 0 })).toBe(true);
|
||||
});
|
||||
|
||||
it("autorise les abonnements et les factures déjà validées BAP", () => {
|
||||
expect(requiresBapBeforeOriginalPdfExport({ isSubscription: 1, bapValidated: 0 })).toBe(false);
|
||||
expect(requiresBapBeforeOriginalPdfExport({ isSubscription: 0, bapValidated: 1 })).toBe(false);
|
||||
});
|
||||
|
||||
it("applique le garde-fou aux deux exports PDF métier côté serveur", () => {
|
||||
const routerSource = readFileSync(join(process.cwd(), "server", "routers.ts"), "utf8");
|
||||
const exportPdfSection = routerSource.slice(
|
||||
routerSource.indexOf("exportToPdf: protectedProcedure"),
|
||||
routerSource.indexOf("exportInvoices: protectedProcedure"),
|
||||
);
|
||||
const exportInvoicesSection = routerSource.slice(
|
||||
routerSource.indexOf("exportInvoices: protectedProcedure"),
|
||||
routerSource.indexOf("// ============= IMPORT LOGS ROUTES"),
|
||||
);
|
||||
|
||||
expect(exportPdfSection).toContain("requiresBapBeforeOriginalPdfExport");
|
||||
expect(exportInvoicesSection).toContain("requiresBapBeforeOriginalPdfExport");
|
||||
});
|
||||
|
||||
it("clarifie les exports historiques et autorise leur régularisation BAP dans l’interface", () => {
|
||||
const invoicesPage = readFileSync(join(process.cwd(), "client", "src", "pages", "Invoices.tsx"), "utf8");
|
||||
const bapPage = readFileSync(join(process.cwd(), "client", "src", "pages", "InvoicesBAP.tsx"), "utf8");
|
||||
|
||||
expect(invoicesPage).toContain("requiresBapBeforeOriginalPdfExport");
|
||||
expect(bapPage).toContain("Exporté avant BAP");
|
||||
expect(bapPage).toContain("wasExportedBeforeBap");
|
||||
expect(bapPage).not.toContain('invoice.exportStatus !== "exported"');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { COOKIE_NAME } from "@shared/const";
|
||||
import { BAP_MIN_QUALITY_SCORE, meetsBapQualityThreshold } from "@shared/bapEligibility";
|
||||
import { requiresBapBeforeOriginalPdfExport } from "@shared/bapExportGuard";
|
||||
import { calculateInvoiceQualityScore, withCalculatedInvoiceQuality } from "@shared/invoiceQuality";
|
||||
import { webPortalUrlSchema } from "./webPortalUrl";
|
||||
|
||||
@@ -1407,6 +1408,19 @@ export const appRouter = router({
|
||||
message: "Toutes les factures doivent avoir un score de qualité de 100% pour être exportées"
|
||||
});
|
||||
}
|
||||
|
||||
// Un PDF hors abonnement doit suivre le circuit BAP. Sans ce contrôle
|
||||
// serveur, l'export manuel contournait la validation BAP puis rendait
|
||||
// l'état métier incohérent dans la liste des factures.
|
||||
const invoicesAwaitingBap = invoices.filter(
|
||||
(invoice) => invoice && requiresBapBeforeOriginalPdfExport(invoice),
|
||||
);
|
||||
if (invoicesAwaitingBap.length > 0) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "La validation BAP est requise avant l'export PDF des factures hors abonnement.",
|
||||
});
|
||||
}
|
||||
|
||||
// La destination est résolue facture par facture : une sélection peut
|
||||
// contenir plusieurs abonnements avec des règles de priorité distinctes.
|
||||
@@ -1649,6 +1663,19 @@ export const appRouter = router({
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "SFTP not configured" });
|
||||
}
|
||||
|
||||
const selectedInvoices = await Promise.all(input.invoiceIds.map(id => getInvoiceById(id)));
|
||||
const invoicesAwaitingBap = selectedInvoices.filter(
|
||||
(invoice) => invoice
|
||||
&& (ctx.user.role === "admin" || invoice.userId === ctx.user.id)
|
||||
&& requiresBapBeforeOriginalPdfExport(invoice),
|
||||
);
|
||||
if (invoicesAwaitingBap.length > 0) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "La validation BAP est requise avant l'export SFTP des factures hors abonnement.",
|
||||
});
|
||||
}
|
||||
|
||||
// Load user settings to check recipient filter
|
||||
const userSettings = await getUserSettings(ctx.user.id);
|
||||
const recipientFilter = (userSettings as any)?.sftpRecipientFilter?.trim() || "";
|
||||
|
||||
Reference in New Issue
Block a user