Checkpoint: Ajout du bouton Validation BAP dans la page Factures BAP : nouveau champ bapValidated/bapValidatedAt dans le schéma, migration DB, route tRPC validateBAP avec vérification des critères (Score 100%, non exportée, isSubscription=0, service+typeAchat+ventilation remplis), bouton vert actif/désactivé avec tooltip explicatif, badge "Validé" pour les factures déjà validées

This commit is contained in:
Manus
2026-03-06 04:17:53 -05:00
parent f16efa4197
commit 9868a1afda
7 changed files with 1430 additions and 2 deletions

View File

@@ -32,7 +32,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { trpc } from "@/lib/trpc";
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle } from "lucide-react";
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle, CheckCircle2 } from "lucide-react";
import * as XLSX from 'xlsx';
import { toast } from "sonner";
import { useLocation } from "wouter";
@@ -131,6 +131,16 @@ export default function InvoicesBAP() {
},
});
const validateBAPMutation = trpc.invoices.validateBAP.useMutation({
onSuccess: () => {
toast.success("Facture validée BAP avec succès !");
utils.invoices.list.invalidate();
},
onError: (error) => {
toast.error(error.message || "Erreur lors de la validation BAP");
},
});
const updateFieldMutation = trpc.invoices.update.useMutation({
onSuccess: () => {
toast.success("Facture mise à jour");
@@ -300,6 +310,36 @@ export default function InvoicesBAP() {
invoice.ventilationComptable
);
};
const isEligibleForBAPValidation = (invoice: any) => {
// Une facture peut être validée BAP si :
// 1. Score = 100%
// 2. Non encore exportée (exportStatus !== 'exported')
// 3. Abonnement = NON (isSubscription = 0)
// 4. Service concerné rempli
// 5. Type d'achat rempli
// 6. Ventilation comptable remplie
return (
(invoice.qualityScore || 0) === 100 &&
invoice.exportStatus !== "exported" &&
invoice.isSubscription === 0 &&
invoice.serviceConcerne &&
invoice.typeAchat &&
invoice.ventilationComptable
);
};
const getBAPValidationTooltip = (invoice: any): string => {
const reasons: string[] = [];
if ((invoice.qualityScore || 0) < 100) reasons.push("Score < 100%");
if (invoice.exportStatus === "exported") reasons.push("Déjà exportée");
if (invoice.isSubscription !== 0) reasons.push("Marquée comme abonnement");
if (!invoice.serviceConcerne) reasons.push("Service manquant");
if (!invoice.typeAchat) reasons.push("Type d'achat manquant");
if (!invoice.ventilationComptable) reasons.push("Ventilation manquante");
if (reasons.length === 0) return "Valider cette facture BAP";
return `Non éligible : ${reasons.join(", ")}`;
};
const hasOnlyEligibleSelected = selectedIds.length > 0 && selectedIds.every(id => {
const invoice = invoices?.find(inv => inv.id === id);
@@ -583,7 +623,7 @@ export default function InvoicesBAP() {
</TableCell>
<TableCell>{getExportStatusBadge(invoice.exportStatus || "not_exported")}</TableCell>
<TableCell>
<div className="flex gap-2">
<div className="flex gap-1">
<Button
size="sm"
variant="outline"
@@ -617,6 +657,31 @@ export default function InvoicesBAP() {
>
<Trash className="h-4 w-4" />
</Button>
{invoice.bapValidated === 1 ? (
<div
className="h-8 px-2 flex items-center justify-center rounded border border-green-300 bg-green-50 text-green-700 text-xs font-semibold gap-1"
title={`Validé BAP le ${invoice.bapValidatedAt ? new Date(invoice.bapValidatedAt).toLocaleDateString('fr-FR') : ''}`}
>
<CheckCircle2 className="h-4 w-4" />
<span>Validé</span>
</div>
) : (
<Button
size="sm"
variant="outline"
onClick={() => validateBAPMutation.mutate({ id: invoice.id })}
disabled={!isEligibleForBAPValidation(invoice) || validateBAPMutation.isPending}
className={`h-8 px-2 gap-1 ${
isEligibleForBAPValidation(invoice)
? "text-emerald-700 border-emerald-400 hover:bg-emerald-50 hover:border-emerald-500"
: "text-gray-400 border-gray-200 cursor-not-allowed opacity-50"
}`}
title={getBAPValidationTooltip(invoice)}
>
<CheckCircle2 className="h-4 w-4" />
<span className="text-xs">BAP</span>
</Button>
)}
</div>
</TableCell>
</TableRow>