Improve invoice verification by adding caching and manual refresh functionality

Implement an invoice verification cache to avoid redundant checks, introduce a manual refresh button to trigger verification on demand, and modify the API to accept a forceRefresh parameter.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/7c468936-2a88-4686-ac0a-84921a45222d/Sh7jLhH
This commit is contained in:
michaelschal committed 2025-08-13 13:46:11 +00:00
1 parent 414c32f110
commit 7fb037bf53
4 files changed
+141 -40

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+53 -32
View File
@@ -69,11 +69,12 @@ export default function BLReconciliation() {
// Fonction de vérification de facture
const verifyInvoiceMutation = useMutation({
mutationFn: async ({ deliveryId, invoiceReference, blNumber }: { deliveryId: number; invoiceReference?: string; blNumber?: string }) => {
mutationFn: async ({ deliveryId, invoiceReference, blNumber, forceRefresh }: { deliveryId: number; invoiceReference?: string; blNumber?: string; forceRefresh?: boolean }) => {
try {
const result = await apiRequest(`/api/deliveries/${deliveryId}/verify-invoice`, 'POST', {
invoiceReference,
blNumber
blNumber,
forceRefresh: forceRefresh || false
});
return result;
} catch (error: any) {
@@ -129,17 +130,16 @@ export default function BLReconciliation() {
});
// Fonction pour déclencher la vérification
const handleVerifyInvoice = (delivery: any) => {
console.log('🔍 Debug vérification:', {
delivery: delivery,
group: delivery.group,
webhookUrl: delivery.group?.webhookUrl,
nocodbTableName: delivery.group?.nocodbTableName,
invoiceColumnName: delivery.group?.invoiceColumnName,
nocodbConfigId: delivery.group?.nocodbConfigId,
supplier: delivery.supplier
});
const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => {
if (!delivery.invoiceReference?.trim()) {
toast({
title: "Référence manquante",
description: "Veuillez saisir une référence de facture avant la vérification",
variant: "destructive",
});
return;
}
if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId && !delivery.group?.webhookUrl) {
toast({
title: "Vérification non disponible",
@@ -154,7 +154,36 @@ export default function BLReconciliation() {
verifyInvoiceMutation.mutate({
deliveryId: delivery.id,
invoiceReference: delivery.invoiceReference,
blNumber: delivery.blNumber
blNumber: delivery.blNumber,
forceRefresh
});
};
// Fonction pour vérifier toutes les factures avec un bouton
const handleVerifyAllInvoices = () => {
const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery =>
delivery.invoiceReference?.trim() &&
(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl)
);
if (deliveriesToVerify.length === 0) {
toast({
title: "Aucune facture à vérifier",
description: "Aucune livraison avec référence de facture trouvée",
});
return;
}
deliveriesToVerify.forEach((delivery, index) => {
// Délai échelonné pour éviter la surcharge
setTimeout(() => {
handleVerifyInvoice(delivery, true); // Force refresh pour toutes
}, index * 200); // 200ms entre chaque vérification
});
toast({
title: "Vérification lancée",
description: `Vérification de ${deliveriesToVerify.length} facture(s) en cours...`,
});
};
@@ -188,24 +217,7 @@ export default function BLReconciliation() {
enabled: !!user
});
// VÉRIFICATION AUTOMATIQUE AU CHARGEMENT
useEffect(() => {
if (!deliveriesWithBL.length || !suppliers.length) return;
console.log('🔄 Déclenchement vérifications automatiques...');
deliveriesWithBL.forEach((delivery: any) => {
// Vérifier seulement si on a une référence de facture et pas déjà de résultat
if (delivery.invoiceReference && !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id)) {
console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference);
// Délai pour éviter de surcharger le serveur
setTimeout(() => {
handleVerifyInvoice(delivery);
}, Math.random() * 2000); // Délai aléatoire entre 0 et 2 secondes
}
});
}, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]);
// Plus de vérification automatique - seulement manuelle via bouton
// Séparer les livraisons par mode de rapprochement
const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
@@ -509,6 +521,15 @@ export default function BLReconciliation() {
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300 bg-blue-50">
{automaticReconciliationDeliveries.length} automatiques
</Badge>
<Button
onClick={handleVerifyAllInvoices}
variant="outline"
size="sm"
className="text-xs sm:text-sm bg-green-50 border-green-300 text-green-700 hover:bg-green-100"
>
<Search className="w-4 h-4 mr-1" />
Vérifier toutes les factures
</Button>
</div>
</div>
+85 -6
View File
@@ -6,19 +6,82 @@ import { storage } from "./storage.js";
*/
export class InvoiceVerificationService {
/**
* Générer une clé de cache unique
*/
private generateCacheKey(invoiceReference: string, groupId: number): string {
return `${groupId}_${invoiceReference.trim().toLowerCase()}`;
}
/**
* Vérifier dans le cache d'abord
*/
async checkCache(invoiceReference: string, groupId: number): Promise<any | null> {
try {
const cacheKey = this.generateCacheKey(invoiceReference, groupId);
const cached = await storage.getInvoiceVerificationCache(cacheKey);
if (cached && new Date() < new Date(cached.expiresAt)) {
console.log('💾 Cache hit pour:', { invoiceReference, groupId });
return {
exists: cached.exists,
matchType: cached.matchType,
errorMessage: cached.errorMessage,
invoiceReference: cached.invoiceReference,
supplierName: cached.supplierName,
fromCache: true
};
}
return null;
} catch (error) {
console.error('❌ Erreur lecture cache:', error);
return null;
}
}
/**
* Sauvegarder dans le cache
*/
async saveToCache(invoiceReference: string, groupId: number, result: any, supplierName?: string): Promise<void> {
try {
const cacheKey = this.generateCacheKey(invoiceReference, groupId);
const expiresAt = new Date();
expiresAt.setHours(expiresAt.getHours() + 24); // Cache pendant 24h
await storage.saveInvoiceVerificationCache({
cacheKey,
groupId,
invoiceReference,
supplierName: supplierName || null,
exists: result.exists,
matchType: result.matchType,
errorMessage: result.errorMessage || null,
cacheHit: false,
apiCallTime: null,
expiresAt
});
console.log('💾 Résultat sauvé en cache:', { invoiceReference, groupId, exists: result.exists });
} catch (error) {
console.error('❌ Erreur sauvegarde cache:', error);
}
}
/**
* Vérifie une référence de facture pour un groupe donné
*/
async verifyInvoice(invoiceReference: string, groupId: number): Promise<{
async verifyInvoice(invoiceReference: string, groupId: number, forceRefresh: boolean = false): Promise<{
exists: boolean;
matchType: 'invoice_reference' | 'bl_number' | 'none';
errorMessage?: string;
invoiceReference?: string;
invoiceAmount?: number;
supplierName?: string;
fromCache?: boolean;
}> {
try {
console.log('🔍 Début vérification facture:', { invoiceReference, groupId });
console.log('🔍 Début vérification facture:', { invoiceReference, groupId, forceRefresh });
if (!invoiceReference || !invoiceReference.trim()) {
return {
@@ -28,6 +91,14 @@ export class InvoiceVerificationService {
};
}
// Vérifier le cache d'abord (sauf si refresh forcé)
if (!forceRefresh) {
const cachedResult = await this.checkCache(invoiceReference, groupId);
if (cachedResult) {
return cachedResult;
}
}
// Récupérer la configuration du groupe
const group = await storage.getGroup(groupId);
if (!group) {
@@ -126,13 +197,17 @@ export class InvoiceVerificationService {
);
if (matchResult.found) {
return {
const result = {
exists: true,
matchType: 'invoice_reference',
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'
};
// Sauvegarder en cache si succès
await this.saveToCache(invoiceReference, groupId, result, result.supplierName);
return result;
}
// Si pas trouvé par référence de facture, chercher par numéro de BL
@@ -145,13 +220,17 @@ export class InvoiceVerificationService {
);
if (matchResult.found) {
return {
const result = {
exists: true,
matchType: 'bl_number',
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'
};
// Sauvegarder en cache si succès
await this.saveToCache(invoiceReference, groupId, result, result.supplierName);
return result;
}
}
+3 -2
View File
@@ -860,7 +860,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
}
const { invoiceReference } = req.body;
const { invoiceReference, forceRefresh } = req.body;
if (!delivery.supplier || !delivery.group) {
console.log('❌ Livraison manque informations:', {
@@ -886,7 +886,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Appeler le service de vérification
const result = await invoiceVerificationService.verifyInvoice(
invoiceReference,
delivery.groupId
delivery.groupId,
forceRefresh || false
);
console.log('✅ Résultat vérification:', result);