fix: expliciter le résultat des imports manuels
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
import { parseImportResultSummary } from "@shared/importResultSummary";
|
||||||
|
|
||||||
interface UploadedFile {
|
interface UploadedFile {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -24,6 +25,9 @@ interface UploadedFile {
|
|||||||
status: "pending" | "processing" | "completed" | "error";
|
status: "pending" | "processing" | "completed" | "error";
|
||||||
progress?: string;
|
progress?: string;
|
||||||
invoicesDetected?: number;
|
invoicesDetected?: number;
|
||||||
|
importedCount?: number;
|
||||||
|
duplicateCount?: number;
|
||||||
|
errorCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Upload() {
|
export default function Upload() {
|
||||||
@@ -66,11 +70,15 @@ export default function Upload() {
|
|||||||
prev.map((f) => {
|
prev.map((f) => {
|
||||||
if (f.sourceFileId !== sf.id) return f;
|
if (f.sourceFileId !== sf.id) return f;
|
||||||
if (f.status === sf.processingStatus) return f;
|
if (f.status === sf.processingStatus) return f;
|
||||||
|
const summary = parseImportResultSummary(sf.processingProgress);
|
||||||
return {
|
return {
|
||||||
...f,
|
...f,
|
||||||
status: (sf.processingStatus ?? "pending") as UploadedFile["status"],
|
status: (sf.processingStatus ?? "pending") as UploadedFile["status"],
|
||||||
progress: sf.processingProgress ?? undefined,
|
progress: sf.processingProgress ?? undefined,
|
||||||
invoicesDetected: sf.totalInvoicesDetected ?? undefined,
|
invoicesDetected: sf.totalInvoicesDetected ?? undefined,
|
||||||
|
importedCount: summary?.importedCount,
|
||||||
|
duplicateCount: summary?.duplicateCount,
|
||||||
|
errorCount: summary?.errorCount,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -165,6 +173,10 @@ export default function Upload() {
|
|||||||
const processingCount = uploadedFiles.filter(
|
const processingCount = uploadedFiles.filter(
|
||||||
(f) => f.status === "processing" || f.status === "pending"
|
(f) => f.status === "processing" || f.status === "pending"
|
||||||
).length;
|
).length;
|
||||||
|
const importedInvoiceCount = uploadedFiles.reduce(
|
||||||
|
(total, file) => total + (file.importedCount ?? 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
@@ -282,8 +294,11 @@ export default function Upload() {
|
|||||||
<div>
|
<div>
|
||||||
<CardTitle>Fichiers importés</CardTitle>
|
<CardTitle>Fichiers importés</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{completedCount > 0 && (
|
{completedCount > 0 && importedInvoiceCount > 0 && (
|
||||||
<span className="text-green-600 font-medium">{completedCount} terminé(s) </span>
|
<span className="text-green-600 font-medium">{importedInvoiceCount} facture(s) créée(s) </span>
|
||||||
|
)}
|
||||||
|
{completedCount > 0 && importedInvoiceCount === 0 && (
|
||||||
|
<span className="text-amber-700 font-medium">{completedCount} terminé(s), aucune facture créée </span>
|
||||||
)}
|
)}
|
||||||
{processingCount > 0 && (
|
{processingCount > 0 && (
|
||||||
<span className="text-blue-600 font-medium">{processingCount} en cours </span>
|
<span className="text-blue-600 font-medium">{processingCount} en cours </span>
|
||||||
@@ -294,7 +309,7 @@ export default function Upload() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{completedCount > 0 && (
|
{importedInvoiceCount > 0 && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setLocation("/invoices")}
|
onClick={() => setLocation("/invoices")}
|
||||||
@@ -332,10 +347,16 @@ export default function Upload() {
|
|||||||
{f.status === "processing" && (
|
{f.status === "processing" && (
|
||||||
<p className="text-xs text-muted-foreground">{f.progress || "Traitement en cours..."}</p>
|
<p className="text-xs text-muted-foreground">{f.progress || "Traitement en cours..."}</p>
|
||||||
)}
|
)}
|
||||||
{f.status === "completed" && f.invoicesDetected !== undefined && (
|
{f.status === "completed" && f.importedCount !== undefined && (
|
||||||
<p className="text-xs text-green-600">
|
f.importedCount > 0 ? (
|
||||||
{f.invoicesDetected} facture(s) détectée(s)
|
<p className="text-xs text-green-600">
|
||||||
</p>
|
{f.importedCount} facture(s) créée(s)
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-amber-700">
|
||||||
|
Aucune facture créée{f.duplicateCount ? ` : ${f.duplicateCount} doublon(s) ou facture(s) supprimée(s)` : ""}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
{f.status === "error" && (
|
{f.status === "error" && (
|
||||||
<p className="text-xs text-red-500">{f.progress || "Erreur de traitement"}</p>
|
<p className="text-xs text-red-500">{f.progress || "Erreur de traitement"}</p>
|
||||||
|
|||||||
23
server/importResultSummary.test.ts
Normal file
23
server/importResultSummary.test.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { parseImportResultSummary } from "@shared/importResultSummary";
|
||||||
|
|
||||||
|
describe("parseImportResultSummary", () => {
|
||||||
|
it("identifie un traitement terminé sans facture créée", () => {
|
||||||
|
expect(parseImportResultSummary("Terminé: 0 importée(s), 1 doublon(s)"))
|
||||||
|
.toEqual({ importedCount: 0, duplicateCount: 1, errorCount: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("identifie une facture réellement créée", () => {
|
||||||
|
expect(parseImportResultSummary("Terminé: 1 importée(s), 0 doublon(s)"))
|
||||||
|
.toEqual({ importedCount: 1, duplicateCount: 0, errorCount: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("conserve les erreurs éventuelles", () => {
|
||||||
|
expect(parseImportResultSummary("Terminé: 0 importée(s), 0 doublon(s), 1 erreur(s)"))
|
||||||
|
.toEqual({ importedCount: 0, duplicateCount: 0, errorCount: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("retourne null pour un statut intermédiaire", () => {
|
||||||
|
expect(parseImportResultSummary("Extraction 1/1 factures...")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
17
shared/importResultSummary.ts
Normal file
17
shared/importResultSummary.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type ImportResultSummary = {
|
||||||
|
importedCount: number;
|
||||||
|
duplicateCount: number;
|
||||||
|
errorCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Extrait le résultat final affiché par les services d'import. */
|
||||||
|
export function parseImportResultSummary(progress: string | null | undefined): ImportResultSummary | null {
|
||||||
|
if (!progress) return null;
|
||||||
|
const match = progress.match(/Termin[ée]\s*:\s*(\d+)\s+importée\(s\),\s*(\d+)\s+doublon\(s\)(?:,\s*(\d+)\s+erreur\(s\))?/i);
|
||||||
|
if (!match) return null;
|
||||||
|
return {
|
||||||
|
importedCount: Number(match[1]),
|
||||||
|
duplicateCount: Number(match[2]),
|
||||||
|
errorCount: Number(match[3] ?? 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user