Checkpoint: Ajout d'un bouton "Détail" dans la liste des factures pour afficher le texte complet extrait du PDF. Le texte est maintenant demandé à Mistral AI lors de l'extraction et sauvegardé dans la base de données. Une boîte de dialogue affiche le texte complet avec défilement.
This commit is contained in:
@@ -48,6 +48,8 @@ export default function Invoices() {
|
||||
const [addDialogType, setAddDialogType] = useState<"service" | "ventilation" | null>(null);
|
||||
const [newItemName, setNewItemName] = useState("");
|
||||
const [pendingInvoiceId, setPendingInvoiceId] = useState<number | null>(null);
|
||||
const [textDialogOpen, setTextDialogOpen] = useState(false);
|
||||
const [selectedInvoiceText, setSelectedInvoiceText] = useState<string | null>(null);
|
||||
const { data: invoices, isLoading } = trpc.invoices.list.useQuery();
|
||||
const { data: departments } = trpc.departments.getByUser.useQuery();
|
||||
const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery();
|
||||
@@ -511,11 +513,24 @@ export default function Invoices() {
|
||||
<TableCell>{getExportStatusBadge(invoice.exportStatus || "not_exported")}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setSelectedInvoiceText(invoice.extractedText || "Aucun texte extrait disponible");
|
||||
setTextDialogOpen(true);
|
||||
}}
|
||||
className="h-8 px-2"
|
||||
title="Voir le texte extrait"
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setLocation(`/invoices/${invoice.id}`)}
|
||||
className="h-8 px-2"
|
||||
title="Modifier"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -527,6 +542,7 @@ export default function Invoices() {
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
className="h-8 px-2 text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
title="Supprimer"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -625,6 +641,24 @@ export default function Invoices() {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Extracted Text Dialog */}
|
||||
<Dialog open={textDialogOpen} onOpenChange={setTextDialogOpen}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Texte extrait de la facture</DialogTitle>
|
||||
<DialogDescription>
|
||||
Texte complet extrait du PDF lors de l'import
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="overflow-y-auto max-h-[60vh] p-4 bg-gray-50 rounded border">
|
||||
<pre className="whitespace-pre-wrap text-sm font-mono">{selectedInvoiceText}</pre>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setTextDialogOpen(false)}>Fermer</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
1
drizzle/0005_small_roxanne_simpson.sql
Normal file
1
drizzle/0005_small_roxanne_simpson.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `invoices` ADD `extractedText` text;
|
||||
1088
drizzle/meta/0005_snapshot.json
Normal file
1088
drizzle/meta/0005_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,13 @@
|
||||
"when": 1770814574366,
|
||||
"tag": "0004_white_the_hunter",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "5",
|
||||
"when": 1770819973462,
|
||||
"tag": "0005_small_roxanne_simpson",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -89,6 +89,9 @@ export const invoices = mysqlTable("invoices", {
|
||||
typeAchat: mysqlEnum("typeAchat", ["CAPEX", "OPEX"]), // Type d'achat
|
||||
ventilationComptable: varchar("ventilationComptable", { length: 100 }), // Ventilation comptable (TOUS, PA, HEP, etc.)
|
||||
|
||||
// Extracted text from PDF
|
||||
extractedText: text("extractedText"), // Full text extracted from the invoice PDF
|
||||
|
||||
// SFTP Export tracking
|
||||
exportedAt: timestamp("exportedAt"),
|
||||
exportMode: mysqlEnum("exportMode", ["manual", "automatic"]),
|
||||
|
||||
@@ -154,6 +154,7 @@ async function processEmailAttachment(
|
||||
totalAmount: invoiceData.totalAmount?.toString(),
|
||||
pageRange: invoiceData.pageRange,
|
||||
qualityScore: invoiceData.qualityScore,
|
||||
extractedText: invoiceData.extractedText,
|
||||
metadataFileKey: metadataKey,
|
||||
metadataFileUrl: metadataUrl,
|
||||
status: "completed",
|
||||
|
||||
@@ -155,6 +155,7 @@ async function processFolderFile(
|
||||
totalAmount: invoiceData.totalAmount?.toString(),
|
||||
pageRange: invoiceData.pageRange,
|
||||
qualityScore: invoiceData.qualityScore,
|
||||
extractedText: invoiceData.extractedText,
|
||||
metadataFileKey: metadataKey,
|
||||
metadataFileUrl: metadataUrl,
|
||||
status: "completed",
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface ExtractedInvoiceData {
|
||||
totalAmount: number | null;
|
||||
pageRange: string;
|
||||
qualityScore: number; // 0-100
|
||||
extractedText: string | null; // Full text extracted from the invoice PDF
|
||||
}
|
||||
|
||||
export interface MultiInvoiceResult {
|
||||
@@ -143,7 +144,8 @@ Pour chaque facture trouvée, extrais les informations suivantes:
|
||||
- orderNumber: Numéro de commande client (si présent)
|
||||
- totalAmount: Montant total TTC (nombre décimal)
|
||||
- pageRange: Plage de pages de cette facture (ex: "1-2" ou "5")
|
||||
- qualityScore: Score de qualité de l'extraction de 0 à 100 (100 = toutes les informations trouvées et claires)${keywordsHint}
|
||||
- qualityScore: Score de qualité de l'extraction de 0 à 100 (100 = toutes les informations trouvées et claires)
|
||||
- extractedText: Texte complet extrait de la facture (tout le texte visible sur les pages de cette facture)${keywordsHint}
|
||||
|
||||
Réponds UNIQUEMENT avec un objet JSON valide au format suivant:
|
||||
{
|
||||
@@ -158,7 +160,8 @@ Réponds UNIQUEMENT avec un objet JSON valide au format suivant:
|
||||
"orderNumber": "...",
|
||||
"totalAmount": 123.45,
|
||||
"pageRange": "1-2",
|
||||
"qualityScore": 85
|
||||
"qualityScore": 85,
|
||||
"extractedText": "Texte complet de la facture..."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ export const appRouter = router({
|
||||
totalAmount: invoiceData.totalAmount?.toString(),
|
||||
pageRange: invoiceData.pageRange,
|
||||
qualityScore: invoiceData.qualityScore,
|
||||
extractedText: invoiceData.extractedText,
|
||||
metadataFileKey: metadataKey,
|
||||
metadataFileUrl: metadataUrl,
|
||||
status: "completed",
|
||||
|
||||
9
todo.md
9
todo.md
@@ -238,3 +238,12 @@
|
||||
- [x] Sauvegarder la nouvelle entrée dans la base de données (createDepartmentMutation, createAllocationMutation)
|
||||
- [x] Sélectionner automatiquement la nouvelle valeur pour la facture
|
||||
- [x] Rafraîchir les listes après ajout (invalidate queries)
|
||||
|
||||
## Affichage du texte extrait de la facture
|
||||
- [x] Ajouter une colonne extractedText dans la table invoices
|
||||
- [x] Modifier le prompt Mistral pour demander le texte extrait
|
||||
- [x] Modifier le type ExtractedInvoiceData pour inclure extractedText
|
||||
- [x] Modifier routers.ts, emailImportService.ts et folderImportService.ts pour sauvegarder extractedText
|
||||
- [x] Ajouter un bouton "Détail" (icône FileText) dans la liste des factures
|
||||
- [x] Créer une boîte de dialogue pour afficher le texte complet
|
||||
- [ ] Tester l'affichage du texte extrait avec une nouvelle facture importée
|
||||
|
||||
Reference in New Issue
Block a user