Merge pull request #391 from R0m1k3/1.1

1.1
This commit is contained in:
LogiFlow authored and GitHub committed 2025-09-08 20:47:29 +02:00
commit 435721844f
3 files changed
+171 -32

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

+124 -32
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Plus, Search, FileText, CheckCircle, AlertCircle, Clock, Edit, Trash2, UserCheck, Send, Upload, XCircle } from "lucide-react"; import { Plus, Search, FileText, CheckCircle, AlertCircle, Clock, Edit, Trash2, UserCheck, Send, Upload, XCircle } from "lucide-react";
import { useStore } from "@/components/Layout"; import { useStore } from "@/components/Layout";
@@ -362,42 +362,61 @@ export default function Avoirs() {
}; };
// Handle validation/devalidation // Handle validation/devalidation
const handleValidateAvoir = (avoirId: number) => { const handleValidateAvoir = async (avoirId: number) => {
const avoir = avoirs.find(a => a.id === avoirId); try {
if (avoir) { // Utiliser la route spécifique pour la vérification NocoDB
editAvoirMutation.mutate({ await apiRequest(`/api/avoirs/${avoirId}/nocodb-verification`, 'PUT', {
id: avoirId, verified: true
data: { });
supplierId: avoir.supplierId,
groupId: avoir.groupId, // Marquer le cache comme réconcilié (permanent)
invoiceReference: avoir.invoiceReference || "", const avoir = avoirs.find(a => a.id === avoirId);
amount: avoir.amount, if (avoir?.invoiceReference) {
comment: avoir.comment || "", try {
commercialProcessed: avoir.commercialProcessed, await apiRequest('/api/cache/mark-reconciled', 'POST', {
status: avoir.status as "En attente de demande" | "Demandé" | "Reçu", invoiceReference: avoir.invoiceReference,
nocodbVerified: true, groupId: avoir.groupId
nocodbVerifiedAt: new Date(), });
} catch (cacheError) {
console.warn('Cache marking failed but verification succeeded:', cacheError);
} }
}
// Recharger les avoirs pour voir les changements
queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] });
toast({
title: "Avoir validé",
description: "L'avoir a été marqué comme validé avec succès",
});
} catch (error) {
toast({
title: "Erreur de validation",
description: error instanceof Error ? error.message : "Erreur lors de la validation",
variant: "destructive",
}); });
} }
}; };
const handleDevalidateAvoir = (avoirId: number) => { const handleDevalidateAvoir = async (avoirId: number) => {
const avoir = avoirs.find(a => a.id === avoirId); try {
if (avoir) { // Utiliser la route spécifique pour la vérification NocoDB
editAvoirMutation.mutate({ await apiRequest(`/api/avoirs/${avoirId}/nocodb-verification`, 'PUT', {
id: avoirId, verified: false
data: { });
supplierId: avoir.supplierId,
groupId: avoir.groupId, // Recharger les avoirs pour voir les changements
invoiceReference: avoir.invoiceReference || "", queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] });
amount: avoir.amount,
comment: avoir.comment || "", toast({
commercialProcessed: avoir.commercialProcessed, title: "Avoir dévalidé",
status: avoir.status as "En attente de demande" | "Demandé" | "Reçu", description: "L'avoir a été marqué comme non-validé avec succès",
nocodbVerified: false, });
nocodbVerifiedAt: null, } catch (error) {
} toast({
title: "Erreur de dévalidation",
description: error instanceof Error ? error.message : "Erreur lors de la dévalidation",
variant: "destructive",
}); });
} }
}; };
@@ -422,6 +441,26 @@ export default function Avoirs() {
[variables.avoirId]: result [variables.avoirId]: result
})); }));
// Auto-remplissage si facture trouvée et montant disponible
if (result.exists && result.invoiceAmount) {
// Auto-remplir le montant dans l'avoir via API
const avoir = avoirs.find(a => a.id === variables.avoirId);
if (avoir) {
editAvoirMutation.mutate({
id: variables.avoirId,
data: {
supplierId: avoir.supplierId,
groupId: avoir.groupId,
invoiceReference: avoir.invoiceReference || "",
amount: result.invoiceAmount, // Auto-remplissage du montant
comment: avoir.comment || "",
commercialProcessed: avoir.commercialProcessed,
status: avoir.status as "En attente de demande" | "Demandé" | "Reçu",
}
});
}
}
setVerifyingAvoirs(prev => { setVerifyingAvoirs(prev => {
const newSet = new Set(prev); const newSet = new Set(prev);
newSet.delete(variables.avoirId); newSet.delete(variables.avoirId);
@@ -519,6 +558,59 @@ export default function Avoirs() {
}); });
}; };
// Charger les résultats de vérification depuis le cache au démarrage
useEffect(() => {
const loadVerificationResults = async () => {
if (!avoirs || avoirs.length === 0) return;
const avoirsWithReferences = avoirs.filter(avoir =>
avoir.invoiceReference?.trim() &&
(avoir.group?.nocodbTableName || avoir.group?.nocodbConfigId)
);
if (avoirsWithReferences.length === 0) return;
console.log(`🔍 Chargement des résultats de vérification pour ${avoirsWithReferences.length} avoirs`);
// Charger les résultats depuis le cache en parallèle
const results = await Promise.allSettled(
avoirsWithReferences.map(async (avoir) => {
try {
const result = await apiRequest(`/api/avoirs/${avoir.id}/verify-invoice`, 'POST', {
invoiceReference: avoir.invoiceReference,
forceRefresh: false // Utiliser le cache si disponible
});
return { avoirId: avoir.id, result };
} catch (error) {
console.error(`Erreur chargement cache avoir ${avoir.id}:`, error);
return {
avoirId: avoir.id,
result: {
exists: false,
matchType: 'none',
errorMessage: 'Erreur de chargement'
}
};
}
})
);
// Appliquer les résultats chargés
const newResults: Record<number, any> = {};
results.forEach((result) => {
if (result.status === 'fulfilled') {
const { avoirId, result: verificationResult } = result.value;
newResults[avoirId] = verificationResult;
}
});
setAvoirVerificationResults(newResults);
console.log(`✅ Résultats de vérification chargés pour ${Object.keys(newResults).length} avoirs`);
};
loadVerificationResults();
}, [avoirs]); // Se déclenche quand les avoirs sont chargés
// 🔥 FONCTIONS WEBHOOK MODAL (comme rapprochement) // 🔥 FONCTIONS WEBHOOK MODAL (comme rapprochement)
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
+47
View File
@@ -2718,6 +2718,24 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
await storage.updateAvoirNocodbVerification(id, verified); await storage.updateAvoirNocodbVerification(id, verified);
// Si validé, marquer le cache comme réconcilié (permanent)
if (verified) {
try {
const avoir = await storage.getAvoir(id);
if (avoir?.invoiceReference?.trim()) {
await invoiceVerificationService.updateCacheAsReconciled(
avoir.invoiceReference,
avoir.groupId
);
console.log('✅ Cache marqué comme réconcilié pour avoir:', id);
}
} catch (cacheError) {
console.error('❌ Erreur marquage cache réconcilié:', cacheError);
// Ne pas faire échouer la validation si le cache échoue
}
}
console.log('✅ Avoir NocoDB verification updated:', id, 'verified:', verified, 'by user:', user.id); console.log('✅ Avoir NocoDB verification updated:', id, 'verified:', verified, 'by user:', user.id);
res.json({ message: "NocoDB verification status updated successfully" }); res.json({ message: "NocoDB verification status updated successfully" });
} catch (error) { } catch (error) {
@@ -2726,6 +2744,35 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
}); });
// Route pour marquer explicitement le cache comme réconcilié
app.post('/api/cache/mark-reconciled', 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 { invoiceReference, groupId } = req.body;
if (!invoiceReference || !groupId) {
return res.status(400).json({ message: "Invoice reference and group ID required" });
}
// Seuls admin et directeur peuvent marquer comme réconcilié
if (user.role !== 'admin' && user.role !== 'directeur') {
return res.status(403).json({ message: "Insufficient permissions" });
}
await invoiceVerificationService.updateCacheAsReconciled(invoiceReference, groupId);
console.log('✅ Cache marqué comme réconcilié:', { invoiceReference, groupId, user: user.id });
res.json({ message: "Cache marked as reconciled successfully" });
} catch (error) {
console.error("Error marking cache as reconciled:", error);
res.status(500).json({ message: "Failed to mark cache as reconciled" });
}
});
// Statistics routes // Statistics routes
app.get('/api/stats/monthly', isAuthenticated, async (req: any, res) => { app.get('/api/stats/monthly', isAuthenticated, async (req: any, res) => {
try { try {