mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-20 13:38:15 +02:00
Merge pull request #166 from R0m1k3/1.1
Allow invoice verification using delivery's bill of lading number
This commit is contained in:
3 files changed
+379
-34
No files matched your search
@@ -135,10 +135,14 @@ export default function BLReconciliation() {
|
|||||||
|
|
||||||
// Fonction pour déclencher la vérification
|
// Fonction pour déclencher la vérification
|
||||||
const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => {
|
const handleVerifyInvoice = (delivery: any, forceRefresh: boolean = false) => {
|
||||||
if (!delivery.invoiceReference?.trim()) {
|
// Accepter soit une référence de facture soit un numéro BL
|
||||||
|
const hasInvoiceRef = delivery.invoiceReference?.trim();
|
||||||
|
const hasBlNumber = delivery.blNumber?.trim();
|
||||||
|
|
||||||
|
if (!hasInvoiceRef && !hasBlNumber) {
|
||||||
toast({
|
toast({
|
||||||
title: "Référence manquante",
|
title: "Référence manquante",
|
||||||
description: "Veuillez saisir une référence de facture avant la vérification",
|
description: "Veuillez saisir une référence de facture ou un numéro BL avant la vérification",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -152,6 +156,15 @@ export default function BLReconciliation() {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('🔍 Déclenchement vérification:', {
|
||||||
|
deliveryId: delivery.id,
|
||||||
|
hasInvoiceRef,
|
||||||
|
hasBlNumber,
|
||||||
|
invoiceReference: delivery.invoiceReference,
|
||||||
|
blNumber: delivery.blNumber,
|
||||||
|
supplier: delivery.supplier?.name
|
||||||
|
});
|
||||||
|
|
||||||
setVerifyingDeliveries(prev => new Set(prev).add(delivery.id));
|
setVerifyingDeliveries(prev => new Set(prev).add(delivery.id));
|
||||||
|
|
||||||
@@ -166,14 +179,14 @@ export default function BLReconciliation() {
|
|||||||
// Fonction pour vérifier toutes les factures avec un bouton
|
// Fonction pour vérifier toutes les factures avec un bouton
|
||||||
const handleVerifyAllInvoices = () => {
|
const handleVerifyAllInvoices = () => {
|
||||||
const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery =>
|
const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery =>
|
||||||
delivery.invoiceReference?.trim() &&
|
(delivery.invoiceReference?.trim() || delivery.blNumber?.trim()) &&
|
||||||
(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl)
|
(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (deliveriesToVerify.length === 0) {
|
if (deliveriesToVerify.length === 0) {
|
||||||
toast({
|
toast({
|
||||||
title: "Aucune facture à vérifier",
|
title: "Aucune facture à vérifier",
|
||||||
description: "Aucune livraison avec référence de facture trouvée",
|
description: "Aucune livraison avec référence de facture ou numéro BL trouvée",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -187,7 +200,7 @@ export default function BLReconciliation() {
|
|||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Vérification lancée",
|
title: "Vérification lancée",
|
||||||
description: `Vérification de ${deliveriesToVerify.length} facture(s) en cours...`,
|
description: `Vérification de ${deliveriesToVerify.length} facture(s)/BL en cours...`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,10 +244,16 @@ export default function BLReconciliation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deliveriesWithBL.forEach((delivery: any) => {
|
deliveriesWithBL.forEach((delivery: any) => {
|
||||||
// Vérifier seulement si on a une référence de facture et pas déjà de résultat
|
// Vérifier seulement si on a une référence de facture ou un BL et pas déjà de résultat
|
||||||
if (delivery.invoiceReference && !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id)) {
|
const hasVerifiableData = delivery.invoiceReference || delivery.blNumber;
|
||||||
|
const notAlreadyProcessed = !verificationResults[delivery.id] && !verifyingDeliveries.has(delivery.id);
|
||||||
|
|
||||||
|
if (hasVerifiableData && notAlreadyProcessed) {
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, delivery.invoiceReference);
|
console.log(`🔍 Vérification auto pour livraison ${delivery.id}:`, {
|
||||||
|
invoiceRef: delivery.invoiceReference,
|
||||||
|
blNumber: delivery.blNumber
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Délai pour éviter de surcharger le serveur - le cache évitera les appels inutiles
|
// Délai pour éviter de surcharger le serveur - le cache évitera les appels inutiles
|
||||||
|
|||||||
+323
-17
@@ -414,29 +414,335 @@ export class InvoiceVerificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche par numéro de BL
|
* Vérifie une facture par numéro de BL et nom de fournisseur
|
||||||
*/
|
*/
|
||||||
async searchByBLNumber(blNumber: string, groupId: number): Promise<any> {
|
async verifyInvoiceByBL(blNumber: string, supplierName: string, groupId: number, forceRefresh: boolean = false): Promise<{
|
||||||
console.log('🔍 Recherche par BL:', { blNumber, groupId });
|
exists: boolean;
|
||||||
|
matchType: 'invoice_reference' | 'bl_number' | 'none';
|
||||||
// Pour le développement, simuler une recherche
|
errorMessage?: string;
|
||||||
if (process.env.NODE_ENV === 'development') {
|
invoiceReference?: string;
|
||||||
if (blNumber && blNumber.trim()) {
|
invoiceAmount?: number;
|
||||||
|
supplierName?: string;
|
||||||
|
fromCache?: boolean;
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
console.log('🔍 Début vérification facture par BL:', { blNumber, supplierName, groupId, forceRefresh });
|
||||||
|
|
||||||
|
if (!blNumber || !blNumber.trim()) {
|
||||||
return {
|
return {
|
||||||
found: true,
|
exists: false,
|
||||||
data: {
|
matchType: 'none',
|
||||||
invoiceReference: `FACT_${blNumber}`,
|
errorMessage: 'Numéro BL vide'
|
||||||
amount: 156.78,
|
|
||||||
supplier: 'Fournisseur BL'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Générer une clé de cache pour le BL
|
||||||
|
const cacheKey = `bl_${groupId}_${blNumber.trim().toLowerCase()}_${supplierName.toLowerCase()}`;
|
||||||
|
|
||||||
|
// Vérifier le cache d'abord (sauf si refresh forcé)
|
||||||
|
if (!forceRefresh) {
|
||||||
|
try {
|
||||||
|
const cached = await storage.getInvoiceVerificationCache(cacheKey);
|
||||||
|
if (cached && new Date() < new Date(cached.expiresAt)) {
|
||||||
|
console.log('💾 Cache hit pour BL:', { blNumber, groupId });
|
||||||
|
return {
|
||||||
|
exists: cached.exists,
|
||||||
|
matchType: cached.matchType as 'invoice_reference' | 'bl_number' | 'none',
|
||||||
|
errorMessage: cached.errorMessage || undefined,
|
||||||
|
invoiceReference: cached.invoiceReference || undefined,
|
||||||
|
supplierName: cached.supplierName || undefined,
|
||||||
|
fromCache: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur lecture cache BL:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer la configuration du groupe
|
||||||
|
const group = await storage.getGroup(groupId);
|
||||||
|
if (!group) {
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: 'Groupe non trouvé'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔧 Configuration groupe pour BL:', {
|
||||||
|
groupName: group.name,
|
||||||
|
hasNocodbConfig: !!group.nocodbConfigId,
|
||||||
|
hasTableName: !!group.nocodbTableName,
|
||||||
|
hasWebhook: !!group.webhookUrl,
|
||||||
|
blColumnName: group.nocodbBlColumnName
|
||||||
|
});
|
||||||
|
|
||||||
|
// Si pas de configuration NocoDB, retourner un résultat par défaut
|
||||||
|
if (!group.nocodbConfigId && !group.nocodbTableName && !group.webhookUrl) {
|
||||||
|
console.log('⚠️ Pas de configuration NocoDB pour ce groupe');
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: 'Configuration NocoDB manquante pour ce magasin'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour le développement, simuler une vérification
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('🔧 Mode développement - simulation vérification BL');
|
||||||
|
|
||||||
|
if (blNumber && blNumber.trim()) {
|
||||||
|
const result = {
|
||||||
|
exists: true,
|
||||||
|
matchType: 'bl_number' as const,
|
||||||
|
invoiceReference: `FACT_${blNumber}`,
|
||||||
|
invoiceAmount: 156.78,
|
||||||
|
supplierName: supplierName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sauvegarder en cache
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||||
|
|
||||||
|
await storage.saveInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: result.invoiceReference,
|
||||||
|
supplierName: supplierName,
|
||||||
|
exists: result.exists,
|
||||||
|
matchType: result.matchType,
|
||||||
|
errorMessage: null,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: null,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur sauvegarde cache BL:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: 'BL non trouvé (mode développement)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// En production, faire l'appel réel à NocoDB
|
||||||
|
console.log('🔍 Vérification BL NocoDB en production...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Récupérer la configuration NocoDB active
|
||||||
|
const nocodbConfig = await storage.getActiveNocodbConfig();
|
||||||
|
if (!nocodbConfig) {
|
||||||
|
console.log('⚠️ Pas de configuration NocoDB active');
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: 'Configuration NocoDB non trouvée'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utiliser l'ID de table configuré dans le groupe
|
||||||
|
const tableId = group.nocodbTableId || 'mrr733dfb8wtt9b';
|
||||||
|
console.log('🔧 Utilisation table ID pour BL:', {
|
||||||
|
groupTable: group.nocodbTableName,
|
||||||
|
configuredId: group.nocodbTableId,
|
||||||
|
resolvedId: tableId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rechercher par numéro de BL
|
||||||
|
const blColumnName = group.nocodbBlColumnName || 'Numero_BL';
|
||||||
|
let matchResult = await this.searchInNocoDB(
|
||||||
|
blNumber,
|
||||||
|
blColumnName,
|
||||||
|
nocodbConfig,
|
||||||
|
tableId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Gérer les erreurs d'API spécifiques
|
||||||
|
if (matchResult.error) {
|
||||||
|
const result = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none' as const,
|
||||||
|
errorMessage: matchResult.error
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sauvegarder en cache même les erreurs
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||||
|
|
||||||
|
await storage.saveInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: blNumber,
|
||||||
|
supplierName: supplierName,
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: result.errorMessage,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: null,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur sauvegarde cache erreur BL:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchResult.found) {
|
||||||
|
// Vérifier que le fournisseur correspond
|
||||||
|
const foundSupplier = matchResult.data[group.nocodbSupplierColumnName || 'supplier'] || '';
|
||||||
|
const supplierMatch = foundSupplier.toLowerCase().includes(supplierName.toLowerCase()) ||
|
||||||
|
supplierName.toLowerCase().includes(foundSupplier.toLowerCase());
|
||||||
|
|
||||||
|
if (!supplierMatch) {
|
||||||
|
console.log('⚠️ Fournisseur ne correspond pas:', {
|
||||||
|
expected: supplierName,
|
||||||
|
found: foundSupplier
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: `BL trouvé mais fournisseur différent: ${foundSupplier} vs ${supplierName}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
exists: true,
|
||||||
|
matchType: 'bl_number' as const,
|
||||||
|
invoiceReference: matchResult.data[group.invoiceColumnName || 'RefFacture'] || `BL_${blNumber}`,
|
||||||
|
invoiceAmount: parseFloat(matchResult.data[group.nocodbAmountColumnName || 'amount'] || '0'),
|
||||||
|
supplierName: foundSupplier || supplierName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sauvegarder en cache si succès
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||||
|
|
||||||
|
await storage.saveInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: result.invoiceReference,
|
||||||
|
supplierName: result.supplierName,
|
||||||
|
exists: true,
|
||||||
|
matchType: 'bl_number',
|
||||||
|
errorMessage: null,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: null,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur sauvegarde cache succès BL:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aucune correspondance trouvée
|
||||||
|
const notFoundResult = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none' as const,
|
||||||
|
errorMessage: 'Aucun BL correspondant trouvé dans NocoDB'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sauvegarder même les résultats "non trouvé"
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||||
|
|
||||||
|
await storage.saveInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: blNumber,
|
||||||
|
supplierName: supplierName,
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: notFoundResult.errorMessage,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: null,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur sauvegarde cache not found BL:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notFoundResult;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = `Erreur NocoDB BL: ${error instanceof Error ? error.message : 'Erreur inconnue'}`;
|
||||||
|
|
||||||
|
console.error('❌ Erreur lors de la vérification BL NocoDB:', error);
|
||||||
|
|
||||||
|
const errorResult = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none' as const,
|
||||||
|
errorMessage
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sauvegarder les erreurs en cache
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||||
|
|
||||||
|
await storage.saveInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: blNumber,
|
||||||
|
supplierName: supplierName,
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: errorResult.errorMessage,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: null,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur sauvegarde cache erreur BL:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur vérification facture par BL:', error);
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recherche par numéro de BL (méthode simplifiée pour compatibilité)
|
||||||
|
*/
|
||||||
|
async searchByBLNumber(blNumber: string, groupId: number): Promise<any> {
|
||||||
|
console.log('🔍 Recherche par BL (legacy):', { blNumber, groupId });
|
||||||
|
|
||||||
return {
|
const result = await this.verifyInvoiceByBL(blNumber, '', groupId, false);
|
||||||
found: false,
|
|
||||||
error: 'BL non trouvé'
|
if (result.exists) {
|
||||||
};
|
return {
|
||||||
|
found: true,
|
||||||
|
data: {
|
||||||
|
invoiceReference: result.invoiceReference,
|
||||||
|
amount: result.invoiceAmount,
|
||||||
|
supplier: result.supplierName
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
found: false,
|
||||||
|
error: result.errorMessage || 'BL non trouvé'
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+29
-9
@@ -882,7 +882,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { invoiceReference, forceRefresh } = req.body;
|
const { invoiceReference, blNumber, forceRefresh } = req.body;
|
||||||
|
|
||||||
if (!delivery.supplier || !delivery.group) {
|
if (!delivery.supplier || !delivery.group) {
|
||||||
console.log('❌ Livraison manque informations:', {
|
console.log('❌ Livraison manque informations:', {
|
||||||
@@ -893,24 +893,44 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(400).json({ message: "Delivery missing supplier or group information" });
|
return res.status(400).json({ message: "Delivery missing supplier or group information" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invoiceReference || !invoiceReference.trim()) {
|
// Accepter soit une référence de facture soit un numéro de BL
|
||||||
return res.status(400).json({ message: "Référence de facture requise" });
|
if ((!invoiceReference || !invoiceReference.trim()) && (!blNumber || !blNumber.trim())) {
|
||||||
|
return res.status(400).json({ message: "Référence de facture ou numéro BL requis" });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔍 Vérification facture:', {
|
console.log('🔍 Vérification facture:', {
|
||||||
deliveryId,
|
deliveryId,
|
||||||
invoiceReference,
|
invoiceReference,
|
||||||
|
blNumber,
|
||||||
supplier: delivery.supplier?.name,
|
supplier: delivery.supplier?.name,
|
||||||
group: delivery.group?.name,
|
group: delivery.group?.name,
|
||||||
groupId: delivery.groupId
|
groupId: delivery.groupId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Appeler le service de vérification
|
let result;
|
||||||
const result = await invoiceVerificationService.verifyInvoice(
|
|
||||||
invoiceReference,
|
if (invoiceReference && invoiceReference.trim()) {
|
||||||
delivery.groupId,
|
// Vérifier par référence de facture
|
||||||
forceRefresh || false
|
result = await invoiceVerificationService.verifyInvoice(
|
||||||
);
|
invoiceReference,
|
||||||
|
delivery.groupId,
|
||||||
|
forceRefresh || false
|
||||||
|
);
|
||||||
|
} else if (blNumber && blNumber.trim()) {
|
||||||
|
// Vérifier par numéro BL
|
||||||
|
result = await invoiceVerificationService.verifyInvoiceByBL(
|
||||||
|
blNumber,
|
||||||
|
delivery.supplier.name,
|
||||||
|
delivery.groupId,
|
||||||
|
forceRefresh || false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
result = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'none',
|
||||||
|
errorMessage: 'Aucune référence de facture ou numéro BL fourni'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log('✅ Résultat vérification:', result);
|
console.log('✅ Résultat vérification:', result);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
|
|||||||
Reference in new issue
Block a user