mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 22:48:15 +02:00
7 files changed
+539
-14
No files matched your search
@@ -263,7 +263,7 @@ export default function BLReconciliation() {
|
|||||||
console.error('Erreur vérification:', error);
|
console.error('Erreur vérification:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur de vérification",
|
title: "Erreur de vérification",
|
||||||
description: error.response?.data?.message || error.message || "Configuration NocoDB manquante ou incorrecte",
|
description: error.response?.data?.message || error.message || "Erreur de vérification - vérifiez la configuration NocoDB",
|
||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -319,7 +319,7 @@ export default function BLReconciliation() {
|
|||||||
console.error('Erreur recherche BL:', error);
|
console.error('Erreur recherche BL:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur de recherche",
|
title: "Erreur de recherche",
|
||||||
description: error.response?.data?.message || error.message || "Configuration NocoDB manquante ou incorrecte",
|
description: error.response?.data?.message || error.message || "Erreur de recherche BL - vérifiez la configuration NocoDB",
|
||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -19,27 +19,29 @@ La plateforme fournit une gestion robuste des flux de travail de livraison avec
|
|||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
### 2025-08-13 - Système de Vérification de Factures NocoDB
|
### 2025-08-13 - Système de Vérification de Factures NocoDB avec Cache Production
|
||||||
✅ **Implémentation complète du système de vérification automatique des factures** :
|
✅ **Implémentation complète du système de vérification automatique des factures** :
|
||||||
|
|
||||||
**Fonctionnalités développées :**
|
**Fonctionnalités développées :**
|
||||||
- Service de vérification NocoDB (`server/services/nocodbVerification.ts`)
|
- Service de vérification production (`server/services/productionInvoiceVerification.ts`)
|
||||||
|
- Utilise les tables existantes de cache (`INVOICE_VERIFICATION_CACHE`, `INVOICE_VERIFICATIONS`)
|
||||||
- API endpoints pour vérification factures et recherche par BL
|
- API endpoints pour vérification factures et recherche par BL
|
||||||
- Interface utilisateur avec coches vertes de validation
|
- Interface utilisateur avec coches vertes de validation
|
||||||
- Auto-remplissage des champs facture/montant depuis NocoDB
|
- Auto-remplissage des champs facture/montant depuis NocoDB
|
||||||
- Support multi-magasin avec configurations NocoDB spécifiques
|
- Support multi-magasin avec configurations NocoDB spécifiques
|
||||||
|
|
||||||
**Logique de fonctionnement :**
|
**Logique de fonctionnement :**
|
||||||
- Vérification références factures existantes avec validation fournisseur
|
- Vérification d'abord dans le cache (tables production)
|
||||||
- Recherche automatique par numéro BL pour lignes sans référence
|
- Si pas trouvé, appel réel à NocoDB pour récupérer les données
|
||||||
- Auto-remplissage automatique des données trouvées
|
- Sauvegarde automatique des résultats dans le cache (1h expiration)
|
||||||
- Indicateurs visuels : coche verte (succès), cercle rouge (erreur)
|
- Indicateurs visuels : coche verte (succès), rouge (erreur)
|
||||||
|
|
||||||
**Configuration technique :**
|
**Configuration technique :**
|
||||||
- Intégration avec configurations NocoDB par magasin
|
- Intégration avec tables cache existantes en production
|
||||||
|
- Appels API NocoDB authentifiés avec tokens personnels
|
||||||
- Mapping flexible des colonnes (facture, BL, montant, fournisseur)
|
- Mapping flexible des colonnes (facture, BL, montant, fournisseur)
|
||||||
- Gestion d'erreurs et notifications utilisateur
|
- Gestion d'erreurs et notifications utilisateur améliorées
|
||||||
- Interface responsive et accessible
|
- Guide complet de configuration production fourni
|
||||||
|
|
||||||
### 2025-08-13 - Uniformisation complète des interfaces utilisateur
|
### 2025-08-13 - Uniformisation complète des interfaces utilisateur
|
||||||
✅ **Harmonisation du design des tableaux** terminée pour une expérience utilisateur cohérente
|
✅ **Harmonisation du design des tableaux** terminée pour une expérience utilisateur cohérente
|
||||||
|
|||||||
+5
-3
@@ -37,7 +37,7 @@ import {
|
|||||||
} from "@shared/schema";
|
} from "@shared/schema";
|
||||||
import { hasPermission } from "@shared/permissions";
|
import { hasPermission } from "@shared/permissions";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { nocodbVerificationService } from "./services/nocodbVerification";
|
import { ProductionInvoiceVerificationService } from "./services/productionInvoiceVerification";
|
||||||
|
|
||||||
export async function registerRoutes(app: Express): Promise<Server> {
|
export async function registerRoutes(app: Express): Promise<Server> {
|
||||||
// Health check endpoint for Docker
|
// Health check endpoint for Docker
|
||||||
@@ -914,7 +914,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await nocodbVerificationService.verifyInvoiceReference(
|
const productionVerificationService = new ProductionInvoiceVerificationService(storage);
|
||||||
|
const result = await productionVerificationService.verifyInvoiceReference(
|
||||||
parseInt(groupId),
|
parseInt(groupId),
|
||||||
invoiceReference,
|
invoiceReference,
|
||||||
supplierName
|
supplierName
|
||||||
@@ -950,7 +951,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await nocodbVerificationService.searchByBLNumber(
|
const productionVerificationService = new ProductionInvoiceVerificationService(storage);
|
||||||
|
const result = await productionVerificationService.searchByBLNumber(
|
||||||
parseInt(groupId),
|
parseInt(groupId),
|
||||||
blNumber,
|
blNumber,
|
||||||
supplierName
|
supplierName
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import { IStorage } from "../storage";
|
||||||
|
|
||||||
|
interface VerificationResult {
|
||||||
|
exists: boolean;
|
||||||
|
matchType: string;
|
||||||
|
invoiceReference?: string;
|
||||||
|
invoiceAmount?: number;
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProductionInvoiceVerificationService {
|
||||||
|
constructor(private storage: IStorage) {}
|
||||||
|
|
||||||
|
// Vérifie une référence facture (avec cache)
|
||||||
|
async verifyInvoiceReference(
|
||||||
|
groupId: number,
|
||||||
|
invoiceReference: string,
|
||||||
|
supplierName?: string
|
||||||
|
): Promise<VerificationResult> {
|
||||||
|
console.log(`🔍 Production Verification - Invoice: ${invoiceReference}, Group: ${groupId}, Supplier: ${supplierName}`);
|
||||||
|
|
||||||
|
// 1. Vérifier d'abord dans les vérifications existantes
|
||||||
|
const existingVerifications = await this.storage.getInvoiceVerifications();
|
||||||
|
const existing = existingVerifications.find(v =>
|
||||||
|
v.groupId === groupId &&
|
||||||
|
v.invoiceReference === invoiceReference &&
|
||||||
|
(!supplierName || v.supplierName === supplierName)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
console.log(`✅ Production Cache Hit - Found existing verification: ${existing.exists}`);
|
||||||
|
return {
|
||||||
|
exists: existing.exists,
|
||||||
|
matchType: existing.matchType || 'direct',
|
||||||
|
invoiceReference: existing.invoiceReference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Vérifier dans le cache
|
||||||
|
const cacheKey = `${groupId}_${invoiceReference}_${supplierName || 'any'}`;
|
||||||
|
const cache = await this.storage.getInvoiceVerificationCache(cacheKey);
|
||||||
|
|
||||||
|
if (cache && cache.expiresAt > new Date()) {
|
||||||
|
console.log(`⚡ Production Cache Hit - Using cached result: ${cache.exists}`);
|
||||||
|
return {
|
||||||
|
exists: cache.exists,
|
||||||
|
matchType: cache.matchType,
|
||||||
|
invoiceReference: cache.invoiceReference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Faire la vérification réelle via NocoDB
|
||||||
|
try {
|
||||||
|
const result = await this.verifyWithNocoDB(groupId, invoiceReference, supplierName);
|
||||||
|
|
||||||
|
// 4. Sauvegarder dans le cache
|
||||||
|
await this.saveToCaches(groupId, invoiceReference, supplierName, result, 'delivery_verification');
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Production NocoDB Error:`, error);
|
||||||
|
|
||||||
|
// Sauvegarder l'erreur dans le cache
|
||||||
|
const errorResult: VerificationResult = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'error',
|
||||||
|
errorMessage: error instanceof Error ? error.message : 'Erreur de vérification'
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.saveToCaches(groupId, invoiceReference, supplierName, errorResult, 'error');
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recherche par numéro BL (avec cache)
|
||||||
|
async searchByBLNumber(
|
||||||
|
groupId: number,
|
||||||
|
blNumber: string,
|
||||||
|
supplierName?: string
|
||||||
|
): Promise<VerificationResult> {
|
||||||
|
console.log(`🔍 Production BL Search - BL: ${blNumber}, Group: ${groupId}, Supplier: ${supplierName}`);
|
||||||
|
|
||||||
|
// Vérifier dans le cache BL
|
||||||
|
const cacheKey = `bl_${groupId}_${blNumber}_${supplierName || 'any'}`;
|
||||||
|
const cache = await this.storage.getInvoiceVerificationCache(cacheKey);
|
||||||
|
|
||||||
|
if (cache && cache.expiresAt > new Date()) {
|
||||||
|
console.log(`⚡ Production BL Cache Hit: ${cache.exists}`);
|
||||||
|
return {
|
||||||
|
exists: cache.exists,
|
||||||
|
matchType: cache.matchType,
|
||||||
|
invoiceReference: cache.invoiceReference,
|
||||||
|
invoiceAmount: cache.apiCallTime // On stocke le montant dans ce champ temporairement
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Faire la recherche réelle via NocoDB
|
||||||
|
try {
|
||||||
|
const result = await this.searchBLWithNocoDB(groupId, blNumber, supplierName);
|
||||||
|
|
||||||
|
// Sauvegarder dans le cache
|
||||||
|
await this.saveToCaches(groupId, blNumber, supplierName, result, 'bl_search');
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Production BL Search Error:`, error);
|
||||||
|
|
||||||
|
const errorResult: VerificationResult = {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'error',
|
||||||
|
errorMessage: error instanceof Error ? error.message : 'Erreur de recherche BL'
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.saveToCaches(groupId, blNumber, supplierName, errorResult, 'bl_error');
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification réelle avec NocoDB
|
||||||
|
private async verifyWithNocoDB(
|
||||||
|
groupId: number,
|
||||||
|
invoiceReference: string,
|
||||||
|
supplierName?: string
|
||||||
|
): Promise<VerificationResult> {
|
||||||
|
// Récupérer la configuration du groupe
|
||||||
|
const group = await this.storage.getGroup(groupId);
|
||||||
|
if (!group || !group.nocodbConfigId || !group.nocodbTableName) {
|
||||||
|
throw new Error(`Configuration NocoDB manquante pour le groupe ${groupId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer la configuration NocoDB
|
||||||
|
const nocodbConfig = await this.storage.getNocodbConfig(group.nocodbConfigId);
|
||||||
|
if (!nocodbConfig || !nocodbConfig.isActive) {
|
||||||
|
throw new Error(`Configuration NocoDB inactive ou introuvable (ID: ${group.nocodbConfigId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🌐 Production NocoDB Call - Table: ${group.nocodbTableName}, Invoice: ${invoiceReference}`);
|
||||||
|
|
||||||
|
// Construire la requête NocoDB
|
||||||
|
const baseUrl = nocodbConfig.baseUrl.replace(/\/+$/, ''); // Enlever les trailing slashes
|
||||||
|
const tableUrl = `${baseUrl}/api/v1/db/data/${nocodbConfig.projectId}/${group.nocodbTableName}`;
|
||||||
|
|
||||||
|
// Construire les conditions de recherche
|
||||||
|
const invoiceColumn = group.invoiceColumnName || 'invoice_reference';
|
||||||
|
const supplierColumn = group.nocodbSupplierColumnName || 'supplier';
|
||||||
|
|
||||||
|
let whereClause = `(${invoiceColumn},eq,${invoiceReference})`;
|
||||||
|
if (supplierName && supplierColumn) {
|
||||||
|
whereClause += `~and(${supplierColumn},eq,${supplierName})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchUrl = `${tableUrl}?where=${encodeURIComponent(whereClause)}&limit=1`;
|
||||||
|
|
||||||
|
console.log(`🌐 Production NocoDB URL: ${searchUrl}`);
|
||||||
|
|
||||||
|
// Faire l'appel API
|
||||||
|
const response = await fetch(searchUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'xc-token': nocodbConfig.apiToken,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`NocoDB API Error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`✅ Production NocoDB Response:`, data);
|
||||||
|
|
||||||
|
const exists = data.list && data.list.length > 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
exists,
|
||||||
|
matchType: 'direct',
|
||||||
|
invoiceReference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recherche BL avec NocoDB
|
||||||
|
private async searchBLWithNocoDB(
|
||||||
|
groupId: number,
|
||||||
|
blNumber: string,
|
||||||
|
supplierName?: string
|
||||||
|
): Promise<VerificationResult> {
|
||||||
|
// Récupérer la configuration du groupe
|
||||||
|
const group = await this.storage.getGroup(groupId);
|
||||||
|
if (!group || !group.nocodbConfigId || !group.nocodbTableName) {
|
||||||
|
throw new Error(`Configuration NocoDB manquante pour le groupe ${groupId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer la configuration NocoDB
|
||||||
|
const nocodbConfig = await this.storage.getNocodbConfig(group.nocodbConfigId);
|
||||||
|
if (!nocodbConfig || !nocodbConfig.isActive) {
|
||||||
|
throw new Error(`Configuration NocoDB inactive ou introuvable (ID: ${group.nocodbConfigId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🌐 Production NocoDB BL Search - Table: ${group.nocodbTableName}, BL: ${blNumber}`);
|
||||||
|
|
||||||
|
// Construire la requête NocoDB pour recherche BL
|
||||||
|
const baseUrl = nocodbConfig.baseUrl.replace(/\/+$/, '');
|
||||||
|
const tableUrl = `${baseUrl}/api/v1/db/data/${nocodbConfig.projectId}/${group.nocodbTableName}`;
|
||||||
|
|
||||||
|
const blColumn = group.nocodbBlColumnName || 'bl_number';
|
||||||
|
const supplierColumn = group.nocodbSupplierColumnName || 'supplier';
|
||||||
|
const invoiceColumn = group.invoiceColumnName || 'invoice_reference';
|
||||||
|
const amountColumn = group.nocodbAmountColumnName || 'amount';
|
||||||
|
|
||||||
|
let whereClause = `(${blColumn},eq,${blNumber})`;
|
||||||
|
if (supplierName && supplierColumn) {
|
||||||
|
whereClause += `~and(${supplierColumn},eq,${supplierName})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchUrl = `${tableUrl}?where=${encodeURIComponent(whereClause)}&limit=1`;
|
||||||
|
|
||||||
|
console.log(`🌐 Production NocoDB BL URL: ${searchUrl}`);
|
||||||
|
|
||||||
|
// Faire l'appel API
|
||||||
|
const response = await fetch(searchUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'xc-token': nocodbConfig.apiToken,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`NocoDB API Error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`✅ Production NocoDB BL Response:`, data);
|
||||||
|
|
||||||
|
if (data.list && data.list.length > 0) {
|
||||||
|
const record = data.list[0];
|
||||||
|
return {
|
||||||
|
exists: true,
|
||||||
|
matchType: 'bl_search',
|
||||||
|
invoiceReference: record[invoiceColumn],
|
||||||
|
invoiceAmount: parseFloat(record[amountColumn]) || undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exists: false,
|
||||||
|
matchType: 'bl_search'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder dans les caches
|
||||||
|
private async saveToCaches(
|
||||||
|
groupId: number,
|
||||||
|
reference: string,
|
||||||
|
supplierName: string | undefined,
|
||||||
|
result: VerificationResult,
|
||||||
|
context: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 1. Sauvegarder dans le cache avec expiration
|
||||||
|
const cacheKey = context.startsWith('bl_')
|
||||||
|
? `bl_${groupId}_${reference}_${supplierName || 'any'}`
|
||||||
|
: `${groupId}_${reference}_${supplierName || 'any'}`;
|
||||||
|
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setHours(expiresAt.getHours() + 1); // Cache 1 heure
|
||||||
|
|
||||||
|
await this.storage.createInvoiceVerificationCache({
|
||||||
|
cacheKey,
|
||||||
|
groupId,
|
||||||
|
invoiceReference: reference,
|
||||||
|
supplierName: supplierName || null,
|
||||||
|
exists: result.exists,
|
||||||
|
matchType: result.matchType,
|
||||||
|
errorMessage: result.errorMessage || null,
|
||||||
|
cacheHit: false,
|
||||||
|
apiCallTime: result.invoiceAmount ? Math.round(result.invoiceAmount) : undefined,
|
||||||
|
expiresAt
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Si c'est une vérification de livraison réussie, sauvegarder aussi dans les vérifications permanentes
|
||||||
|
if (context === 'delivery_verification' && result.exists) {
|
||||||
|
// On a besoin du deliveryId, pour l'instant on utilise 0 comme placeholder
|
||||||
|
await this.storage.createInvoiceVerification({
|
||||||
|
deliveryId: 0, // Sera mis à jour par l'API route
|
||||||
|
groupId,
|
||||||
|
invoiceReference: reference,
|
||||||
|
supplierName: supplierName || null,
|
||||||
|
exists: result.exists,
|
||||||
|
matchType: result.matchType,
|
||||||
|
isValid: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`💾 Production Cache Saved - Key: ${cacheKey}, Exists: ${result.exists}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Production Cache Save Error:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
customerOrders,
|
customerOrders,
|
||||||
dlcProducts,
|
dlcProducts,
|
||||||
tasks,
|
tasks,
|
||||||
|
invoiceVerificationCache,
|
||||||
|
invoiceVerifications,
|
||||||
type User,
|
type User,
|
||||||
type UpsertUser,
|
type UpsertUser,
|
||||||
type Group,
|
type Group,
|
||||||
@@ -41,6 +43,10 @@ import {
|
|||||||
type DlcProductFrontend,
|
type DlcProductFrontend,
|
||||||
type InsertDlcProductFrontend,
|
type InsertDlcProductFrontend,
|
||||||
type DlcProductWithRelations,
|
type DlcProductWithRelations,
|
||||||
|
type InvoiceVerificationCache,
|
||||||
|
type InsertInvoiceVerificationCache,
|
||||||
|
type InvoiceVerification,
|
||||||
|
type InsertInvoiceVerification,
|
||||||
type Task,
|
type Task,
|
||||||
type InsertTask,
|
type InsertTask,
|
||||||
} from "@shared/schema";
|
} from "@shared/schema";
|
||||||
@@ -152,6 +158,12 @@ export interface IStorage {
|
|||||||
updateTask(id: number, task: Partial<InsertTask>): Promise<Task>;
|
updateTask(id: number, task: Partial<InsertTask>): Promise<Task>;
|
||||||
deleteTask(id: number): Promise<void>;
|
deleteTask(id: number): Promise<void>;
|
||||||
completeTask(id: number, completedBy?: string): Promise<void>;
|
completeTask(id: number, completedBy?: string): Promise<void>;
|
||||||
|
|
||||||
|
// Invoice Verification operations - Tables existantes en production
|
||||||
|
getInvoiceVerifications(deliveryId?: number): Promise<InvoiceVerification[]>;
|
||||||
|
createInvoiceVerification(verification: InsertInvoiceVerification): Promise<InvoiceVerification>;
|
||||||
|
getInvoiceVerificationCache(cacheKey: string): Promise<InvoiceVerificationCache | undefined>;
|
||||||
|
createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise<InvoiceVerificationCache>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatabaseStorage implements IStorage {
|
export class DatabaseStorage implements IStorage {
|
||||||
@@ -2026,6 +2038,52 @@ export class DatabaseStorage implements IStorage {
|
|||||||
})
|
})
|
||||||
.where(eq(tasks.id, id));
|
.where(eq(tasks.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoice Verification operations - Utilise les tables existantes en production
|
||||||
|
async getInvoiceVerifications(deliveryId?: number): Promise<InvoiceVerification[]> {
|
||||||
|
const query = db.select().from(invoiceVerifications);
|
||||||
|
if (deliveryId) {
|
||||||
|
return await query.where(eq(invoiceVerifications.deliveryId, deliveryId));
|
||||||
|
}
|
||||||
|
return await query.orderBy(desc(invoiceVerifications.verifiedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvoiceVerification(verification: InsertInvoiceVerification): Promise<InvoiceVerification> {
|
||||||
|
const [newVerification] = await db
|
||||||
|
.insert(invoiceVerifications)
|
||||||
|
.values({
|
||||||
|
...verification,
|
||||||
|
verifiedAt: new Date(),
|
||||||
|
lastCheckedAt: new Date(),
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
return newVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInvoiceVerificationCache(cacheKey: string): Promise<InvoiceVerificationCache | undefined> {
|
||||||
|
const [cache] = await db
|
||||||
|
.select()
|
||||||
|
.from(invoiceVerificationCache)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(invoiceVerificationCache.cacheKey, cacheKey),
|
||||||
|
sql`expires_at > NOW()`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise<InvoiceVerificationCache> {
|
||||||
|
const [newCache] = await db
|
||||||
|
.insert(invoiceVerificationCache)
|
||||||
|
.values({
|
||||||
|
...cache,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
return newCache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple in-memory storage for development
|
// Simple in-memory storage for development
|
||||||
@@ -2564,6 +2622,52 @@ class MemStorage implements IStorage {
|
|||||||
return { userId, roleId, assignedBy, assignedAt: new Date() };
|
return { userId, roleId, assignedBy, assignedAt: new Date() };
|
||||||
}
|
}
|
||||||
async removeRoleFromUser(): Promise<void> {}
|
async removeRoleFromUser(): Promise<void> {}
|
||||||
|
|
||||||
|
// Invoice Verification operations - Stubs pour développement
|
||||||
|
async getInvoiceVerifications(deliveryId?: number): Promise<InvoiceVerification[]> {
|
||||||
|
console.log('📋 MemStorage - getInvoiceVerifications called (development mode)');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvoiceVerification(verification: InsertInvoiceVerification): Promise<InvoiceVerification> {
|
||||||
|
console.log('📋 MemStorage - createInvoiceVerification called (development mode)');
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
deliveryId: verification.deliveryId,
|
||||||
|
groupId: verification.groupId,
|
||||||
|
invoiceReference: verification.invoiceReference,
|
||||||
|
supplierName: verification.supplierName,
|
||||||
|
exists: verification.exists,
|
||||||
|
matchType: verification.matchType,
|
||||||
|
verifiedAt: new Date(),
|
||||||
|
isValid: true,
|
||||||
|
lastCheckedAt: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInvoiceVerificationCache(cacheKey: string): Promise<InvoiceVerificationCache | undefined> {
|
||||||
|
console.log('📋 MemStorage - getInvoiceVerificationCache called (development mode)');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise<InvoiceVerificationCache> {
|
||||||
|
console.log('📋 MemStorage - createInvoiceVerificationCache called (development mode)');
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
cacheKey: cache.cacheKey,
|
||||||
|
groupId: cache.groupId,
|
||||||
|
invoiceReference: cache.invoiceReference,
|
||||||
|
supplierName: cache.supplierName,
|
||||||
|
exists: cache.exists,
|
||||||
|
matchType: cache.matchType,
|
||||||
|
errorMessage: cache.errorMessage,
|
||||||
|
cacheHit: cache.cacheHit,
|
||||||
|
apiCallTime: cache.apiCallTime,
|
||||||
|
expiresAt: cache.expiresAt,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use MemStorage in development, DatabaseStorage in production
|
// Use MemStorage in development, DatabaseStorage in production
|
||||||
|
|||||||
@@ -217,6 +217,37 @@ export const dlcProducts = pgTable("dlc_products", {
|
|||||||
updatedAt: timestamp("updated_at").defaultNow(), // Date de mise à jour
|
updatedAt: timestamp("updated_at").defaultNow(), // Date de mise à jour
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Invoice Verification Cache - Table existante en production
|
||||||
|
export const invoiceVerificationCache = pgTable("invoice_verification_cache", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
cacheKey: varchar("cache_key", { length: 255 }).notNull(),
|
||||||
|
groupId: integer("group_id").notNull(),
|
||||||
|
invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(),
|
||||||
|
supplierName: varchar("supplier_name", { length: 255 }),
|
||||||
|
exists: boolean("exists").notNull(),
|
||||||
|
matchType: varchar("match_type", { length: 50 }).notNull(),
|
||||||
|
errorMessage: text("error_message"),
|
||||||
|
cacheHit: boolean("cache_hit").default(false),
|
||||||
|
apiCallTime: integer("api_call_time"),
|
||||||
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invoice Verifications - Table existante en production
|
||||||
|
export const invoiceVerifications = pgTable("invoice_verifications", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
deliveryId: integer("delivery_id").notNull(),
|
||||||
|
groupId: integer("group_id").notNull(),
|
||||||
|
invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(),
|
||||||
|
supplierName: varchar("supplier_name", { length: 255 }),
|
||||||
|
exists: boolean("exists").notNull(),
|
||||||
|
matchType: varchar("match_type", { length: 50 }),
|
||||||
|
verifiedAt: timestamp("verified_at").defaultNow(),
|
||||||
|
isValid: boolean("is_valid").default(true),
|
||||||
|
lastCheckedAt: timestamp("last_checked_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
// Tasks table
|
// Tasks table
|
||||||
export const tasks = pgTable("tasks", {
|
export const tasks = pgTable("tasks", {
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
@@ -481,6 +512,18 @@ export const insertDlcProductFrontendSchema = insertDlcProductSchema
|
|||||||
.omit({ expiryDate: true })
|
.omit({ expiryDate: true })
|
||||||
.extend({ dlcDate: z.coerce.date() });
|
.extend({ dlcDate: z.coerce.date() });
|
||||||
|
|
||||||
|
export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertInvoiceVerificationsSchema = createInsertSchema(invoiceVerifications).omit({
|
||||||
|
id: true,
|
||||||
|
verifiedAt: true,
|
||||||
|
lastCheckedAt: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const insertTaskSchema = createInsertSchema(tasks).omit({
|
export const insertTaskSchema = createInsertSchema(tasks).omit({
|
||||||
id: true,
|
id: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
@@ -516,6 +559,10 @@ export type DlcProduct = typeof dlcProducts.$inferSelect;
|
|||||||
export type InsertDlcProduct = z.infer<typeof insertDlcProductSchema>;
|
export type InsertDlcProduct = z.infer<typeof insertDlcProductSchema>;
|
||||||
export type DlcProductFrontend = Omit<DlcProduct, 'expiryDate'> & { dlcDate: Date };
|
export type DlcProductFrontend = Omit<DlcProduct, 'expiryDate'> & { dlcDate: Date };
|
||||||
export type InsertDlcProductFrontend = z.infer<typeof insertDlcProductFrontendSchema>;
|
export type InsertDlcProductFrontend = z.infer<typeof insertDlcProductFrontendSchema>;
|
||||||
|
export type InvoiceVerificationCache = typeof invoiceVerificationCache.$inferSelect;
|
||||||
|
export type InsertInvoiceVerificationCache = z.infer<typeof insertInvoiceVerificationCacheSchema>;
|
||||||
|
export type InvoiceVerification = typeof invoiceVerifications.$inferSelect;
|
||||||
|
export type InsertInvoiceVerification = z.infer<typeof insertInvoiceVerificationsSchema>;
|
||||||
export type Task = typeof tasks.$inferSelect;
|
export type Task = typeof tasks.$inferSelect;
|
||||||
export type InsertTask = z.infer<typeof insertTaskSchema>;
|
export type InsertTask = z.infer<typeof insertTaskSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Test de la Configuration Production
|
||||||
|
|
||||||
|
## Système Mis en Place
|
||||||
|
|
||||||
|
✅ **Service de Vérification Production** :
|
||||||
|
- Utilise vos tables `INVOICE_VERIFICATION_CACHE` et `INVOICE_VERIFICATIONS` pour le cache
|
||||||
|
- Fait les appels réels à NocoDB pour récupérer les données factures
|
||||||
|
- Sauvegarde les résultats dans le cache (1 heure d'expiration)
|
||||||
|
|
||||||
|
✅ **Interface Utilisateur** :
|
||||||
|
- Boutons de vérification (loupe et coche) dans le rapprochement BL/Factures
|
||||||
|
- Coches vertes pour les factures vérifiées
|
||||||
|
- Messages d'erreur explicites
|
||||||
|
|
||||||
|
## Configuration Requise sur Votre Serveur
|
||||||
|
|
||||||
|
**1. Configuration NocoDB :**
|
||||||
|
```sql
|
||||||
|
-- Insérer votre configuration
|
||||||
|
INSERT INTO nocodb_config (
|
||||||
|
name, base_url, project_id, api_token, description, is_active
|
||||||
|
) VALUES (
|
||||||
|
'Production NocoDB',
|
||||||
|
'https://app.nocodb.com', -- Votre URL NocoDB
|
||||||
|
'p_votre_project_id', -- Votre Project ID
|
||||||
|
'nc_votre_api_token', -- Votre API Token
|
||||||
|
'Configuration production pour vérification factures',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Configuration Groupes/Magasins :**
|
||||||
|
```sql
|
||||||
|
-- Pour chaque magasin
|
||||||
|
UPDATE groups SET
|
||||||
|
nocodb_config_id = 1, -- ID de votre config NocoDB
|
||||||
|
nocodb_table_name = 'nom_de_votre_table_factures',
|
||||||
|
invoice_column_name = 'nom_colonne_reference_facture',
|
||||||
|
nocodb_bl_column_name = 'nom_colonne_numero_bl',
|
||||||
|
nocodb_amount_column_name = 'nom_colonne_montant',
|
||||||
|
nocodb_supplier_column_name = 'nom_colonne_fournisseur'
|
||||||
|
WHERE id = 1; -- ID de votre magasin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test du Système
|
||||||
|
|
||||||
|
Une fois configuré, vous devriez voir :
|
||||||
|
1. **Boutons** : loupe et coche dans la colonne "Actions" du rapprochement
|
||||||
|
2. **Vérification** : cliquer la loupe vérifie la facture dans NocoDB
|
||||||
|
3. **Auto-remplissage** : cliquer la coche recherche par BL et remplit automatiquement
|
||||||
|
4. **Coches vertes** : apparaissent quand la vérification réussit
|
||||||
|
5. **Logs** : dans la console serveur vous verrez les appels NocoDB
|
||||||
|
|
||||||
|
## Logs de Débogage
|
||||||
|
|
||||||
|
Surveillez ces logs dans votre console serveur :
|
||||||
|
```
|
||||||
|
🔍 Production Verification - Invoice: FAC123, Group: 1, Supplier: Fournisseur A
|
||||||
|
🌐 Production NocoDB Call - Table: ma_table_factures, Invoice: FAC123
|
||||||
|
🌐 Production NocoDB URL: https://app.nocodb.com/api/v1/db/data/...
|
||||||
|
✅ Production NocoDB Response: {list: [...]}
|
||||||
|
💾 Production Cache Saved - Key: 1_FAC123_Fournisseur A, Exists: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
Si ça ne marche pas :
|
||||||
|
1. **Pas de boutons** : Configuration manquante dans la base
|
||||||
|
2. **Erreurs NocoDB** : Vérifiez l'API token et les noms de tables/colonnes
|
||||||
|
3. **Pas de coches** : Vérifiez les logs serveur pour voir les erreurs
|
||||||
Reference in new issue
Block a user