mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-15 19:29:54 +02:00
Restored to 'd418fda3c966ae0ab78451c384bd2504f82bcd1f'
Replit-Restored-To: d418fda3c9
This commit is contained in:
1 parent
3721ea9a14
commit
695cd8ad7c
10 files changed
+44
-1260
No files matched your search
@@ -37,7 +37,6 @@ import {
|
||||
} from "@shared/schema";
|
||||
import { hasPermission } from "@shared/permissions";
|
||||
import { z } from "zod";
|
||||
import { ProductionInvoiceVerificationService } from "./services/productionInvoiceVerification";
|
||||
|
||||
export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Health check endpoint for Docker
|
||||
@@ -890,81 +889,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Routes de vérification NocoDB pour les factures
|
||||
app.post('/api/deliveries/verify-invoice', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
const { groupId, invoiceReference, supplierName } = req.body;
|
||||
|
||||
if (!groupId || !invoiceReference || !supplierName) {
|
||||
return res.status(400).json({
|
||||
message: "groupId, invoiceReference et supplierName sont requis"
|
||||
});
|
||||
}
|
||||
|
||||
// Vérifier les permissions d'accès au groupe
|
||||
if (user.role !== 'admin') {
|
||||
const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || [];
|
||||
if (!userGroupIds.includes(parseInt(groupId))) {
|
||||
return res.status(403).json({ message: "Access denied to this group" });
|
||||
}
|
||||
}
|
||||
|
||||
const productionVerificationService = new ProductionInvoiceVerificationService(storage);
|
||||
const result = await productionVerificationService.verifyInvoiceReference(
|
||||
parseInt(groupId),
|
||||
invoiceReference,
|
||||
supplierName
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error("Error verifying invoice:", error);
|
||||
res.status(500).json({ message: "Failed to verify invoice" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/deliveries/search-by-bl', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
}
|
||||
|
||||
const { groupId, blNumber, supplierName } = req.body;
|
||||
|
||||
if (!groupId || !blNumber || !supplierName) {
|
||||
return res.status(400).json({
|
||||
message: "groupId, blNumber et supplierName sont requis"
|
||||
});
|
||||
}
|
||||
|
||||
// Vérifier les permissions d'accès au groupe
|
||||
if (user.role !== 'admin') {
|
||||
const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || [];
|
||||
if (!userGroupIds.includes(parseInt(groupId))) {
|
||||
return res.status(403).json({ message: "Access denied to this group" });
|
||||
}
|
||||
}
|
||||
|
||||
const productionVerificationService = new ProductionInvoiceVerificationService(storage);
|
||||
const result = await productionVerificationService.searchByBLNumber(
|
||||
parseInt(groupId),
|
||||
blNumber,
|
||||
supplierName
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error("Error searching by BL:", error);
|
||||
res.status(500).json({ message: "Failed to search by BL number" });
|
||||
}
|
||||
});
|
||||
|
||||
// Statistics routes
|
||||
app.get('/api/stats/monthly', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
import { storage } from "../storage";
|
||||
|
||||
interface NocodbConfig {
|
||||
id: number;
|
||||
baseUrl: string;
|
||||
apiToken: string;
|
||||
projectId: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
interface GroupConfig {
|
||||
nocodbConfigId?: number;
|
||||
nocodbTableName?: string;
|
||||
invoiceColumnName?: string;
|
||||
nocodbBlColumnName?: string;
|
||||
nocodbAmountColumnName?: string;
|
||||
nocodbSupplierColumnName?: string;
|
||||
}
|
||||
|
||||
class NocodbVerificationService {
|
||||
private async getNocodbConfig(configId: number): Promise<NocodbConfig | null> {
|
||||
try {
|
||||
return await storage.getNocodbConfig(configId);
|
||||
} catch (error) {
|
||||
console.error('Erreur récupération config NocoDB:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async getGroupConfig(groupId: number): Promise<GroupConfig | null> {
|
||||
try {
|
||||
const group = await storage.getGroup(groupId);
|
||||
if (!group) return null;
|
||||
|
||||
return {
|
||||
nocodbConfigId: group.nocodbConfigId || undefined,
|
||||
nocodbTableName: group.nocodbTableName || undefined,
|
||||
invoiceColumnName: group.invoiceColumnName || 'invoice_reference',
|
||||
nocodbBlColumnName: group.nocodbBlColumnName || 'bl_number',
|
||||
nocodbAmountColumnName: group.nocodbAmountColumnName || 'amount',
|
||||
nocodbSupplierColumnName: group.nocodbSupplierColumnName || 'supplier'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erreur récupération config groupe:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async makeNocodbRequest(
|
||||
config: NocodbConfig,
|
||||
tableName: string,
|
||||
queryParams: Record<string, any>
|
||||
): Promise<any> {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// Construire les filtres NocoDB
|
||||
Object.entries(queryParams).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
params.append(`where`, `(${key},eq,${value})`);
|
||||
}
|
||||
});
|
||||
|
||||
const url = `${config.baseUrl}/api/v1/db/data/${config.projectId}/${tableName}?${params.toString()}`;
|
||||
|
||||
console.log('🔍 Requête NocoDB:', { url, table: tableName, filters: queryParams });
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'xc-auth': config.apiToken,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('✅ Réponse NocoDB:', { count: data.list?.length || 0 });
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur requête NocoDB:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async verifyInvoiceReference(
|
||||
groupId: number,
|
||||
invoiceReference: string,
|
||||
supplierName: string
|
||||
): Promise<{ found: boolean; supplierMatch: boolean; data?: any }> {
|
||||
try {
|
||||
console.log('🔍 Vérification facture:', { groupId, invoiceReference, supplierName });
|
||||
|
||||
// Récupérer la configuration du groupe
|
||||
const groupConfig = await this.getGroupConfig(groupId);
|
||||
if (!groupConfig?.nocodbConfigId || !groupConfig?.nocodbTableName) {
|
||||
console.log('⚠️ Configuration NocoDB manquante pour le groupe', groupId);
|
||||
return { found: false, supplierMatch: false };
|
||||
}
|
||||
|
||||
// Récupérer la configuration NocoDB
|
||||
const nocodbConfig = await this.getNocodbConfig(groupConfig.nocodbConfigId);
|
||||
if (!nocodbConfig || !nocodbConfig.isActive) {
|
||||
console.log('⚠️ Configuration NocoDB inactive ou manquante');
|
||||
return { found: false, supplierMatch: false };
|
||||
}
|
||||
|
||||
// Effectuer la recherche
|
||||
const queryParams = {
|
||||
[groupConfig.invoiceColumnName!]: invoiceReference
|
||||
};
|
||||
|
||||
const response = await this.makeNocodbRequest(
|
||||
nocodbConfig,
|
||||
groupConfig.nocodbTableName,
|
||||
queryParams
|
||||
);
|
||||
|
||||
const records = response.list || response.rows || [];
|
||||
|
||||
if (records.length === 0) {
|
||||
return { found: false, supplierMatch: false };
|
||||
}
|
||||
|
||||
// Vérifier la correspondance du fournisseur
|
||||
const record = records[0];
|
||||
const recordSupplier = record[groupConfig.nocodbSupplierColumnName!];
|
||||
|
||||
const supplierMatch = recordSupplier &&
|
||||
recordSupplier.toString().toLowerCase().includes(supplierName.toLowerCase());
|
||||
|
||||
return {
|
||||
found: true,
|
||||
supplierMatch: !!supplierMatch,
|
||||
data: record
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur vérification facture:', error);
|
||||
return { found: false, supplierMatch: false };
|
||||
}
|
||||
}
|
||||
|
||||
async searchByBLNumber(
|
||||
groupId: number,
|
||||
blNumber: string,
|
||||
supplierName: string
|
||||
): Promise<{ found: boolean; invoiceReference?: string; invoiceAmount?: number; data?: any }> {
|
||||
try {
|
||||
console.log('🔍 Recherche par BL:', { groupId, blNumber, supplierName });
|
||||
|
||||
// Récupérer la configuration du groupe
|
||||
const groupConfig = await this.getGroupConfig(groupId);
|
||||
if (!groupConfig?.nocodbConfigId || !groupConfig?.nocodbTableName) {
|
||||
console.log('⚠️ Configuration NocoDB manquante pour le groupe', groupId);
|
||||
return { found: false };
|
||||
}
|
||||
|
||||
// Récupérer la configuration NocoDB
|
||||
const nocodbConfig = await this.getNocodbConfig(groupConfig.nocodbConfigId);
|
||||
if (!nocodbConfig || !nocodbConfig.isActive) {
|
||||
console.log('⚠️ Configuration NocoDB inactive ou manquante');
|
||||
return { found: false };
|
||||
}
|
||||
|
||||
// Effectuer la recherche par BL
|
||||
const queryParams = {
|
||||
[groupConfig.nocodbBlColumnName!]: blNumber,
|
||||
[groupConfig.nocodbSupplierColumnName!]: supplierName
|
||||
};
|
||||
|
||||
const response = await this.makeNocodbRequest(
|
||||
nocodbConfig,
|
||||
groupConfig.nocodbTableName,
|
||||
queryParams
|
||||
);
|
||||
|
||||
const records = response.list || response.rows || [];
|
||||
|
||||
if (records.length === 0) {
|
||||
return { found: false };
|
||||
}
|
||||
|
||||
const record = records[0];
|
||||
const invoiceReference = record[groupConfig.invoiceColumnName!];
|
||||
const invoiceAmount = record[groupConfig.nocodbAmountColumnName!];
|
||||
|
||||
return {
|
||||
found: true,
|
||||
invoiceReference: invoiceReference?.toString(),
|
||||
invoiceAmount: invoiceAmount ? parseFloat(invoiceAmount.toString()) : undefined,
|
||||
data: record
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur recherche par BL:', error);
|
||||
return { found: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const nocodbVerificationService = new NocodbVerificationService();
|
||||
@@ -1,300 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-177
@@ -10,8 +10,6 @@ import {
|
||||
customerOrders,
|
||||
dlcProducts,
|
||||
tasks,
|
||||
invoiceVerificationCache,
|
||||
invoiceVerifications,
|
||||
type User,
|
||||
type UpsertUser,
|
||||
type Group,
|
||||
@@ -43,10 +41,6 @@ import {
|
||||
type DlcProductFrontend,
|
||||
type InsertDlcProductFrontend,
|
||||
type DlcProductWithRelations,
|
||||
type InvoiceVerificationCache,
|
||||
type InsertInvoiceVerificationCache,
|
||||
type InvoiceVerification,
|
||||
type InsertInvoiceVerification,
|
||||
type Task,
|
||||
type InsertTask,
|
||||
} from "@shared/schema";
|
||||
@@ -158,12 +152,6 @@ export interface IStorage {
|
||||
updateTask(id: number, task: Partial<InsertTask>): Promise<Task>;
|
||||
deleteTask(id: number): 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 {
|
||||
@@ -1085,26 +1073,19 @@ export class DatabaseStorage implements IStorage {
|
||||
}
|
||||
|
||||
async getNocodbConfig(id: number): Promise<NocodbConfig | undefined> {
|
||||
try {
|
||||
const configs = await db.select({
|
||||
id: nocodbConfig.id,
|
||||
name: nocodbConfig.name,
|
||||
baseUrl: nocodbConfig.baseUrl,
|
||||
projectId: nocodbConfig.projectId,
|
||||
apiToken: nocodbConfig.apiToken,
|
||||
description: nocodbConfig.description,
|
||||
isActive: nocodbConfig.isActive,
|
||||
createdBy: nocodbConfig.createdBy,
|
||||
createdAt: nocodbConfig.createdAt,
|
||||
updatedAt: nocodbConfig.updatedAt,
|
||||
}).from(nocodbConfig).where(eq(nocodbConfig.id, id));
|
||||
|
||||
console.log(`🔍 Production NocoDB Config Query - ID: ${id}, Found: ${configs.length > 0}`);
|
||||
return configs[0];
|
||||
} catch (error) {
|
||||
console.error(`❌ Erreur récupération config NocoDB ${id}:`, error);
|
||||
return undefined;
|
||||
}
|
||||
const configs = await db.select({
|
||||
id: nocodbConfig.id,
|
||||
name: nocodbConfig.name,
|
||||
baseUrl: nocodbConfig.baseUrl,
|
||||
projectId: nocodbConfig.projectId,
|
||||
apiToken: nocodbConfig.apiToken,
|
||||
description: nocodbConfig.description,
|
||||
isActive: nocodbConfig.isActive,
|
||||
createdBy: nocodbConfig.createdBy,
|
||||
createdAt: nocodbConfig.createdAt,
|
||||
updatedAt: nocodbConfig.updatedAt,
|
||||
}).from(nocodbConfig).where(eq(nocodbConfig.id, id));
|
||||
return configs[0];
|
||||
}
|
||||
|
||||
async createNocodbConfig(config: InsertNocodbConfig): Promise<NocodbConfig> {
|
||||
@@ -2038,52 +2019,6 @@ export class DatabaseStorage implements IStorage {
|
||||
})
|
||||
.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
|
||||
@@ -2118,43 +2053,11 @@ class MemStorage implements IStorage {
|
||||
this.users.set('admin_dev', adminUser);
|
||||
this.users.set('admin', adminUser); // Also set by username for easy lookup
|
||||
|
||||
// Add test groups with NocoDB configuration
|
||||
// Add test groups
|
||||
const testGroups = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Magasin A',
|
||||
color: '#FF5733',
|
||||
webhookUrl: 'https://webhook.site/test-facture-magasin-a',
|
||||
nocodbConfigId: 1,
|
||||
nocodbTableName: 'factures_magasin_a',
|
||||
invoiceColumnName: 'reference_facture',
|
||||
nocodbBlColumnName: 'numero_bl',
|
||||
nocodbAmountColumnName: 'montant',
|
||||
nocodbSupplierColumnName: 'fournisseur',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Magasin B',
|
||||
color: '#33FF57',
|
||||
webhookUrl: 'https://webhook.site/test-facture-magasin-b',
|
||||
nocodbConfigId: 1,
|
||||
nocodbTableName: 'factures_magasin_b',
|
||||
invoiceColumnName: 'ref_facture',
|
||||
nocodbBlColumnName: 'bl_number',
|
||||
nocodbAmountColumnName: 'amount',
|
||||
nocodbSupplierColumnName: 'supplier_name',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Magasin C',
|
||||
color: '#3357FF',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
{ id: 1, name: 'Magasin A', color: '#FF5733', webhookUrl: 'https://webhook.site/test-facture-magasin-a', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: 2, name: 'Magasin B', color: '#33FF57', webhookUrl: 'https://webhook.site/test-facture-magasin-b', createdAt: new Date(), updatedAt: new Date() },
|
||||
{ id: 3, name: 'Magasin C', color: '#3357FF', createdAt: new Date(), updatedAt: new Date() }
|
||||
];
|
||||
testGroups.forEach(group => this.groups.set(group.id, group as Group));
|
||||
|
||||
@@ -2318,9 +2221,7 @@ class MemStorage implements IStorage {
|
||||
async getGroups(): Promise<Group[]> {
|
||||
return Array.from(this.groups.values());
|
||||
}
|
||||
async getGroup(id: number): Promise<Group | undefined> {
|
||||
return this.groups.get(id);
|
||||
}
|
||||
async getGroup(id: number): Promise<Group | undefined> { return undefined; }
|
||||
async createGroup(group: InsertGroup): Promise<Group> { return {} as Group; }
|
||||
async updateGroup(id: number, group: Partial<InsertGroup>): Promise<Group> { return {} as Group; }
|
||||
async deleteGroup(id: number): Promise<void> {}
|
||||
@@ -2548,20 +2449,7 @@ class MemStorage implements IStorage {
|
||||
async removeUserFromGroup(): Promise<void> {}
|
||||
async getMonthlyStats(): Promise<any> { return {}; }
|
||||
async getNocodbConfigs(): Promise<any[]> { return []; }
|
||||
async getNocodbConfig(id: number): Promise<any> {
|
||||
// Retourner une configuration de test pour le développement
|
||||
if (id === 1) {
|
||||
return {
|
||||
id: 1,
|
||||
name: 'Config Test NocoDB',
|
||||
baseUrl: 'https://nocodb-test.example.com',
|
||||
projectId: 'test-project',
|
||||
apiToken: 'test-token-123',
|
||||
isActive: true
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async getNocodbConfig(): Promise<any> { return null; }
|
||||
async createNocodbConfig(): Promise<any> { return {}; }
|
||||
async updateNocodbConfig(): Promise<any> { return {}; }
|
||||
async deleteNocodbConfig(): Promise<void> {}
|
||||
@@ -2622,52 +2510,6 @@ class MemStorage implements IStorage {
|
||||
return { userId, roleId, assignedBy, assignedAt: new Date() };
|
||||
}
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user