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

@@ -94,6 +94,7 @@ import {
getWebImportSourceById,
createWebImportSource,
updateWebImportSource,
updateWebImportSourceStatus,
deleteWebImportSource,
} from "./db";
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured } from "./auth";
@@ -108,6 +109,7 @@ import { resolveBapExportDestination } from "./exportDestinationResolver";
import { createEmailImportAccountController, startEmailImportService, stopEmailImportService, isEmailImportServiceRunning, triggerEmailCheck, triggerManualEmailCheck, testImapConnection } from "./emailImportService";
import { startFolderImportService, stopFolderImportService, isFolderImportServiceRunning } from "./folderImportService";
import { getSafeWebImportSourceError, logWebImportSourceSaveFailure } from "./webImportSourcesError";
import { getManualWebImportAvailability, runManualWebImport } from "./manualWebImport";
import { TRPCError } from "@trpc/server";
import { processFreeproExcel } from "./freeproService";
import { runFreeproAutoImport, testFreeproConnection, startFreeproAutoJob, stopFreeproAutoJob, frequencyToMs } from "./freeproAutoImport";
@@ -2803,6 +2805,38 @@ export const appRouter = router({
}
return { apiToken: source.apiToken };
}),
/** Lance ponctuellement un connecteur pris en charge, sans activer de planification. */
runManual: protectedProcedure
.input(z.object({ id: z.number() }))
.mutation(async ({ input, ctx }) => {
const source = await getWebImportSourceById(input.id);
if (!source || source.userId !== ctx.user.id) {
throw new TRPCError({ code: "NOT_FOUND", message: "Source introuvable" });
}
const availability = getManualWebImportAvailability(source.connectorType);
if (!availability.available) {
throw new TRPCError({ code: "BAD_REQUEST", message: availability.reason });
}
try {
const result = await runManualWebImport(source);
return { success: true, ...result };
} catch (error) {
// Ne jamais inclure une erreur de portail dans l’interface : elle peut contenir
// des informations de session ou des sélecteurs internes.
const technicalCode = error && typeof error === "object" && "code" in error && typeof error.code === "string"
? error.code
: "unknown";
console.error("[WebImport] Échec d’import manuel", { sourceId: source.id, technicalCode });
await updateWebImportSourceStatus(source.id, "Erreur lors de l’import manuel", 0, false);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "L’import manuel n’a pas abouti. Consultez le statut du connecteur puis réessayez.",
});
}
}),
}),
// ============= BACKUP ROUTES =============