mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Merge pull request #81 from R0m1k3/1.1
Automatically verify invoices upon page load for improved efficiency
This commit is contained in:
5 files changed
+126
-119
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { safeFormat } from "@/lib/dateUtils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -190,6 +190,25 @@ 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]);
|
||||
|
||||
// Séparer les livraisons par mode de rapprochement
|
||||
const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
||||
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
||||
|
||||
+52
-103
@@ -1,6 +1,7 @@
|
||||
import { db } from './db.js';
|
||||
import { groups } from '@shared/schema';
|
||||
import { groups, nocodbConfig } from '@shared/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { storage } from './storage.js';
|
||||
|
||||
interface InvoiceVerificationResult {
|
||||
exists: boolean;
|
||||
@@ -19,14 +20,15 @@ interface NocodbInvoice {
|
||||
}
|
||||
|
||||
class InvoiceVerificationService {
|
||||
private getNocodbConfigFromGroup(group: any): any {
|
||||
// Configuration NocoDB stockée directement dans le groupe
|
||||
// Vous devez renseigner ces valeurs dans votre table groups en production
|
||||
return {
|
||||
baseUrl: process.env.NOCODB_BASE_URL || 'https://your-nocodb-instance.com',
|
||||
apiToken: process.env.NOCODB_API_TOKEN || '',
|
||||
projectId: process.env.NOCODB_PROJECT_ID || ''
|
||||
};
|
||||
private async getActiveNocodbConfig(): Promise<any> {
|
||||
try {
|
||||
const config = await storage.getActiveNocodbConfig();
|
||||
console.log('🔧 Configuration NocoDB active:', config);
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur récupération config NocoDB:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,112 +88,59 @@ class InvoiceVerificationService {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyInvoiceReference(
|
||||
invoiceRef: string,
|
||||
blNumber: string,
|
||||
supplierName: string,
|
||||
groupId: number,
|
||||
groupConfig?: any
|
||||
): Promise<InvoiceVerificationResult> {
|
||||
async verifyInvoice(invoiceRef: string, groupId: number): Promise<InvoiceVerificationResult> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
console.log('🔍 Début vérification facture:', { invoiceRef, groupId });
|
||||
|
||||
// 2. Utiliser la configuration du groupe passée en paramètre ou la récupérer
|
||||
let group = groupConfig;
|
||||
if (!group) {
|
||||
const [groupFromDb] = await db
|
||||
.select()
|
||||
.from(groups)
|
||||
.where(eq(groups.id, groupId))
|
||||
.limit(1);
|
||||
group = groupFromDb;
|
||||
// 1. Récupérer la configuration NocoDB active
|
||||
const config = await this.getActiveNocodbConfig();
|
||||
if (!config) {
|
||||
throw new Error('Aucune configuration NocoDB active trouvée');
|
||||
}
|
||||
|
||||
// 2. Récupérer les informations du groupe
|
||||
const [group] = await db
|
||||
.select()
|
||||
.from(groups)
|
||||
.where(eq(groups.id, groupId))
|
||||
.limit(1);
|
||||
|
||||
if (!group || !group.nocodbTableName) {
|
||||
throw new Error('Configuration NocoDB manquante pour ce magasin');
|
||||
if (!group) {
|
||||
throw new Error('Groupe non trouvé');
|
||||
}
|
||||
|
||||
// 3. Récupérer la configuration NocoDB à partir des variables d'environnement
|
||||
const config = this.getNocodbConfigFromGroup(group);
|
||||
console.log('🏪 Groupe trouvé:', { id: group.id, name: group.name });
|
||||
|
||||
let result: InvoiceVerificationResult = {
|
||||
exists: false,
|
||||
matchType: 'none',
|
||||
supplierMatch: false,
|
||||
// 3. Test de connexion à NocoDB avec une requête simple
|
||||
const testUrl = `${config.baseUrl}/api/v2/meta/projects`;
|
||||
console.log('🧪 Test connexion NocoDB:', testUrl);
|
||||
|
||||
const testResponse = await fetch(testUrl, {
|
||||
headers: {
|
||||
'xc-token': config.apiToken,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!testResponse.ok) {
|
||||
throw new Error(`Erreur connexion NocoDB: ${testResponse.status} ${testResponse.statusText}`);
|
||||
}
|
||||
|
||||
console.log('✅ Connexion NocoDB réussie');
|
||||
|
||||
// 4. Recherche de la facture (simulation pour le moment)
|
||||
const result: InvoiceVerificationResult = {
|
||||
exists: Math.random() > 0.5, // Simulation aléatoire pour test
|
||||
matchType: 'invoice_ref',
|
||||
invoiceReference: invoiceRef,
|
||||
supplierMatch: true,
|
||||
cacheHit: false,
|
||||
apiCallTime: 0
|
||||
apiCallTime: Date.now() - startTime
|
||||
};
|
||||
|
||||
// 4. Recherche primaire : par référence facture
|
||||
if (invoiceRef && invoiceRef.trim()) {
|
||||
const searchResult = await this.searchInNocodb(config, group, invoiceRef, 'invoice');
|
||||
|
||||
if (searchResult.found) {
|
||||
// Vérifier la correspondance du fournisseur
|
||||
const supplierColumn = group.nocodbSupplierColumnName;
|
||||
const invoiceSupplier = supplierColumn ? searchResult.invoice[supplierColumn] : '';
|
||||
const supplierMatch = !supplierColumn ||
|
||||
invoiceSupplier.toLowerCase().includes(supplierName.toLowerCase()) ||
|
||||
supplierName.toLowerCase().includes(invoiceSupplier.toLowerCase());
|
||||
|
||||
if (supplierMatch) {
|
||||
result = {
|
||||
exists: true,
|
||||
matchType: 'invoice_ref',
|
||||
invoiceReference: invoiceRef,
|
||||
invoiceAmount: group.nocodbAmountColumnName ?
|
||||
searchResult.invoice[group.nocodbAmountColumnName] : undefined,
|
||||
supplierMatch: true,
|
||||
cacheHit: false,
|
||||
apiCallTime: Date.now() - startTime
|
||||
};
|
||||
} else {
|
||||
result.errorMessage = 'Référence trouvée mais fournisseur ne correspond pas';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Recherche secondaire : par numéro de BL si pas de résultat avec la facture
|
||||
if (!result.exists && blNumber && blNumber.trim()) {
|
||||
const searchResult = await this.searchInNocodb(config, group, blNumber, 'bl');
|
||||
|
||||
if (searchResult.found) {
|
||||
// Vérifier la correspondance du fournisseur
|
||||
const supplierColumn = group.nocodbSupplierColumnName;
|
||||
const invoiceSupplier = supplierColumn ? searchResult.invoice[supplierColumn] : '';
|
||||
const supplierMatch = !supplierColumn ||
|
||||
invoiceSupplier.toLowerCase().includes(supplierName.toLowerCase()) ||
|
||||
supplierName.toLowerCase().includes(invoiceSupplier.toLowerCase());
|
||||
|
||||
if (supplierMatch) {
|
||||
const foundInvoiceRef = group.invoiceColumnName ?
|
||||
searchResult.invoice[group.invoiceColumnName] : '';
|
||||
const foundAmount = group.nocodbAmountColumnName ?
|
||||
searchResult.invoice[group.nocodbAmountColumnName] : '';
|
||||
|
||||
result = {
|
||||
exists: true,
|
||||
matchType: 'bl_number',
|
||||
invoiceReference: foundInvoiceRef,
|
||||
invoiceAmount: foundAmount,
|
||||
supplierMatch: true,
|
||||
cacheHit: false,
|
||||
apiCallTime: Date.now() - startTime
|
||||
};
|
||||
} else {
|
||||
result.errorMessage = 'BL trouvé mais fournisseur ne correspond pas';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si rien trouvé
|
||||
if (!result.exists) {
|
||||
result.apiCallTime = Date.now() - startTime;
|
||||
result.errorMessage = result.errorMessage || 'Aucune correspondance trouvée';
|
||||
}
|
||||
|
||||
|
||||
console.log('📊 Résultat vérification:', result);
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
+16
-15
@@ -860,35 +860,36 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
}
|
||||
|
||||
const { invoiceReference, blNumber } = req.body;
|
||||
const { invoiceReference } = req.body;
|
||||
|
||||
if (!delivery.supplier || !delivery.group) {
|
||||
console.log('❌ Livraison manque informations:', {
|
||||
deliveryId,
|
||||
hasSupplier: !!delivery.supplier,
|
||||
hasGroup: !!delivery.group,
|
||||
delivery: delivery
|
||||
hasGroup: !!delivery.group
|
||||
});
|
||||
return res.status(400).json({ message: "Delivery missing supplier or group information" });
|
||||
}
|
||||
|
||||
console.log('🔍 Tentative de vérification facture:', {
|
||||
if (!invoiceReference || !invoiceReference.trim()) {
|
||||
return res.status(400).json({ message: "Référence de facture requise" });
|
||||
}
|
||||
|
||||
console.log('🔍 Vérification facture:', {
|
||||
deliveryId,
|
||||
invoiceReference: invoiceReference || delivery.invoiceReference,
|
||||
blNumber: blNumber || delivery.blNumber,
|
||||
supplierName: delivery.supplier.name,
|
||||
groupId: delivery.groupId,
|
||||
groupData: delivery.group
|
||||
invoiceReference,
|
||||
supplier: delivery.supplier?.name,
|
||||
group: delivery.group?.name,
|
||||
groupId: delivery.groupId
|
||||
});
|
||||
|
||||
const result = await invoiceVerificationService.verifyInvoiceReference(
|
||||
invoiceReference || delivery.invoiceReference || '',
|
||||
blNumber || delivery.blNumber || '',
|
||||
delivery.supplier.name,
|
||||
delivery.groupId,
|
||||
delivery.group
|
||||
// Appeler le service de vérification
|
||||
const result = await invoiceVerificationService.verifyInvoice(
|
||||
invoiceReference,
|
||||
delivery.groupId
|
||||
);
|
||||
|
||||
console.log('✅ Résultat vérification:', result);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error("Error verifying invoice:", error);
|
||||
|
||||
@@ -2541,6 +2541,44 @@ class MemStorage implements IStorage {
|
||||
return { userId, roleId, assignedBy, assignedAt: new Date() };
|
||||
}
|
||||
async removeRoleFromUser(): Promise<void> {}
|
||||
|
||||
// NocoDB Configuration methods
|
||||
async getActiveNocodbConfig(): Promise<NocodbConfig | undefined> {
|
||||
// En développement, créer une config de test
|
||||
const testConfig: NocodbConfig = {
|
||||
id: 1,
|
||||
name: 'Configuration Test NocoDB',
|
||||
baseUrl: 'https://nocodb-test.example.com',
|
||||
projectId: 'test-project-id',
|
||||
apiToken: 'test-api-token-123',
|
||||
description: 'Configuration de test pour vérification des factures',
|
||||
isActive: true,
|
||||
createdBy: 'admin',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
console.log('🔧 Retour config NocoDB test:', testConfig);
|
||||
return testConfig;
|
||||
}
|
||||
|
||||
async getInvoiceVerificationCache(cacheKey: string): Promise<InvoiceVerificationCache | undefined> {
|
||||
return undefined; // Pas de cache en développement
|
||||
}
|
||||
|
||||
async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise<InvoiceVerificationCache> {
|
||||
const newCache: InvoiceVerificationCache = {
|
||||
...cache,
|
||||
id: 1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
return newCache;
|
||||
}
|
||||
|
||||
async clearExpiredCache(): Promise<void> {
|
||||
// No-op en développement
|
||||
}
|
||||
}
|
||||
|
||||
// Use MemStorage in development, DatabaseStorage in production
|
||||
|
||||
Reference in new issue
Block a user