mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-19 21:18:25 +02:00
5 files changed
+151
-30
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -69,11 +69,12 @@ export default function BLReconciliation() {
|
|||||||
|
|
||||||
// Fonction de vérification de facture
|
// Fonction de vérification de facture
|
||||||
const verifyInvoiceMutation = useMutation({
|
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 {
|
try {
|
||||||
const result = await apiRequest(`/api/deliveries/${deliveryId}/verify-invoice`, 'POST', {
|
const result = await apiRequest(`/api/deliveries/${deliveryId}/verify-invoice`, 'POST', {
|
||||||
invoiceReference,
|
invoiceReference,
|
||||||
blNumber
|
blNumber,
|
||||||
|
forceRefresh: forceRefresh || false
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -129,16 +130,15 @@ export default function BLReconciliation() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Fonction pour déclencher la vérification
|
// Fonction pour déclencher la vérification
|
||||||
const handleVerifyInvoice = (delivery: any) => {
|
const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => {
|
||||||
console.log('🔍 Debug vérification:', {
|
if (!delivery.invoiceReference?.trim()) {
|
||||||
delivery: delivery,
|
toast({
|
||||||
group: delivery.group,
|
title: "Référence manquante",
|
||||||
webhookUrl: delivery.group?.webhookUrl,
|
description: "Veuillez saisir une référence de facture avant la vérification",
|
||||||
nocodbTableName: delivery.group?.nocodbTableName,
|
variant: "destructive",
|
||||||
invoiceColumnName: delivery.group?.invoiceColumnName,
|
});
|
||||||
nocodbConfigId: delivery.group?.nocodbConfigId,
|
return;
|
||||||
supplier: delivery.supplier
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId && !delivery.group?.webhookUrl) {
|
if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId && !delivery.group?.webhookUrl) {
|
||||||
toast({
|
toast({
|
||||||
@@ -154,7 +154,36 @@ export default function BLReconciliation() {
|
|||||||
verifyInvoiceMutation.mutate({
|
verifyInvoiceMutation.mutate({
|
||||||
deliveryId: delivery.id,
|
deliveryId: delivery.id,
|
||||||
invoiceReference: delivery.invoiceReference,
|
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,7 +217,7 @@ export default function BLReconciliation() {
|
|||||||
enabled: !!user
|
enabled: !!user
|
||||||
});
|
});
|
||||||
|
|
||||||
// VÉRIFICATION AUTOMATIQUE AU CHARGEMENT
|
// VÉRIFICATION AUTOMATIQUE AU CHARGEMENT avec système de cache
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!deliveriesWithBL.length || !suppliers.length) return;
|
if (!deliveriesWithBL.length || !suppliers.length) return;
|
||||||
|
|
||||||
@@ -199,10 +228,10 @@ export default function BLReconciliation() {
|
|||||||
if (delivery.invoiceReference && !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id)) {
|
if (delivery.invoiceReference && !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id)) {
|
||||||
console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference);
|
console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference);
|
||||||
|
|
||||||
// Délai pour éviter de surcharger le serveur
|
// Délai pour éviter de surcharger le serveur - le cache évitera les appels inutiles
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handleVerifyInvoice(delivery);
|
handleVerifyInvoice(delivery, false); // Pas de force refresh, utilise le cache
|
||||||
}, Math.random() * 2000); // Délai aléatoire entre 0 et 2 secondes
|
}, Math.random() * 1000); // Délai aléatoire entre 0 et 1 seconde
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]);
|
}, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]);
|
||||||
@@ -509,6 +538,15 @@ export default function BLReconciliation() {
|
|||||||
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300 bg-blue-50">
|
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300 bg-blue-50">
|
||||||
{automaticReconciliationDeliveries.length} automatiques
|
{automaticReconciliationDeliveries.length} automatiques
|
||||||
</Badge>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,82 @@ import { storage } from "./storage.js";
|
|||||||
*/
|
*/
|
||||||
export class InvoiceVerificationService {
|
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é
|
* 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;
|
exists: boolean;
|
||||||
matchType: 'invoice_reference' | 'bl_number' | 'none';
|
matchType: 'invoice_reference' | 'bl_number' | 'none';
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
invoiceReference?: string;
|
invoiceReference?: string;
|
||||||
invoiceAmount?: number;
|
invoiceAmount?: number;
|
||||||
supplierName?: string;
|
supplierName?: string;
|
||||||
|
fromCache?: boolean;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 Début vérification facture:', { invoiceReference, groupId });
|
console.log('🔍 Début vérification facture:', { invoiceReference, groupId, forceRefresh });
|
||||||
|
|
||||||
if (!invoiceReference || !invoiceReference.trim()) {
|
if (!invoiceReference || !invoiceReference.trim()) {
|
||||||
return {
|
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
|
// Récupérer la configuration du groupe
|
||||||
const group = await storage.getGroup(groupId);
|
const group = await storage.getGroup(groupId);
|
||||||
if (!group) {
|
if (!group) {
|
||||||
@@ -126,13 +197,17 @@ export class InvoiceVerificationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (matchResult.found) {
|
if (matchResult.found) {
|
||||||
return {
|
const result = {
|
||||||
exists: true,
|
exists: true,
|
||||||
matchType: 'invoice_reference',
|
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'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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
|
// 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) {
|
if (matchResult.found) {
|
||||||
return {
|
const result = {
|
||||||
exists: true,
|
exists: true,
|
||||||
matchType: 'bl_number',
|
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'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sauvegarder en cache si succès
|
||||||
|
await this.saveToCache(invoiceReference, groupId, result, result.supplierName);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -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) {
|
if (!delivery.supplier || !delivery.group) {
|
||||||
console.log('❌ Livraison manque informations:', {
|
console.log('❌ Livraison manque informations:', {
|
||||||
@@ -886,7 +886,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Appeler le service de vérification
|
// Appeler le service de vérification
|
||||||
const result = await invoiceVerificationService.verifyInvoice(
|
const result = await invoiceVerificationService.verifyInvoice(
|
||||||
invoiceReference,
|
invoiceReference,
|
||||||
delivery.groupId
|
delivery.groupId,
|
||||||
|
forceRefresh || false
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ Résultat vérification:', result);
|
console.log('✅ Résultat vérification:', result);
|
||||||
|
|||||||
+8
-5
@@ -651,7 +651,13 @@ export class DatabaseStorage implements IStorage {
|
|||||||
.from(orders)
|
.from(orders)
|
||||||
.where(pendingWhereCondition);
|
.where(pendingWhereCondition);
|
||||||
|
|
||||||
// Calculer les totaux de palettes et colis du mois
|
// Calculer les totaux de palettes et colis du mois (SEULEMENT livraisons delivered)
|
||||||
|
let deliveredWhereCondition = and(
|
||||||
|
deliveriesWhereCondition,
|
||||||
|
eq(deliveries.status, 'delivered'),
|
||||||
|
isNotNull(deliveries.deliveredDate)
|
||||||
|
);
|
||||||
|
|
||||||
const deliveriesStatsResult = await db
|
const deliveriesStatsResult = await db
|
||||||
.select({
|
.select({
|
||||||
totalPalettes: sql<number>`COALESCE(SUM(CAST(${deliveries.quantity} as INTEGER)), 0)`,
|
totalPalettes: sql<number>`COALESCE(SUM(CAST(${deliveries.quantity} as INTEGER)), 0)`,
|
||||||
@@ -659,10 +665,7 @@ export class DatabaseStorage implements IStorage {
|
|||||||
avgDelay: sql<number>`COALESCE(AVG(EXTRACT(DAY FROM (${deliveries.deliveredDate} - ${deliveries.scheduledDate}))), 0)`
|
avgDelay: sql<number>`COALESCE(AVG(EXTRACT(DAY FROM (${deliveries.deliveredDate} - ${deliveries.scheduledDate}))), 0)`
|
||||||
})
|
})
|
||||||
.from(deliveries)
|
.from(deliveries)
|
||||||
.where(and(
|
.where(deliveredWhereCondition);
|
||||||
deliveriesWhereCondition,
|
|
||||||
isNotNull(deliveries.deliveredDate)
|
|
||||||
));
|
|
||||||
|
|
||||||
const ordersCount = Number(ordersResult[0]?.count || 0);
|
const ordersCount = Number(ordersResult[0]?.count || 0);
|
||||||
const deliveriesCount = Number(deliveriesResult[0]?.count || 0);
|
const deliveriesCount = Number(deliveriesResult[0]?.count || 0);
|
||||||
|
|||||||
Reference in new issue
Block a user