mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-23 23:18:18 +02:00
3 files changed
+171
-32
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
+124
-32
@@ -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",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -421,6 +440,26 @@ export default function Avoirs() {
|
|||||||
...prev,
|
...prev,
|
||||||
[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);
|
||||||
@@ -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];
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user