From 84edfbfeddce267c8373e234cb57ac60637794b2 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:22:35 +0000 Subject: [PATCH 1/8] Transitioned from Plan to Build mode Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 20203fbf-cfb8-4af3-808d-bc7dff970678 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/9uIte90 From 09c873eb06cf69773b9187597322a1f380a28a1c Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:28:41 +0000 Subject: [PATCH 2/8] Add due date tracking to invoice verification and delivery data Updates InvoiceVerificationService to include dueDate, modifies routes.ts to fetch deliveries with due dates from storage instead of NocoDB, and adds dueDate to the deliveries schema in shared/schema.ts. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 45962ece-1470-4431-936b-016ef39720d8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/G9gVBac --- server/invoiceVerification.ts | 11 ++- server/routes.ts | 138 +++++++++++++++------------------- shared/schema.ts | 1 + 3 files changed, 69 insertions(+), 81 deletions(-) diff --git a/server/invoiceVerification.ts b/server/invoiceVerification.ts index 566ad1d..ff8fdd3 100644 --- a/server/invoiceVerification.ts +++ b/server/invoiceVerification.ts @@ -242,6 +242,7 @@ export class InvoiceVerificationService { invoiceReference?: string; invoiceAmount?: number; supplierName?: string; + dueDate?: string | null; fromCache?: boolean; }> { try { @@ -396,7 +397,8 @@ export class InvoiceVerificationService { matchType: 'invoice_reference' as const, invoiceReference: matchResult.data.invoice_reference || invoiceReference, invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), - supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu' + supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu', + dueDate: matchResult.data[group.nocodbDueDateColumnName || 'Echeance'] || null }; // Sauvegarder en cache si succès @@ -432,7 +434,8 @@ export class InvoiceVerificationService { matchType: 'bl_number' as const, invoiceReference: matchResult.data.invoice_reference || `BL_${invoiceReference}`, invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), - supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu' + supplierName: matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || 'Inconnu', + dueDate: matchResult.data[group.nocodbDueDateColumnName || 'Echeance'] || null }; // Sauvegarder en cache si succès @@ -599,6 +602,7 @@ export class InvoiceVerificationService { invoiceReference?: string; invoiceAmount?: number; supplierName?: string; + dueDate?: string | null; fromCache?: boolean; }> { try { @@ -795,7 +799,8 @@ export class InvoiceVerificationService { matchType: 'bl_number' as const, invoiceReference: matchResult.data[group.invoiceColumnName || 'RefFacture'] || `BL_${blNumber}`, invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), - supplierName: foundSupplier || supplierName + supplierName: foundSupplier || supplierName, + dueDate: matchResult.data[group.nocodbDueDateColumnName || 'Echeance'] || null }; // Sauvegarder en cache si succès diff --git a/server/routes.ts b/server/routes.ts index e4229b5..e44fe43 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -309,92 +309,41 @@ export async function registerRoutes(app: Express): Promise { } } - // Récupérer le groupe avec la configuration NocoDB + // Récupérer le groupe const group = await storage.getGroup(groupId); - if (!group || !group.nocodbConfigId || !group.nocodbTableId) { - return res.json({ schedules: [], message: 'Pas de configuration NocoDB pour ce groupe' }); + if (!group) { + return res.json({ schedules: [], message: 'Groupe non trouvé' }); } - // Récupérer la configuration NocoDB - const nocodbConfig = await storage.getNocodbConfig(group.nocodbConfigId); - if (!nocodbConfig) { - return res.json({ schedules: [], message: 'Configuration NocoDB non trouvée' }); - } + console.log('📅 Récupération échéances depuis deliveries:', { groupId, groupName: group.name }); - // Vérifier que les colonnes nécessaires sont configurées - if (!group.nocodbDueDateColumnName || !group.invoiceColumnName || !group.nocodbAmountColumnName) { - return res.json({ - schedules: [], - message: 'Configuration incomplète: colonnes date échéance, facture ou montant manquantes' - }); - } + // Récupérer toutes les livraisons du groupe avec échéance + const allDeliveries = await storage.getDeliveries(); + const deliveriesWithDueDate = allDeliveries.filter( + (d: any) => d.groupId === groupId && d.dueDate && d.invoiceReference + ); - // Récupérer tous les enregistrements de NocoDB - const url = `${nocodbConfig.baseUrl}/api/v1/db/data/noco/${nocodbConfig.projectId}/${group.nocodbTableId}`; - - console.log('📅 Récupération échéances NocoDB:', { - url, - groupId, - dueDateColumn: group.nocodbDueDateColumnName - }); + console.log(`📅 Trouvé ${deliveriesWithDueDate.length} livraisons avec échéance pour le groupe ${groupId}`); - const response = await fetch(url, { - method: "GET", - headers: { - "xc-token": nocodbConfig.apiToken, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - console.error('❌ Erreur NocoDB:', response.status, response.statusText); - return res.status(500).json({ error: 'Erreur lors de la récupération des données NocoDB' }); - } - - const data = await response.json(); - - // Récupérer tous les fournisseurs pour le mapping + // Récupérer tous les fournisseurs pour le mapping du mode de paiement const allSuppliers = await storage.getSuppliers(); - - // Fonction de normalisation pour comparer les noms de fournisseurs - const normalizeName = (name: string) => { - return name - .toLowerCase() - .trim() - .normalize("NFD") // Décompose les accents - .replace(/[\u0300-\u036f]/g, "") // Supprime les diacritiques - .replace(/\s+/g, ' '); // Normalise les espaces multiples - }; - - // Créer une map avec noms normalisés - const supplierMap = new Map(allSuppliers.map(s => [normalizeName(s.name), s])); + const supplierMap = new Map(allSuppliers.map((s: any) => [s.id, s])); - // Traiter les données et filtrer les enregistrements avec une date d'échéance - const schedules = (data.list || []) - .map((record: any) => { - const dueDate = record[group.nocodbDueDateColumnName!]; - const invoiceRef = record[group.invoiceColumnName!]; - const amount = record[group.nocodbAmountColumnName!]; - const supplierName = record[group.nocodbSupplierColumnName || 'Fournisseur']; - - if (!dueDate) return null; - - // Chercher le fournisseur correspondant avec normalisation - const normalizedSupplierName = normalizeName(supplierName || ''); - const supplier = supplierMap.get(normalizedSupplierName); - - return { - id: record.Id || record.id, - invoiceReference: invoiceRef, - dueDate: dueDate, - amount: amount ? parseFloat(amount) : 0, - supplierName: supplierName, - paymentMethod: supplier?.paymentMethod || null, - groupId: group.id, - groupName: group.name - }; - }) - .filter((s: any) => s !== null); + // Formatter les échéances + const schedules = deliveriesWithDueDate.map((delivery: any) => { + const supplier = supplierMap.get(delivery.supplierId); + + return { + id: delivery.id, + invoiceReference: delivery.invoiceReference, + dueDate: delivery.dueDate, + amount: delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0, + supplierName: supplier?.name || 'Fournisseur inconnu', + paymentMethod: supplier?.paymentMethod || null, + groupId: group.id, + groupName: group.name + }; + }); res.json({ schedules }); @@ -1374,6 +1323,39 @@ export async function registerRoutes(app: Express): Promise { } } + // GESTION ÉCHÉANCE : Si la référence facture change, mettre à jour la date d'échéance + if (data.invoiceReference !== undefined && data.invoiceReference !== delivery.invoiceReference) { + if (data.invoiceReference && data.invoiceReference.trim()) { + // Nouvelle référence facture : reverifier dans NocoDB pour récupérer l'échéance + try { + const { InvoiceVerificationService } = await import('./invoiceVerification.js'); + const verificationService = new InvoiceVerificationService(); + const result = await verificationService.verifyInvoice( + data.invoiceReference, + delivery.groupId, + true, // forceRefresh + delivery.reconciled || false + ); + + if (result.exists && result.dueDate) { + data.dueDate = result.dueDate; + console.log(`📅 Date d'échéance récupérée depuis NocoDB: ${result.dueDate}`); + } else { + data.dueDate = null; + console.log(`📅 Aucune date d'échéance trouvée dans NocoDB`); + } + } catch (error) { + console.error('❌ Erreur récupération échéance:', error); + // Ne pas bloquer la mise à jour, juste ne pas avoir d'échéance + data.dueDate = null; + } + } else { + // Référence facture vidée : vider aussi l'échéance + data.dueDate = null; + console.log(`📅 Référence facture vidée, échéance également vidée`); + } + } + const updatedDelivery = await storage.updateDelivery(id, data); console.log('✅ Delivery updated successfully:', { id, updatedDelivery }); diff --git a/shared/schema.ts b/shared/schema.ts index 5a5136a..8dd0b9e 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -119,6 +119,7 @@ export const deliveries = pgTable("deliveries", { blAmount: decimal("bl_amount", { precision: 10, scale: 2 }), // Montant BL invoiceReference: varchar("invoice_reference"), // Référence facture invoiceAmount: decimal("invoice_amount", { precision: 10, scale: 2 }), // Montant facture + dueDate: date("due_date"), // Date d'échéance de paiement (récupérée depuis NocoDB) reconciled: boolean("reconciled").default(false), // Rapprochement effectué validatedAt: timestamp("validated_at"), // Date de validation de la livraison // Champs pour le contrôle des livraisons (fournisseurs nécessitant un contrôle) From 972286e22e5089082096c53c6030107c42055b96 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:38:48 +0000 Subject: [PATCH 3/8] Improve date handling for improved data accuracy Add a date normalization function to server/routes.ts to handle various date formats from NocoDB and standardize them to YYYY-MM-DD, also refactor delivery retrieval logic to separate dated and undated deliveries. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 4d0fa35d-6e0d-44c6-b7e9-f78186630c41 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/G9gVBac --- server/routes.ts | 124 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 8 deletions(-) diff --git a/server/routes.ts b/server/routes.ts index e44fe43..4845239 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -7,6 +7,79 @@ import { db, pool } from "./db"; console.log('🔍 Using development storage and authentication'); +// Fonction de normalisation des dates pour gérer différents formats de NocoDB +function normalizeDateString(dateString: string | null | undefined): string | null { + if (!dateString || typeof dateString !== 'string') return null; + + const trimmed = dateString.trim(); + if (!trimmed) return null; + + try { + // Si déjà au format ISO (YYYY-MM-DD), le retourner tel quel + if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) { + return trimmed; + } + + // Format slash ou tiret : DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, MM-DD-YYYY + const slashMatch = trimmed.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})$/); + if (slashMatch) { + const [, first, second, year] = slashMatch; + const firstNum = parseInt(first); + const secondNum = parseInt(second); + + // Validation basique des valeurs + if (firstNum > 31 || secondNum > 31 || firstNum === 0 || secondNum === 0) { + console.warn(`⚠️ Date invalide (valeurs hors limites): ${trimmed}`); + return null; + } + + // Déterminer le format en fonction des valeurs + let day: string, month: string; + + if (firstNum > 12) { + // first > 12 → forcément DD/MM (format français/européen) + day = first.padStart(2, '0'); + month = second.padStart(2, '0'); + } else if (secondNum > 12) { + // second > 12 → forcément MM/DD (format américain) + day = second.padStart(2, '0'); + month = first.padStart(2, '0'); + } else { + // Ambiguïté (les deux < 12) → on assume format français DD/MM par défaut + // Pour être plus sûr, on pourrait vérifier la configuration du groupe/locale + day = first.padStart(2, '0'); + month = second.padStart(2, '0'); + } + + // Validation finale : mois entre 1-12, jour entre 1-31 + const monthNum = parseInt(month); + const dayNum = parseInt(day); + if (monthNum < 1 || monthNum > 12 || dayNum < 1 || dayNum > 31) { + console.warn(`⚠️ Date invalide après parsing: ${trimmed} → month=${month}, day=${day}`); + return null; + } + + return `${year}-${month}-${day}`; + } + + // Essayer de parser avec Date (format ISO complet avec heures) + const date = new Date(trimmed); + if (!isNaN(date.getTime())) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + // Si aucun format reconnu, retourner null (ne pas persister une date invalide) + console.warn(`⚠️ Format de date non reconnu: ${trimmed}`); + return null; + } catch (error) { + console.error('❌ Erreur normalisation date:', error); + return null; // Retourner null en cas d'erreur pour éviter de persister des données invalides + } +} + // Simple hash password function using crypto async function hashPasswordSimple(password: string) { const crypto = await import('crypto'); @@ -317,20 +390,53 @@ export async function registerRoutes(app: Express): Promise { console.log('📅 Récupération échéances depuis deliveries:', { groupId, groupName: group.name }); - // Récupérer toutes les livraisons du groupe avec échéance + // Récupérer toutes les livraisons du groupe const allDeliveries = await storage.getDeliveries(); - const deliveriesWithDueDate = allDeliveries.filter( - (d: any) => d.groupId === groupId && d.dueDate && d.invoiceReference - ); + const groupDeliveries = allDeliveries.filter((d: any) => d.groupId === groupId && d.invoiceReference); - console.log(`📅 Trouvé ${deliveriesWithDueDate.length} livraisons avec échéance pour le groupe ${groupId}`); + // Séparer les livraisons avec et sans dueDate + const deliveriesWithDueDate = groupDeliveries.filter((d: any) => d.dueDate); + const deliveriesWithoutDueDate = groupDeliveries.filter((d: any) => !d.dueDate); + + console.log(`📅 Livraisons avec échéance: ${deliveriesWithDueDate.length}, sans échéance: ${deliveriesWithoutDueDate.length}`); + + // FALLBACK : Pour les livraisons sans dueDate, interroger NocoDB + const { InvoiceVerificationService } = await import('./invoiceVerification.js'); + const verificationService = new InvoiceVerificationService(); + + for (const delivery of deliveriesWithoutDueDate) { + try { + const result = await verificationService.verifyInvoice( + delivery.invoiceReference!, + delivery.groupId, + false, // Ne pas forcer le refresh, utiliser cache si disponible + delivery.reconciled || false + ); + + if (result.exists && result.dueDate) { + // Normaliser la date avant de la stocker + const normalizedDate = normalizeDateString(result.dueDate); + if (normalizedDate) { + // Mettre à jour deliveries avec la date normalisée (caching) + await storage.updateDelivery(delivery.id, { dueDate: normalizedDate }); + delivery.dueDate = normalizedDate; + console.log(`📅 Fallback: échéance récupérée et normalisée pour livraison #${delivery.id}`); + } + } + } catch (error) { + console.error(`❌ Fallback échéance échoué pour livraison #${delivery.id}:`, error); + } + } + + // Combiner toutes les livraisons qui ont maintenant une dueDate + const allDeliveriesWithDueDate = [...deliveriesWithDueDate, ...deliveriesWithoutDueDate.filter((d: any) => d.dueDate)]; // Récupérer tous les fournisseurs pour le mapping du mode de paiement const allSuppliers = await storage.getSuppliers(); const supplierMap = new Map(allSuppliers.map((s: any) => [s.id, s])); // Formatter les échéances - const schedules = deliveriesWithDueDate.map((delivery: any) => { + const schedules = allDeliveriesWithDueDate.map((delivery: any) => { const supplier = supplierMap.get(delivery.supplierId); return { @@ -345,6 +451,7 @@ export async function registerRoutes(app: Express): Promise { }; }); + console.log(`📅 Total échéances retournées: ${schedules.length}`); res.json({ schedules }); } catch (error: any) { @@ -1338,8 +1445,9 @@ export async function registerRoutes(app: Express): Promise { ); if (result.exists && result.dueDate) { - data.dueDate = result.dueDate; - console.log(`📅 Date d'échéance récupérée depuis NocoDB: ${result.dueDate}`); + // Normaliser la date avant de la stocker + data.dueDate = normalizeDateString(result.dueDate); + console.log(`📅 Date d'échéance récupérée et normalisée: ${data.dueDate} (original: ${result.dueDate})`); } else { data.dueDate = null; console.log(`📅 Aucune date d'échéance trouvée dans NocoDB`); From 0b43d8446a73f6ff5b4c999e2e8622da4bdcbaa6 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:39:34 +0000 Subject: [PATCH 4/8] Improve payment schedule tracking with hybrid architecture and local caching Refactored the Payment Schedule module to use a hybrid architecture, storing due dates locally in the deliveries table and fetching/caching them from NocoDB during invoice verification. Implemented automatic date normalization for multiple formats and intelligent fallback to NocoDB. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fd7dd4a7-0d4d-436f-9597-17f7dcf2091a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/G9gVBac --- replit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/replit.md b/replit.md index b47d895..5a1a166 100644 --- a/replit.md +++ b/replit.md @@ -40,7 +40,7 @@ The platform is built with a clear separation of concerns, utilizing a modern fu - **Reporting:** Statistical reporting, including monthly summaries of deliveries. - **UI Harmonization:** All detail modals across modules (orders, deliveries, calendar) use a unified component for consistent information display. - **Analyse Vente (Sales Analysis):** A dedicated module for admins and directors that displays a configurable external URL/IP in an iframe. Configuration is managed via a dedicated tab in the Utilities page, allowing administrators to set or update the sales analysis dashboard URL. The menu is visible only to admin and directeur roles, ensuring proper access control. Includes intelligent error handling with informative messages when the URL is not configured. Features a refresh button to force complete iframe reloads. -- **Échéancier des Paiements (Payment Schedule):** A comprehensive payment deadline tracking system integrated with NocoDB. Displays monthly aggregations of supplier payment schedules with breakdown by payment method (Virement, Traite, Traite Magnétique, Chèque). Features include: role-based access (admin/director), group-based filtering with selectedStoreId, Zod validation for security, robust supplier name normalization (case/accent/whitespace handling), monthly statistics with payment method breakdowns, and configurable NocoDB column mapping for due dates. Requires configuration of nocodbDueDateColumnName in Groups settings and paymentMethod in Suppliers. **Security (November 2025)**: Implements strict authorization - directors can only access their assigned groups, preventing cross-group data access. +- **Échéancier des Paiements (Payment Schedule):** A comprehensive payment deadline tracking system with hybrid architecture for optimal performance. **Architecture Refactorization (November 2025):** Due dates are now stored locally in deliveries table (dueDate column) as the single source of truth, retrieved once during invoice verification from NocoDB and cached locally for fast queries. Features include: role-based access (admin/director), group-based filtering with selectedStoreId, intelligent fallback to NocoDB for historical deliveries without cached dates, automatic date normalization handling multiple formats (ISO, French DD/MM/YYYY, American MM/DD/YYYY), monthly aggregations with payment method breakdowns (Virement, Traite, Traite Magnétique, Chèque), Zod validation for security, and robust supplier name normalization. Requires configuration of nocodbDueDateColumnName in Groups settings and paymentMethod in Suppliers. **Data Flow:** Invoice verification → NocoDB (fetch dueDate) → deliveries (local storage) → payment-schedule (fast local reads with NocoDB fallback). **Security (November 2025)**: Implements strict authorization - directors can only access their assigned groups, preventing cross-group data access. ## External Dependencies - **API Météo:** Visual Crossing API From 3204f95c3787f52f325796dedd4392b66707ef6b Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:40:11 +0000 Subject: [PATCH 5/8] Transitioned from Plan to Build mode Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 153c98e8-b9e2-4cfc-8e44-aefe12100848 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/G9gVBac From cd600ff43a15c0f3d15fb5b13cf76ca878177210 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 16:44:06 +0000 Subject: [PATCH 6/8] Display invoice details and due date in reconciliation confirmation messages Adds a toast notification to the invoice reconciliation process to display the invoice reference, amount, and due date if available, enhancing user feedback and error detection for missing due dates. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 917cea46-8014-4981-b291-34484d4d4b10 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/G9gVBac --- client/src/pages/BLReconciliation.tsx | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index e2ba332..5092595 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -98,6 +98,52 @@ export default function BLReconciliation() { [variables.deliveryId]: result })); + // Toast de confirmation avec détails de la vérification + if (result.exists) { + // Formater la date d'échéance si présente + let dueDateText = ''; + if (result.dueDate) { + try { + const date = new Date(result.dueDate); + dueDateText = date.toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric' + }); + } catch (e) { + dueDateText = result.dueDate; // Afficher tel quel si le format n'est pas reconnu + } + } + + // Formater le montant avec garde contre NaN + const amountText = result.invoiceAmount ? + (() => { + const amount = parseFloat(result.invoiceAmount); + return isNaN(amount) ? 'Format invalide' : `${amount.toFixed(2)}€`; + })() : + 'Non disponible'; + + toast({ + title: "✅ Facture vérifiée avec succès", + description: ( +
+
Référence : {result.invoiceReference || 'Non disponible'}
+
Montant : {amountText}
+ {result.dueDate ? ( +
+ 📅 Échéance : {dueDateText} +
+ ) : ( +
+ ⚠️ Aucune date d'échéance trouvée +
+ )} +
+ ), + duration: 5000, + }); + } + // Auto-remplissage si facture trouvée via BL if (result.exists && result.matchType === 'bl_number' && result.invoiceReference) { // Auto-remplir les champs dans la livraison via API From 20e1276da89c068a0d84bbaf8ec19803ebff0816 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 17:04:15 +0000 Subject: [PATCH 7/8] Add error message for when a bill is not found during reconciliation Update BLReconciliation page to display a destructive toast notification with an error message when the bill is not found, including a default message if result.errorMessage is null or undefined. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ffea74b1-4f55-41ad-9874-ea7da36be493 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/Tx7N1IN --- client/src/pages/BLReconciliation.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 5092595..c7f8b62 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -100,6 +100,7 @@ export default function BLReconciliation() { // Toast de confirmation avec détails de la vérification if (result.exists) { + // Facture trouvée - afficher les détails // Formater la date d'échéance si présente let dueDateText = ''; if (result.dueDate) { @@ -142,6 +143,14 @@ export default function BLReconciliation() { ), duration: 5000, }); + } else { + // Facture non trouvée - afficher message d'erreur + toast({ + title: "⚠️ Facture non trouvée", + description: result.errorMessage || 'La facture n\'a pas été trouvée dans la base de données', + variant: "destructive", + duration: 5000, + }); } // Auto-remplissage si facture trouvée via BL From 1bf457be785307c94934b05f036e044affdc4445 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Mon, 3 Nov 2025 17:06:49 +0000 Subject: [PATCH 8/8] Add the due date column to the reconciliation page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a new 'Échéance' column to the BLReconciliation page and component, displaying the formatted due date for each delivery item. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: f756bd7d-b58c-4a1b-a974-4cde9dd2106f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/Tx7N1IN --- client/src/pages/BLReconciliation.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index c7f8b62..1bf7bab 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -844,6 +844,9 @@ export default function BLReconciliation() { Montant Fact. + + Échéance + Écart @@ -962,6 +965,17 @@ export default function BLReconciliation() { } + +
+ {delivery.dueDate ? ( + + {safeFormat(delivery.dueDate, 'dd/MM/yyyy')} + + ) : ( + - + )} +
+ {(() => { const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; @@ -1188,6 +1202,9 @@ export default function BLReconciliation() { Montant Fact. + + Échéance + Écart