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
+69 -81

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
+60 -78
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:', {
url,
groupId,
dueDateColumn: group.nocodbDueDateColumnName
});
const response = await fetch(url, { // Récupérer tous les fournisseurs pour le mapping du mode de paiement
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
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]));
// Traiter les données et filtrer les enregistrements avec une date d'échéance // Formatter les échéances
const schedules = (data.list || []) const schedules = deliveriesWithDueDate.map((delivery: any) => {
.map((record: any) => { const supplier = supplierMap.get(delivery.supplierId);
const dueDate = record[group.nocodbDueDateColumnName!];
const invoiceRef = record[group.invoiceColumnName!]; return {
const amount = record[group.nocodbAmountColumnName!]; id: delivery.id,
const supplierName = record[group.nocodbSupplierColumnName || 'Fournisseur']; invoiceReference: delivery.invoiceReference,
dueDate: delivery.dueDate,
if (!dueDate) return null; amount: delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0,
supplierName: supplier?.name || 'Fournisseur inconnu',
// Chercher le fournisseur correspondant avec normalisation paymentMethod: supplier?.paymentMethod || null,
const normalizedSupplierName = normalizeName(supplierName || ''); groupId: group.id,
const supplier = supplierMap.get(normalizedSupplierName); groupName: group.name
};
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)