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 [addDialogType, setAddDialogType] = useState<"service" | "ventilation" | null>(null);
|
||||||
const [newItemName, setNewItemName] = useState("");
|
const [newItemName, setNewItemName] = useState("");
|
||||||
const [pendingInvoiceId, setPendingInvoiceId] = useState<number | null>(null);
|
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: invoices, isLoading } = trpc.invoices.list.useQuery();
|
||||||
const { data: departments } = trpc.departments.getByUser.useQuery();
|
const { data: departments } = trpc.departments.getByUser.useQuery();
|
||||||
const { data: allocations } = trpc.accountingAllocations.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>{getExportStatusBadge(invoice.exportStatus || "not_exported")}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex gap-2">
|
<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
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setLocation(`/invoices/${invoice.id}`)}
|
onClick={() => setLocation(`/invoices/${invoice.id}`)}
|
||||||
className="h-8 px-2"
|
className="h-8 px-2"
|
||||||
|
title="Modifier"
|
||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -527,6 +542,7 @@ export default function Invoices() {
|
|||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="h-8 px-2 text-red-600 hover:text-red-700 hover:bg-red-50"
|
className="h-8 px-2 text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||||
|
title="Supprimer"
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -625,6 +641,24 @@ export default function Invoices() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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>
|
</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,
|
"when": 1770814574366,
|
||||||
"tag": "0004_white_the_hunter",
|
"tag": "0004_white_the_hunter",
|
||||||
"breakpoints": true
|
"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
|
typeAchat: mysqlEnum("typeAchat", ["CAPEX", "OPEX"]), // Type d'achat
|
||||||
ventilationComptable: varchar("ventilationComptable", { length: 100 }), // Ventilation comptable (TOUS, PA, HEP, etc.)
|
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
|
// SFTP Export tracking
|
||||||
exportedAt: timestamp("exportedAt"),
|
exportedAt: timestamp("exportedAt"),
|
||||||
exportMode: mysqlEnum("exportMode", ["manual", "automatic"]),
|
exportMode: mysqlEnum("exportMode", ["manual", "automatic"]),
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ async function processEmailAttachment(
|
|||||||
totalAmount: invoiceData.totalAmount?.toString(),
|
totalAmount: invoiceData.totalAmount?.toString(),
|
||||||
pageRange: invoiceData.pageRange,
|
pageRange: invoiceData.pageRange,
|
||||||
qualityScore: invoiceData.qualityScore,
|
qualityScore: invoiceData.qualityScore,
|
||||||
|
extractedText: invoiceData.extractedText,
|
||||||
metadataFileKey: metadataKey,
|
metadataFileKey: metadataKey,
|
||||||
metadataFileUrl: metadataUrl,
|
metadataFileUrl: metadataUrl,
|
||||||
status: "completed",
|
status: "completed",
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ async function processFolderFile(
|
|||||||
totalAmount: invoiceData.totalAmount?.toString(),
|
totalAmount: invoiceData.totalAmount?.toString(),
|
||||||
pageRange: invoiceData.pageRange,
|
pageRange: invoiceData.pageRange,
|
||||||
qualityScore: invoiceData.qualityScore,
|
qualityScore: invoiceData.qualityScore,
|
||||||
|
extractedText: invoiceData.extractedText,
|
||||||
metadataFileKey: metadataKey,
|
metadataFileKey: metadataKey,
|
||||||
metadataFileUrl: metadataUrl,
|
metadataFileUrl: metadataUrl,
|
||||||
status: "completed",
|
status: "completed",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface ExtractedInvoiceData {
|
|||||||
totalAmount: number | null;
|
totalAmount: number | null;
|
||||||
pageRange: string;
|
pageRange: string;
|
||||||
qualityScore: number; // 0-100
|
qualityScore: number; // 0-100
|
||||||
|
extractedText: string | null; // Full text extracted from the invoice PDF
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultiInvoiceResult {
|
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)
|
- orderNumber: Numéro de commande client (si présent)
|
||||||
- totalAmount: Montant total TTC (nombre décimal)
|
- totalAmount: Montant total TTC (nombre décimal)
|
||||||
- pageRange: Plage de pages de cette facture (ex: "1-2" ou "5")
|
- 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:
|
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": "...",
|
"orderNumber": "...",
|
||||||
"totalAmount": 123.45,
|
"totalAmount": 123.45,
|
||||||
"pageRange": "1-2",
|
"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(),
|
totalAmount: invoiceData.totalAmount?.toString(),
|
||||||
pageRange: invoiceData.pageRange,
|
pageRange: invoiceData.pageRange,
|
||||||
qualityScore: invoiceData.qualityScore,
|
qualityScore: invoiceData.qualityScore,
|
||||||
|
extractedText: invoiceData.extractedText,
|
||||||
metadataFileKey: metadataKey,
|
metadataFileKey: metadataKey,
|
||||||
metadataFileUrl: metadataUrl,
|
metadataFileUrl: metadataUrl,
|
||||||
status: "completed",
|
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] Sauvegarder la nouvelle entrée dans la base de données (createDepartmentMutation, createAllocationMutation)
|
||||||
- [x] Sélectionner automatiquement la nouvelle valeur pour la facture
|
- [x] Sélectionner automatiquement la nouvelle valeur pour la facture
|
||||||
- [x] Rafraîchir les listes après ajout (invalidate queries)
|
- [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