Checkpoint: Ajout du téléchargement groupé ZIP dans Factures BAP, bouton Re-générer dans Historique BAP, filtres Validées BAP / En attente BAP dans Factures BAP
This commit is contained in:
@@ -32,7 +32,32 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle, CheckCircle2, ShieldCheck, RefreshCw } from "lucide-react";
|
||||
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle, CheckCircle2, ShieldCheck, RefreshCw, FolderDown } from "lucide-react";
|
||||
|
||||
// Helper : télécharge un ZIP de plusieurs PDFs annotés BAP
|
||||
async function downloadBapZip(
|
||||
files: Array<{ pdfPath: string; filename: string }>
|
||||
) {
|
||||
const response = await fetch('/api/download-bap-zip', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ files }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({}));
|
||||
throw new Error((err as any).error || 'Erreur lors de la génération du ZIP');
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const zipFilename = `BAP_export_${new Date().toLocaleDateString('fr-CA')}.zip`;
|
||||
a.download = zipFilename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
|
||||
// Nommage : AAAA-MM-JJ - Fournisseur - N°Facture.pdf
|
||||
@@ -95,6 +120,7 @@ export default function InvoicesBAP() {
|
||||
const [bapPdfUrls, setBapPdfUrls] = useState<Record<number, string>>({});
|
||||
const [textDialogOpen, setTextDialogOpen] = useState(false);
|
||||
const [selectedInvoiceText, setSelectedInvoiceText] = useState<string | null>(null);
|
||||
const [isZipDownloading, setIsZipDownloading] = useState(false);
|
||||
const { data: allInvoices, isLoading } = trpc.invoices.list.useQuery();
|
||||
// Filter for BAP invoices only (Abonnement = NON, isSubscription = 0)
|
||||
const invoices = allInvoices?.filter(inv => inv.isSubscription === 0);
|
||||
@@ -299,6 +325,8 @@ export default function InvoicesBAP() {
|
||||
if (statusFilter === "exported" && inv.exportStatus !== "exported") return false;
|
||||
if (statusFilter === "not_exported" && inv.exportStatus !== "not_exported") return false;
|
||||
if (statusFilter === "export_error" && inv.exportStatus !== "export_error") return false;
|
||||
if (statusFilter === "bap_validated" && inv.bapValidated !== 1) return false;
|
||||
if (statusFilter === "bap_pending" && inv.bapValidated === 1) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -309,6 +337,8 @@ export default function InvoicesBAP() {
|
||||
exported: invoices?.filter(inv => inv.exportStatus === "exported").length || 0,
|
||||
not_exported: invoices?.filter(inv => inv.exportStatus === "not_exported").length || 0,
|
||||
export_error: invoices?.filter(inv => inv.exportStatus === "export_error").length || 0,
|
||||
bap_validated: invoices?.filter(inv => inv.bapValidated === 1).length || 0,
|
||||
bap_pending: invoices?.filter(inv => inv.bapValidated !== 1).length || 0,
|
||||
};
|
||||
|
||||
// Old filter logic (to be removed)
|
||||
@@ -455,6 +485,43 @@ export default function InvoicesBAP() {
|
||||
<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);
|
||||
const pdfUrl = allBapPdfUrls[id];
|
||||
if (!inv || !pdfUrl) return null;
|
||||
const datePart = new Date().toLocaleDateString('fr-CA');
|
||||
const supplierPart = (inv.supplierName || '').replace(/[^a-zA-Z0-9\u00e0-\u00ff \-]/g, '').trim();
|
||||
const numberPart = (inv.invoiceNumber || '').replace(/[^a-zA-Z0-9\-]/g, '').trim();
|
||||
const parts = [datePart, supplierPart, numberPart].filter(Boolean);
|
||||
const filename = parts.length > 0 ? `${parts.join(' - ')}.pdf` : pdfUrl.split('/').pop() || 'BAP.pdf';
|
||||
return { pdfPath: pdfUrl, filename };
|
||||
})
|
||||
.filter(Boolean) as Array<{ pdfPath: string; filename: string }>;
|
||||
if (!files.length) {
|
||||
toast.error('Aucune facture validée BAP parmi la sélection');
|
||||
return;
|
||||
}
|
||||
setIsZipDownloading(true);
|
||||
try {
|
||||
await downloadBapZip(files);
|
||||
toast.success(`ZIP généré avec ${files.length} PDF(s)`);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || 'Erreur lors de la génération du ZIP');
|
||||
} finally {
|
||||
setIsZipDownloading(false);
|
||||
}
|
||||
}}
|
||||
disabled={selectedIds.length === 0 || isZipDownloading}
|
||||
variant="outline"
|
||||
className="border-indigo-500 text-indigo-600 hover:bg-indigo-50"
|
||||
>
|
||||
<FolderDown className={`w-4 h-4 mr-2 ${isZipDownloading ? 'animate-bounce' : ''}`} />
|
||||
{isZipDownloading ? 'ZIP...' : `ZIP (${selectedIds.length})`}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
if (confirm(`Voulez-vous vraiment supprimer ${selectedIds.length} facture(s) ?`)) {
|
||||
@@ -560,6 +627,22 @@ export default function InvoicesBAP() {
|
||||
>
|
||||
Erreurs ({statusCounts.export_error})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_validated" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_validated")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_validated" ? "bg-emerald-600 hover:bg-emerald-700" : ""}
|
||||
>
|
||||
✅ Validées BAP ({statusCounts.bap_validated})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "bap_pending" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("bap_pending")}
|
||||
size="sm"
|
||||
className={statusFilter === "bap_pending" ? "bg-orange-600 hover:bg-orange-700" : ""}
|
||||
>
|
||||
⏳ En attente BAP ({statusCounts.bap_pending})
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
|
||||
Reference in New Issue
Block a user