Checkpoint: Ajout de la colonne 'Export SP' dans le tableau historique de Ventilation FreePro : icône horloge grise = non exporté, icône verte = exporté (date au survol), icône rouge = erreur (message au survol). Le statut est sauvegardé en base (sharepointUploadStatus, sharepointUploadPath, sharepointUploadError, sharepointExportedAt) lors de chaque export via 'Envoyer vers SharePoint'.

This commit is contained in:
Manus
2026-06-08 10:53:34 -04:00
parent 43cacda7ad
commit 056bccd985
7 changed files with 2214 additions and 0 deletions

View File

@@ -66,6 +66,10 @@ interface ImportRecord {
nbLignes: number; nbLignes: number;
totalTtc: string | null; totalTtc: string | null;
createdAt: Date; createdAt: Date;
sharepointUploadStatus: 'success' | 'error' | null;
sharepointUploadPath: string | null;
sharepointUploadError: string | null;
sharepointExportedAt: Date | null;
} }
// ── Helpers ──────────────────────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────────────────────
@@ -847,6 +851,7 @@ function VentilationFreeProContent() {
<TableHead className="text-right">Lignes</TableHead> <TableHead className="text-right">Lignes</TableHead>
<TableHead className="text-right">Total TTC</TableHead> <TableHead className="text-right">Total TTC</TableHead>
<TableHead>Importé le</TableHead> <TableHead>Importé le</TableHead>
<TableHead className="text-center">Export SP</TableHead>
<TableHead className="text-right">Actions</TableHead> <TableHead className="text-right">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -876,6 +881,29 @@ function VentilationFreeProContent() {
<TableCell className="text-xs text-muted-foreground"> <TableCell className="text-xs text-muted-foreground">
{new Date(imp.createdAt).toLocaleDateString("fr-FR")} {new Date(imp.createdAt).toLocaleDateString("fr-FR")}
</TableCell> </TableCell>
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
{imp.sharepointUploadStatus === 'success' ? (
<span
title={`Exporté le ${imp.sharepointExportedAt ? new Date(imp.sharepointExportedAt).toLocaleString('fr-FR') : ''}${imp.sharepointUploadPath ? '\n' + imp.sharepointUploadPath : ''}`}
className="inline-flex items-center gap-1 text-green-600 cursor-default"
>
<CheckCircle2 className="h-4 w-4" />
<span className="text-xs hidden sm:inline">Exporté</span>
</span>
) : imp.sharepointUploadStatus === 'error' ? (
<span
title={`Erreur : ${imp.sharepointUploadError || 'Erreur inconnue'}`}
className="inline-flex items-center gap-1 text-red-500 cursor-default"
>
<XCircle className="h-4 w-4" />
<span className="text-xs hidden sm:inline">Erreur</span>
</span>
) : (
<span className="inline-flex items-center gap-1 text-muted-foreground/40 cursor-default" title="Non exporté vers SharePoint">
<Clock className="h-4 w-4" />
</span>
)}
</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex items-center justify-end gap-1" onClick={(e) => e.stopPropagation()}> <div className="flex items-center justify-end gap-1" onClick={(e) => e.stopPropagation()}>
<Button <Button

View File

@@ -0,0 +1,4 @@
ALTER TABLE `freeproImports` ADD `sharepointUploadStatus` enum('success','error');--> statement-breakpoint
ALTER TABLE `freeproImports` ADD `sharepointUploadPath` text;--> statement-breakpoint
ALTER TABLE `freeproImports` ADD `sharepointUploadError` text;--> statement-breakpoint
ALTER TABLE `freeproImports` ADD `sharepointExportedAt` timestamp;

File diff suppressed because it is too large Load Diff

View File

@@ -211,6 +211,13 @@
"when": 1780924653802, "when": 1780924653802,
"tag": "0029_unknown_spot", "tag": "0029_unknown_spot",
"breakpoints": true "breakpoints": true
},
{
"idx": 30,
"version": "5",
"when": 1780930222253,
"tag": "0030_vengeful_warbird",
"breakpoints": true
} }
] ]
} }

View File

@@ -442,6 +442,14 @@ export const freeproImports = mysqlTable("freeproImports", {
nbLignes: int("nbLignes").default(0).notNull(), nbLignes: int("nbLignes").default(0).notNull(),
/** Total général TTC calculé */ /** Total général TTC calculé */
totalTtc: varchar("totalTtc", { length: 30 }), totalTtc: varchar("totalTtc", { length: 30 }),
/** Statut de l'export SharePoint : null = jamais exporté, 'success' = exporté, 'error' = erreur */
sharepointUploadStatus: mysqlEnum("sharepointUploadStatus", ["success", "error"]),
/** URL/chemin du fichier dans SharePoint après export réussi */
sharepointUploadPath: text("sharepointUploadPath"),
/** Message d'erreur si l'export SharePoint a échoué */
sharepointUploadError: text("sharepointUploadError"),
/** Date de l'export SharePoint */
sharepointExportedAt: timestamp("sharepointExportedAt"),
createdAt: timestamp("createdAt").defaultNow().notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
}); });

View File

@@ -1008,6 +1008,26 @@ export async function getFreeproImportWithLines(
return { import: imports[0], lines }; return { import: imports[0], lines };
} }
/** Met à jour le statut d'export SharePoint d'un import FreePro */
export async function updateFreeproSharepointStatus(
importId: number,
status: 'success' | 'error',
path?: string | null,
error?: string | null
): Promise<void> {
const db = await getDb();
if (!db) return;
await db
.update(freeproImports)
.set({
sharepointUploadStatus: status,
sharepointUploadPath: path ?? null,
sharepointUploadError: error ?? null,
sharepointExportedAt: new Date(),
})
.where(eq(freeproImports.id, importId));
}
/** Supprime un import FreePro et ses lignes */ /** Supprime un import FreePro et ses lignes */
export async function deleteFreeproImport(importId: number): Promise<void> { export async function deleteFreeproImport(importId: number): Promise<void> {
const db = await getDb(); const db = await getDb();

View File

@@ -2436,9 +2436,16 @@ export const appRouter = router({
); );
if (!result.success) { if (!result.success) {
// Sauvegarder le statut d'erreur en base
const { updateFreeproSharepointStatus } = await import('./db');
await updateFreeproSharepointStatus(input.id, 'error', null, result.error || 'Erreur inconnue');
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: result.error || 'Erreur upload SharePoint' }); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: result.error || 'Erreur upload SharePoint' });
} }
// Sauvegarder le statut de succès en base
const { updateFreeproSharepointStatus } = await import('./db');
await updateFreeproSharepointStatus(input.id, 'success', result.webUrl || null, null);
return { success: true, webUrl: result.webUrl, fileName }; return { success: true, webUrl: result.webUrl, fileName };
}), }),
}), }),