Checkpoint: Ajout du déclenchement manuel pour les connecteurs web SFR : confirmation explicite, automate Chromium côté serveur, récupération des PDF par session authentifiée et injection dans le flux existant de stockage/dédoublonnage/analyse. Le bouton est désactivé pour les types sans moteur réellement livré. Le résultat affiche factures importées et doublons, sans créer de planification. Chromium et playwright-core sont intégrés à l’image. Validation locale : 77 tests, TypeScript et build.

This commit is contained in:
Manus
2026-09-30 14:39:43 +00:00
parent 405649c606
commit e08df31909
8 changed files with 260 additions and 1 deletions

View File

@@ -25,7 +25,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Globe, Plus, Pencil, Trash2, Key, CheckCircle, XCircle, Clock, RefreshCw } from "lucide-react";
import { Globe, Plus, Pencil, Trash2, Key, CheckCircle, XCircle, Clock, RefreshCw, Play } from "lucide-react";
import { toast } from "sonner";
const CONNECTOR_TYPES = [
@@ -85,6 +85,7 @@ export default function WebImportSources() {
const [showForm, setShowForm] = useState(false);
const [editSource, setEditSource] = useState<Source | null>(null);
const [deleteId, setDeleteId] = useState<number | null>(null);
const [manualImportId, setManualImportId] = useState<number | null>(null);
const [showToken, setShowToken] = useState<number | null>(null);
const [form, setForm] = useState<FormData>(emptyForm);
@@ -124,6 +125,21 @@ export default function WebImportSources() {
onError: (e) => toast.error("Erreur", { description: e.message }),
});
const runManualMutation = trpc.webImportSources.runManual.useMutation({
onSuccess: (result) => {
toast.success("Import manuel terminé", {
description: `${result.imported} facture(s) importée(s), ${result.duplicates} doublon(s) ignoré(s).`,
});
setManualImportId(null);
refetch();
},
onError: (e) => {
toast.error("Import manuel en erreur", { description: e.message });
setManualImportId(null);
refetch();
},
});
function openCreate() {
setForm(emptyForm);
setEditSource(null);
@@ -253,6 +269,21 @@ export default function WebImportSources() {
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<Button
variant="outline"
size="sm"
className="gap-1 text-xs"
disabled={source.connectorType !== "sfr" || runManualMutation.isPending}
title={source.connectorType === "sfr" ? "Lancer une lecture ponctuelle" : "Ce connecteur ne dispose pas encore d’un moteur d’import manuel"}
onClick={() => setManualImportId(source.id)}
>
{runManualMutation.isPending && manualImportId === source.id ? (
<RefreshCw className="w-3 h-3 animate-spin" />
) : (
<Play className="w-3 h-3" />
)}
Importer maintenant
</Button>
<Button
variant="outline"
size="sm"
@@ -388,6 +419,26 @@ export default function WebImportSources() {
</DialogContent>
</Dialog>
<AlertDialog open={manualImportId !== null} onOpenChange={(open) => { if (!open && !runManualMutation.isPending) setManualImportId(null); }}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Lancer l’import manuel ?</AlertDialogTitle>
<AlertDialogDescription>
L’application se connectera ponctuellement à l’espace client SFR, téléchargera les nouvelles factures puis les analysera via le flux normal. Cette action ne crée aucune planification automatique.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={runManualMutation.isPending}>Annuler</AlertDialogCancel>
<AlertDialogAction
disabled={runManualMutation.isPending}
onClick={() => manualImportId !== null && runManualMutation.mutate({ id: manualImportId })}
>
{runManualMutation.isPending ? "Import en cours…" : "Lancer l’import"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Confirmation suppression */}
<AlertDialog open={deleteId !== null} onOpenChange={(open) => { if (!open) setDeleteId(null); }}>
<AlertDialogContent>