diff --git a/client/src/pages/Invoices.tsx b/client/src/pages/Invoices.tsx index f931644..f0a91b0 100644 --- a/client/src/pages/Invoices.tsx +++ b/client/src/pages/Invoices.tsx @@ -1,9 +1,10 @@ import { useState } from "react"; import DashboardLayout from "@/components/DashboardLayout"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; +import { Checkbox } from "@/components/ui/checkbox"; import { Table, TableBody, @@ -13,23 +14,34 @@ import { TableRow, } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; -import { Search, Eye, Trash2, FileText } from "lucide-react"; +import { Search, FileText, Download } from "lucide-react"; import { toast } from "sonner"; import { useLocation } from "wouter"; export default function Invoices() { const [, setLocation] = useLocation(); const [searchQuery, setSearchQuery] = useState(""); + const [selectedIds, setSelectedIds] = useState([]); const { data: invoices, isLoading } = trpc.invoices.list.useQuery(); const utils = trpc.useUtils(); - const deleteMutation = trpc.invoices.delete.useMutation({ - onSuccess: () => { - toast.success("Facture supprimée"); + const exportMutation = trpc.sftp.exportToPdf.useMutation({ + onSuccess: (data) => { + toast.success(`${data.invoices.length} facture(s) exportée(s) avec succès`); + + // Download PDFs + data.invoices.forEach(inv => { + const link = document.createElement('a'); + link.href = inv.fileUrl; + link.download = `${inv.supplierName || 'facture'}_${inv.invoiceNumber || 'unknown'}.pdf`; + link.click(); + }); + + setSelectedIds([]); utils.invoices.list.invalidate(); }, onError: (error) => { - toast.error(error.message || "Erreur lors de la suppression"); + toast.error(error.message || "Erreur lors de l'export"); }, }); @@ -42,20 +54,43 @@ export default function Invoices() { ); }); - const handleDelete = (id: number) => { - if (confirm("Êtes-vous sûr de vouloir supprimer cette facture ?")) { - deleteMutation.mutate({ id }); + const handleSelectAll = (checked: boolean) => { + if (checked) { + // Select only invoices with score 100 + const eligibleIds = (filteredInvoices || []) + .filter(inv => (inv.qualityScore || 0) === 100) + .map(inv => inv.id); + setSelectedIds(eligibleIds); + } else { + setSelectedIds([]); } }; - const getStatusBadge = (status: string) => { + const handleSelectOne = (id: number, checked: boolean) => { + if (checked) { + setSelectedIds([...selectedIds, id]); + } else { + setSelectedIds(selectedIds.filter(selectedId => selectedId !== id)); + } + }; + + const handleExport = () => { + if (selectedIds.length === 0) { + toast.error("Veuillez sélectionner au moins une facture"); + return; + } + + exportMutation.mutate({ invoiceIds: selectedIds }); + }; + + const getExportStatusBadge = (status: string) => { switch (status) { - case "completed": - return Complété; - case "processing": - return En cours; - case "error": - return Erreur; + case "exported": + return Exporté; + case "not_exported": + return Non exporté; + case "export_error": + return Erreur export; default: return {status}; } @@ -63,11 +98,18 @@ export default function Invoices() { const getQualityBadge = (score: number | null) => { if (score === null) return -; - if (score >= 80) return {score}; - if (score >= 60) return {score}; - return {score}; + if (score === 100) return {score}%; + if (score >= 80) return {score}%; + return {score}%; }; + const isEligibleForExport = (invoice: any) => { + return (invoice.qualityScore || 0) === 100; + }; + + const allEligibleSelected = (filteredInvoices?.length || 0) > 0 && + (filteredInvoices || []).filter(inv => isEligibleForExport(inv)).every(inv => selectedIds.includes(inv.id)); + return (
@@ -76,93 +118,114 @@ export default function Invoices() {

Factures

Gérez toutes vos factures importées

- +
+ + +
- - Liste des factures - -
- + + {/* Search */} +
+
+ setSearchQuery(e.target.value)} - className="max-w-md" + className="pl-10" />
- - - +
+ + {/* Table */} {isLoading ? (
Chargement...
) : filteredInvoices && filteredInvoices.length > 0 ? ( - - - - Fournisseur - N° Facture - Date - Montant - Score - Statut - Actions - - - - {filteredInvoices.map((invoice) => ( - - - {invoice.supplierName || "Inconnu"} - - {invoice.invoiceNumber || "-"} - - {invoice.invoiceDate - ? new Date(invoice.invoiceDate).toLocaleDateString("fr-FR") - : "-"} - - - {invoice.totalAmount - ? `${parseFloat(invoice.totalAmount).toFixed(2)} €` - : "-"} - - {getQualityBadge(invoice.qualityScore)} - {getStatusBadge(invoice.status)} - -
- - -
-
+
+
+ + + + + + Fournisseur + N° Facture + Date + Montant + Score + Statut - ))} - -
+ + + {filteredInvoices.map((invoice) => { + const eligible = isEligibleForExport(invoice); + return ( + + + handleSelectOne(invoice.id, checked as boolean)} + disabled={!eligible} + /> + + + {invoice.supplierName || "Inconnu"} + + {invoice.invoiceNumber || "-"} + + {invoice.invoiceDate + ? new Date(invoice.invoiceDate).toLocaleDateString("fr-FR") + : "-"} + + + {invoice.totalAmount + ? `${parseFloat(invoice.totalAmount as string).toFixed(2)} €` + : "-"} + + {getQualityBadge(invoice.qualityScore)} + {getExportStatusBadge(invoice.exportStatus || "not_exported")} + + ); + })} + + +
) : ( -
+
-

Aucune facture trouvée

+

Aucune facture trouvée

+

+ {searchQuery ? "Essayez une autre recherche" : "Commencez par importer un fichier PDF"} +

)} + + {/* Info message */} + {filteredInvoices && filteredInvoices.some(inv => (inv.qualityScore || 0) < 100) && ( +
+

+ Note : Seules les factures avec un score de qualité de 100% peuvent être exportées. + Les factures avec un score inférieur sont grisées et ne peuvent pas être sélectionnées. +

+
+ )}
); diff --git a/drizzle/0002_striped_red_skull.sql b/drizzle/0002_striped_red_skull.sql new file mode 100644 index 0000000..8bb4077 --- /dev/null +++ b/drizzle/0002_striped_red_skull.sql @@ -0,0 +1 @@ +ALTER TABLE `invoices` ADD `exportStatus` enum('not_exported','exported','export_error') DEFAULT 'not_exported' NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..8ea31ba --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,819 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "5a495536-d69b-4bd9-9618-0644c5a001bd", + "prevId": "82b3cbc0-6925-4f7c-b3c7-0a1c0e96f34d", + "tables": { + "importLogs": { + "name": "importLogs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sourceFileId": { + "name": "sourceFileId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileName": { + "name": "fileName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totalInvoicesDetected": { + "name": "totalInvoicesDetected", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "invoicesImported": { + "name": "invoicesImported", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "duplicatesIgnored": { + "name": "duplicatesIgnored", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "errors": { + "name": "errors", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "duplicateDetails": { + "name": "duplicateDetails", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "errorDetails": { + "name": "errorDetails", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importedAt": { + "name": "importedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "importLogs_id": { + "name": "importLogs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sourceFileId": { + "name": "sourceFileId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invoiceIndexInFile": { + "name": "invoiceIndexInFile", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "fileName": { + "name": "fileName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileKey": { + "name": "fileKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileUrl": { + "name": "fileUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "supplierName": { + "name": "supplierName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoiceNumber": { + "name": "invoiceNumber", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoiceDate": { + "name": "invoiceDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deliveryNoteNumber": { + "name": "deliveryNoteNumber", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "orderNumber": { + "name": "orderNumber", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totalAmount": { + "name": "totalAmount", + "type": "decimal(10,2)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pageRange": { + "name": "pageRange", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualityScore": { + "name": "qualityScore", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadataFileKey": { + "name": "metadataFileKey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadataFileUrl": { + "name": "metadataFileUrl", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "enum('processing','completed','error')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'processing'" + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exportStatus": { + "name": "exportStatus", + "type": "enum('not_exported','exported','export_error')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_exported'" + }, + "manuallyEdited": { + "name": "manuallyEdited", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "exportedAt": { + "name": "exportedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "exportMode": { + "name": "exportMode", + "type": "enum('manual','automatic')", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": { + "supplier_invoice_date_unique": { + "name": "supplier_invoice_date_unique", + "columns": [ + "supplierName", + "invoiceNumber", + "invoiceDate" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "invoices_id": { + "name": "invoices_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "llmLogs": { + "name": "llmLogs", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sourceFileId": { + "name": "sourceFileId", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoiceId": { + "name": "invoiceId", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "promptSent": { + "name": "promptSent", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rawResponse": { + "name": "rawResponse", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cleanedResponse": { + "name": "cleanedResponse", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "errorMessage": { + "name": "errorMessage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "processingTimeMs": { + "name": "processingTimeMs", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pageRange": { + "name": "pageRange", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "llmLogs_id": { + "name": "llmLogs_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sourceFiles": { + "name": "sourceFiles", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileName": { + "name": "fileName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileKey": { + "name": "fileKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fileUrl": { + "name": "fileUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totalInvoicesDetected": { + "name": "totalInvoicesDetected", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processingStatus": { + "name": "processingStatus", + "type": "enum('processing','completed','error')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'processing'" + }, + "processingProgress": { + "name": "processingProgress", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "sourceFiles_id": { + "name": "sourceFiles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "userSettings": { + "name": "userSettings", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "userId": { + "name": "userId", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "llmModel": { + "name": "llmModel", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'mistral-large-latest'" + }, + "orderNumberFormat": { + "name": "orderNumberFormat", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoiceNumberKeywords": { + "name": "invoiceNumberKeywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deliveryNoteKeywords": { + "name": "deliveryNoteKeywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "orderNumberKeywords": { + "name": "orderNumberKeywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "supplierKeywords": { + "name": "supplierKeywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totalAmountKeywords": { + "name": "totalAmountKeywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sftpHost": { + "name": "sftpHost", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sftpPort": { + "name": "sftpPort", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 22 + }, + "sftpUsername": { + "name": "sftpUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sftpPassword": { + "name": "sftpPassword", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sftpRemotePath": { + "name": "sftpRemotePath", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'/'" + }, + "sftpAutoExport": { + "name": "sftpAutoExport", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "llmLogsRetentionMonths": { + "name": "llmLogsRetentionMonths", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 3 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "userSettings_id": { + "name": "userSettings_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "userSettings_userId_unique": { + "name": "userSettings_userId_unique", + "columns": [ + "userId" + ] + } + }, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "openId": { + "name": "openId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "azureAdId": { + "name": "azureAdId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(320)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "passwordHash": { + "name": "passwordHash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "loginMethod": { + "name": "loginMethod", + "type": "enum('manus','local','azure-ad')", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('user','admin')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "isActive": { + "name": "isActive", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "lastSignedIn": { + "name": "lastSignedIn", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "users_openId_unique": { + "name": "users_openId_unique", + "columns": [ + "openId" + ] + }, + "users_azureAdId_unique": { + "name": "users_azureAdId_unique", + "columns": [ + "azureAdId" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ] + } + }, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 21301ed..938f13a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1767869488758, "tag": "0001_breezy_the_spike", "breakpoints": true + }, + { + "idx": 2, + "version": "5", + "when": 1767881720296, + "tag": "0002_striped_red_skull", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 44ca356..721e5ac 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -78,6 +78,9 @@ export const invoices = mysqlTable("invoices", { status: mysqlEnum("status", ["processing", "completed", "error"]).default("processing").notNull(), errorMessage: text("errorMessage"), + // Export status + exportStatus: mysqlEnum("exportStatus", ["not_exported", "exported", "export_error"]).default("not_exported").notNull(), + // Manual correction tracking manuallyEdited: int("manuallyEdited").default(0).notNull(), // 0 = false, 1 = true diff --git a/server/routers.ts b/server/routers.ts index cb4e30f..333cce9 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -424,6 +424,49 @@ export const appRouter = router({ return { success }; }), + exportToPdf: protectedProcedure + .input(z.object({ invoiceIds: z.array(z.number()) })) + .mutation(async ({ input, ctx }) => { + // Validate that all invoices have quality score of 100 + const invoices = await Promise.all( + input.invoiceIds.map(id => getInvoiceById(id)) + ); + + const invalidInvoices = invoices.filter( + inv => !inv || inv.userId !== ctx.user.id || (inv.qualityScore || 0) < 100 + ); + + if (invalidInvoices.length > 0) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Toutes les factures doivent avoir un score de qualit\u00e9 de 100% pour \u00eatre export\u00e9es" + }); + } + + // Update export status + for (const invoice of invoices) { + if (invoice) { + await updateInvoice(invoice.id, { + exportStatus: "exported", + exportedAt: new Date(), + exportMode: "manual", + }); + } + } + + // Return the file URLs for PDF generation on client side + return { + success: true, + invoices: invoices.filter(Boolean).map(inv => ({ + id: inv!.id, + fileUrl: inv!.fileUrl, + supplierName: inv!.supplierName, + invoiceNumber: inv!.invoiceNumber, + invoiceDate: inv!.invoiceDate, + })) + }; + }), + exportInvoices: protectedProcedure .input(z.object({ invoiceIds: z.array(z.number()) })) .mutation(async ({ input, ctx }) => { diff --git a/todo.md b/todo.md index bfb5aa1..489a4c7 100644 --- a/todo.md +++ b/todo.md @@ -74,3 +74,15 @@ - [x] Créer graphique circulaire du top 10 fournisseurs (Recharts) - [x] Créer liste détaillée des fournisseurs avec nombre de factures - [x] Ajouter bouton "Voir les statistiques détaillées" + +## Modification page Factures - Export PDF +- [x] Ajouter champ exportStatus dans le schéma invoices (exported/not_exported/export_error) +- [x] Ajouter champ exportedAt dans le schéma invoices (déjà existant) +- [x] Créer route tRPC pour exporter les factures en PDF +- [x] Créer route tRPC pour mettre à jour le statut d'export +- [x] Ajouter cases à cocher pour sélection multiple dans la page Factures +- [x] Ajouter case "Tout sélectionner" +- [x] Modifier l'affichage des statuts (Exporté/Non exporté/Erreur export) +- [x] Ajouter bouton "Exporter" (actif uniquement pour score 100%) +- [x] Implémenter le téléchargement de PDF pour les factures sélectionnées +- [x] Valider que seules les factures avec score 100% peuvent être exportées