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
This commit is contained in:
michaelschal committed 2025-11-03 16:28:41 +00:00
1 parent 84edfbfedd
commit 09c873eb06
3 files changed
+68 -80

No files matched your search

+8 -3
View File
@@ -242,6 +242,7 @@ export class InvoiceVerificationService {
invoiceReference?: string; invoiceReference?: string;
invoiceAmount?: number; invoiceAmount?: number;
supplierName?: string; supplierName?: string;
dueDate?: string | null;
fromCache?: boolean; fromCache?: boolean;
}> { }> {
try { try {
@@ -396,7 +397,8 @@ export class InvoiceVerificationService {
matchType: 'invoice_reference' as const, matchType: 'invoice_reference' as const,
invoiceReference: matchResult.data.invoice_reference || invoiceReference, invoiceReference: matchResult.data.invoice_reference || invoiceReference,
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), 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 // Sauvegarder en cache si succès
@@ -432,7 +434,8 @@ export class InvoiceVerificationService {
matchType: 'bl_number' as const, matchType: 'bl_number' as const,
invoiceReference: matchResult.data.invoice_reference || `BL_${invoiceReference}`, invoiceReference: matchResult.data.invoice_reference || `BL_${invoiceReference}`,
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), 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 // Sauvegarder en cache si succès
@@ -599,6 +602,7 @@ export class InvoiceVerificationService {
invoiceReference?: string; invoiceReference?: string;
invoiceAmount?: number; invoiceAmount?: number;
supplierName?: string; supplierName?: string;
dueDate?: string | null;
fromCache?: boolean; fromCache?: boolean;
}> { }> {
try { try {
@@ -795,7 +799,8 @@ export class InvoiceVerificationService {
matchType: 'bl_number' as const, matchType: 'bl_number' as const,
invoiceReference: matchResult.data[group.invoiceColumnName || 'RefFacture'] || `BL_${blNumber}`, invoiceReference: matchResult.data[group.invoiceColumnName || 'RefFacture'] || `BL_${blNumber}`,
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'), 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 // Sauvegarder en cache si succès
+59 -77
View File
@@ -309,92 +309,41 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
} }
// Récupérer le groupe avec la configuration NocoDB // Récupérer le groupe
const group = await storage.getGroup(groupId); const group = await storage.getGroup(groupId);
if (!group || !group.nocodbConfigId || !group.nocodbTableId) { if (!group) {
return res.json({ schedules: [], message: 'Pas de configuration NocoDB pour ce groupe' }); return res.json({ schedules: [], message: 'Groupe non trouvé' });
} }
// Récupérer la configuration NocoDB console.log('📅 Récupération échéances depuis deliveries:', { groupId, groupName: group.name });
const nocodbConfig = await storage.getNocodbConfig(group.nocodbConfigId);
if (!nocodbConfig) {
return res.json({ schedules: [], message: 'Configuration NocoDB non trouvée' });
}
// Vérifier que les colonnes nécessaires sont configurées // Récupérer toutes les livraisons du groupe avec échéance
if (!group.nocodbDueDateColumnName || !group.invoiceColumnName || !group.nocodbAmountColumnName) { const allDeliveries = await storage.getDeliveries();
return res.json({ const deliveriesWithDueDate = allDeliveries.filter(
schedules: [], (d: any) => d.groupId === groupId && d.dueDate && d.invoiceReference
message: 'Configuration incomplète: colonnes date échéance, facture ou montant manquantes' );
});
}
// Récupérer tous les enregistrements de NocoDB console.log(`📅 Trouvé ${deliveriesWithDueDate.length} livraisons avec échéance pour le groupe ${groupId}`);
const url = `${nocodbConfig.baseUrl}/api/v1/db/data/noco/${nocodbConfig.projectId}/${group.nocodbTableId}`;
console.log('📅 Récupération échéances NocoDB:', { // Récupérer tous les fournisseurs pour le mapping du mode de paiement
url,
groupId,
dueDateColumn: group.nocodbDueDateColumnName
});
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
const allSuppliers = await storage.getSuppliers(); const allSuppliers = await storage.getSuppliers();
const supplierMap = new Map(allSuppliers.map((s: any) => [s.id, s]));
// Fonction de normalisation pour comparer les noms de fournisseurs // Formatter les échéances
const normalizeName = (name: string) => { const schedules = deliveriesWithDueDate.map((delivery: any) => {
return name const supplier = supplierMap.get(delivery.supplierId);
.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 return {
const supplierMap = new Map(allSuppliers.map(s => [normalizeName(s.name), s])); id: delivery.id,
invoiceReference: delivery.invoiceReference,
// Traiter les données et filtrer les enregistrements avec une date d'échéance dueDate: delivery.dueDate,
const schedules = (data.list || []) amount: delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0,
.map((record: any) => { supplierName: supplier?.name || 'Fournisseur inconnu',
const dueDate = record[group.nocodbDueDateColumnName!]; paymentMethod: supplier?.paymentMethod || null,
const invoiceRef = record[group.invoiceColumnName!]; groupId: group.id,
const amount = record[group.nocodbAmountColumnName!]; groupName: group.name
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);
res.json({ schedules }); res.json({ schedules });
@@ -1374,6 +1323,39 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
} }
// 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); const updatedDelivery = await storage.updateDelivery(id, data);
console.log('✅ Delivery updated successfully:', { id, updatedDelivery }); console.log('✅ Delivery updated successfully:', { id, updatedDelivery });
+1
View File
@@ -119,6 +119,7 @@ export const deliveries = pgTable("deliveries", {
blAmount: decimal("bl_amount", { precision: 10, scale: 2 }), // Montant BL blAmount: decimal("bl_amount", { precision: 10, scale: 2 }), // Montant BL
invoiceReference: varchar("invoice_reference"), // Référence facture invoiceReference: varchar("invoice_reference"), // Référence facture
invoiceAmount: decimal("invoice_amount", { precision: 10, scale: 2 }), // Montant 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é reconciled: boolean("reconciled").default(false), // Rapprochement effectué
validatedAt: timestamp("validated_at"), // Date de validation de la livraison validatedAt: timestamp("validated_at"), // Date de validation de la livraison
// Champs pour le contrôle des livraisons (fournisseurs nécessitant un contrôle) // Champs pour le contrôle des livraisons (fournisseurs nécessitant un contrôle)