feat: bouton Dévalider BAP, barre actions 2 lignes, suppression colonne Utilisateur

This commit is contained in:
Manus
2026-06-15 12:14:25 -04:00
parent a9387eaaea
commit b34e145d5d
2 changed files with 65 additions and 21 deletions

View File

@@ -284,6 +284,17 @@ export default function InvoicesBAP() {
},
});
const devalidateBAPMutation = trpc.invoices.devalidateBAP.useMutation({
onSuccess: (data) => {
toast.success(`${data.processed} facture(s) dévalidée(s) BAP avec succès`);
setSelectedIds([]);
utils.invoices.list.invalidate();
},
onError: (error) => {
toast.error(error.message || "Erreur lors de la dévalidation BAP");
},
});
const updateFieldMutation = trpc.invoices.update.useMutation({
onSuccess: () => {
toast.success("Facture mise à jour");
@@ -556,7 +567,9 @@ export default function InvoicesBAP() {
<h1 className="text-3xl font-bold">Factures BAP</h1>
<p className="text-gray-500 mt-1">Factures non-abonnement (Abonnement = NON)</p>
</div>
{/* Barre d'actions : boutons sur une ligne dédiée avec retour à la ligne automatique */}
{/* Barre d'actions : 2 lignes */}
<div className="space-y-2">
{/* Ligne 1 : Excel, ZIP, Valider tout en BAP, Importer */}
<div className="flex flex-wrap gap-2">
<Button
onClick={handleExportExcel}
@@ -567,17 +580,8 @@ export default function InvoicesBAP() {
<FileSpreadsheet className="w-4 h-4 mr-2" />
Excel ({selectedIds.length})
</Button>
<Button
onClick={handleExport}
disabled={selectedIds.length === 0 || exportMutation.isPending}
className="bg-blue-600 hover:bg-blue-700"
>
<Download className="w-4 h-4 mr-2" />
Exporter ({selectedIds.length})
</Button>
<Button
onClick={async () => {
// Construire la liste des PDFs validés parmi la sélection
const files = selectedIds
.map(id => {
const inv = invoices?.find(i => i.id === id);
@@ -612,6 +616,25 @@ export default function InvoicesBAP() {
<FolderDown className={`w-4 h-4 mr-2 ${isZipDownloading ? 'animate-bounce' : ''}`} />
{isZipDownloading ? 'ZIP...' : `ZIP (${selectedIds.length})`}
</Button>
<Button
onClick={() => {
if (confirm("Valider en BAP toutes les factures éligibles (score 100%, champs remplis, non abonnement) ? Les PDFs annotés seront générés automatiquement.")) {
validateBAPBulkMutation.mutate();
}
}}
disabled={validateBAPBulkMutation.isPending}
className="bg-green-700 hover:bg-green-800 text-white"
>
<ShieldCheck className="w-4 h-4 mr-2" />
{validateBAPBulkMutation.isPending ? "Validation en cours..." : "Valider tout en BAP"}
</Button>
<Button onClick={() => setLocation("/upload")}>
<FileText className="w-4 h-4 mr-2" />
Importer
</Button>
</div>
{/* Ligne 2 : Supprimer, Dévalider BAP, Relancer */}
<div className="flex flex-wrap gap-2">
<Button
onClick={async () => {
if (confirm(`Voulez-vous vraiment supprimer ${selectedIds.length} facture(s) ?`)) {
@@ -635,15 +658,21 @@ export default function InvoicesBAP() {
</Button>
<Button
onClick={() => {
if (confirm("Valider en BAP toutes les factures éligibles (score 100%, champs remplis, non abonnement) ? Les PDFs annotés seront générés automatiquement.")) {
validateBAPBulkMutation.mutate();
const bapIds = selectedIds.filter(id => invoices?.find(inv => inv.id === id)?.bapValidated === 1);
if (bapIds.length === 0) {
toast.error("Aucune facture BAP validée parmi la sélection");
return;
}
if (confirm(`Dévalider ${bapIds.length} facture(s) BAP ? Elles repasser ont en statut à traiter.`)) {
devalidateBAPMutation.mutate({ invoiceIds: bapIds });
}
}}
disabled={validateBAPBulkMutation.isPending}
className="bg-green-700 hover:bg-green-800 text-white"
disabled={selectedIds.length === 0 || devalidateBAPMutation.isPending}
variant="outline"
className="border-orange-500 text-orange-600 hover:bg-orange-50"
>
<ShieldCheck className="w-4 h-4 mr-2" />
{validateBAPBulkMutation.isPending ? "Validation en cours..." : "Valider tout en BAP"}
<ShieldCheck className="w-4 h-4 mr-2 rotate-180" />
{devalidateBAPMutation.isPending ? "Dévalidation..." : `Dévalider BAP (${selectedIds.length})`}
</Button>
<Button
onClick={() => {
@@ -662,10 +691,7 @@ export default function InvoicesBAP() {
<RefreshCw className={`w-4 h-4 mr-2 ${reprocessMutation.isPending ? 'animate-spin' : ''}`} />
{reprocessMutation.isPending ? `Retraitement...` : `Relancer (${selectedIds.length})`}
</Button>
<Button onClick={() => setLocation("/upload")}>
<FileText className="w-4 h-4 mr-2" />
Importer
</Button>
</div>
</div>
<Card>

View File

@@ -654,6 +654,24 @@ export const appRouter = router({
};
}),
// ── Dévalidation BAP ──────────────────────────────────────
devalidateBAP: protectedProcedure
.input(z.object({ invoiceIds: z.array(z.number()) }))
.mutation(async ({ input, ctx }) => {
let processed = 0;
for (const id of input.invoiceIds) {
const invoice = await getInvoiceById(id);
if (!invoice || invoice.userId !== ctx.user.id) continue;
await updateInvoice(id, {
bapValidated: 0,
bapValidatedAt: null,
exportStatus: "not_exported",
});
processed++;
}
return { success: true, processed };
}),
// ── Validation BAP en masse ──────────────────────────────
validateBAPBulk: protectedProcedure
.mutation(async ({ ctx }) => {