Checkpoint: Automatismes séparé en onglets Import et Export. Les règles d’export ciblent un destinataire ou une ventilation, avec destination locale, Teams ou SharePoint et ouverture navigateur optionnelle. La validation BAP unique ou en masse applique la première règle active par priorité puis conserve le paramétrage historique comme repli. Paramètres d’export déplacés vers Automatismes et page renommée Paramètres. Migration additive, tests, TypeScript, build et contrôles visuels validés.
This commit is contained in:
@@ -66,6 +66,11 @@ import {
|
||||
createAutomationRule,
|
||||
updateAutomationRule,
|
||||
deleteAutomationRule,
|
||||
getExportAutomationRulesByUser,
|
||||
getExportAutomationRuleById,
|
||||
createExportAutomationRule,
|
||||
updateExportAutomationRule,
|
||||
deleteExportAutomationRule,
|
||||
getSignaturesByUser,
|
||||
getSignatureById,
|
||||
createSignature,
|
||||
@@ -97,6 +102,7 @@ import { calculateFileSha256 } from "./fileFingerprint";
|
||||
import { localStoragePut, generateStorageKey } from "./localStorage";
|
||||
import { testSftpConnection, exportInvoiceToSftp, getUserSftpConfig } from "./sftpExport";
|
||||
import { drawBapCartouche } from "./bapCartouche";
|
||||
import { resolveBapExportDestination } from "./exportDestinationResolver";
|
||||
import { startEmailImportService, stopEmailImportService, isEmailImportServiceRunning, triggerEmailCheck, triggerManualEmailCheck, testImapConnection } from "./emailImportService";
|
||||
import { startFolderImportService, stopFolderImportService, isFolderImportServiceRunning } from "./folderImportService";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -521,9 +527,10 @@ export const appRouter = router({
|
||||
const { localStoragePut, generateStorageKey } = await import('./localStorage');
|
||||
|
||||
const importSettings = await getImportSettingsByUser(ctx.user.id);
|
||||
const bapExportMode = importSettings?.bapExportMode || 'browser';
|
||||
const exportFolder = importSettings?.exportFolder || null;
|
||||
const exportFolderType = (importSettings as any)?.exportFolderType || 'local';
|
||||
const resolvedDestination = await resolveBapExportDestination(ctx.user.id, invoice, importSettings);
|
||||
const bapExportMode = resolvedDestination.exportMode;
|
||||
const exportFolder = resolvedDestination.destinationPath;
|
||||
const exportFolderType = resolvedDestination.destinationType;
|
||||
const STORAGE_BASE_PATH = process.env.STORAGE_BASE_PATH || path.join(process.cwd(), 'storage');
|
||||
|
||||
let pdfUrl: string | null = null;
|
||||
@@ -609,11 +616,11 @@ export const appRouter = router({
|
||||
const _bapNumber = (invoice.invoiceNumber || '').replace(/[^a-zA-Z0-9\-]/g, '').trim();
|
||||
const bapFilename = [_bapDateStr, _bapSupplier, _bapNumber].filter(Boolean).join(' - ') + '.pdf';
|
||||
|
||||
console.log(`[BAP] Mode export: ${bapExportMode}, type: ${exportFolderType}, dossier: ${exportFolder ? 'configuré' : 'non configuré'}`);
|
||||
console.log(`[BAP] Mode export: ${bapExportMode}, type: ${exportFolderType}, source: ${resolvedDestination.source}, dossier: ${exportFolder ? 'configuré' : 'non configuré'}`);
|
||||
if ((bapExportMode === 'folder' || bapExportMode === 'both') && exportFolder) {
|
||||
if (exportFolderType === 'sharepoint') {
|
||||
// Mode SharePoint : upload via Microsoft Graph
|
||||
console.log('[BAP] Démarrage upload SharePoint pour:', bapFilename);
|
||||
if (exportFolderType === 'sharepoint' || exportFolderType === 'teams') {
|
||||
// Les fichiers Teams sont déposés via le site SharePoint de l’équipe.
|
||||
console.log('[BAP] Démarrage upload Microsoft 365 pour:', bapFilename);
|
||||
const { uploadToSharePoint } = await import('./sharepoint');
|
||||
const spResult = await uploadToSharePoint(
|
||||
{
|
||||
@@ -740,9 +747,6 @@ export const appRouter = router({
|
||||
const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib');
|
||||
const { localStoragePut, generateStorageKey } = await import('./localStorage');
|
||||
const importSettings = await getImportSettingsByUser(ctx.user.id);
|
||||
const bapExportMode = importSettings?.bapExportMode || 'browser';
|
||||
const exportFolder = importSettings?.exportFolder || null;
|
||||
const exportFolderType = (importSettings as any)?.exportFolderType || 'local';
|
||||
const STORAGE_BASE_PATH = process.env.STORAGE_BASE_PATH || path.join(process.cwd(), 'storage');
|
||||
const serviceSignaturesList = await getServiceSignaturesByUser(ctx.user.id);
|
||||
const results: Array<{ id: number; success: boolean; pdfUrl?: string | null; exportPath?: string | null; error?: string }> = [];
|
||||
@@ -756,6 +760,10 @@ export const appRouter = router({
|
||||
let sharepointUploadStatus: 'success' | 'error' | 'skipped' | null = null;
|
||||
let sharepointUploadPath: string | null = null;
|
||||
let sharepointUploadError: string | null = null;
|
||||
const resolvedDestination = await resolveBapExportDestination(ctx.user.id, invoice, importSettings);
|
||||
const bapExportMode = resolvedDestination.exportMode;
|
||||
const exportFolder = resolvedDestination.destinationPath;
|
||||
const exportFolderType = resolvedDestination.destinationType;
|
||||
try {
|
||||
// ─ Lecture du PDF source ─
|
||||
let sourcePdfBytes: Buffer;
|
||||
@@ -826,7 +834,7 @@ export const appRouter = router({
|
||||
const _bapNumber2 = (invoice.invoiceNumber || '').replace(/[^a-zA-Z0-9\-]/g, '').trim();
|
||||
const bapFilename = [_bapDateStr2, _bapSupplier2, _bapNumber2].filter(Boolean).join(' - ') + '.pdf';
|
||||
if ((bapExportMode === 'folder' || bapExportMode === 'both') && exportFolder) {
|
||||
if (exportFolderType === 'sharepoint') {
|
||||
if (exportFolderType === 'sharepoint' || exportFolderType === 'teams') {
|
||||
const { uploadToSharePoint } = await import('./sharepoint');
|
||||
const spResult = await uploadToSharePoint(
|
||||
{
|
||||
@@ -2276,6 +2284,55 @@ export const appRouter = router({
|
||||
}),
|
||||
}),
|
||||
|
||||
// ============= EXPORT AUTOMATION RULES =============
|
||||
exportAutomationRules: router({
|
||||
list: protectedProcedure.query(async ({ ctx }) => {
|
||||
return getExportAutomationRulesByUser(ctx.user.id);
|
||||
}),
|
||||
create: protectedProcedure
|
||||
.input(z.object({
|
||||
name: z.string().trim().min(1).max(255),
|
||||
conditionField: z.enum(["recipientName", "ventilationComptable"]),
|
||||
conditionValue: z.string().trim().min(1).max(255),
|
||||
destinationType: z.enum(["local", "teams", "sharepoint"]),
|
||||
destinationPath: z.string().trim().min(1),
|
||||
openInBrowser: z.number().int().min(0).max(1).default(0),
|
||||
isActive: z.number().int().min(0).max(1).default(1),
|
||||
priority: z.number().int().min(0).default(0),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => createExportAutomationRule({ userId: ctx.user.id, ...input })),
|
||||
update: protectedProcedure
|
||||
.input(z.object({
|
||||
id: z.number().int().positive(),
|
||||
name: z.string().trim().min(1).max(255).optional(),
|
||||
conditionField: z.enum(["recipientName", "ventilationComptable"]).optional(),
|
||||
conditionValue: z.string().trim().min(1).max(255).optional(),
|
||||
destinationType: z.enum(["local", "teams", "sharepoint"]).optional(),
|
||||
destinationPath: z.string().trim().min(1).optional(),
|
||||
openInBrowser: z.number().int().min(0).max(1).optional(),
|
||||
isActive: z.number().int().min(0).max(1).optional(),
|
||||
priority: z.number().int().min(0).optional(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const existing = await getExportAutomationRuleById(input.id);
|
||||
if (!existing || (ctx.user.role !== "admin" && existing.userId !== ctx.user.id)) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
const { id, ...data } = input;
|
||||
return updateExportAutomationRule(id, data);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.number().int().positive() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const existing = await getExportAutomationRuleById(input.id);
|
||||
if (!existing || (ctx.user.role !== "admin" && existing.userId !== ctx.user.id)) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
await deleteExportAutomationRule(input.id);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
// ============= SIGNATURES ROUTES =============
|
||||
signatures: router({
|
||||
list: protectedProcedure.query(async ({ ctx }) => {
|
||||
|
||||
Reference in New Issue
Block a user