Add invoice verification system using NocoDB for automated data checks

Integrates NocoDB to verify invoice references and supplier matches, and allows searching by BL number to auto-populate delivery details, enhancing reconciliation accuracy and efficiency.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: c52a701b-4841-4d00-b8cf-5a41c12abdf3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/c52a701b-4841-4d00-b8cf-5a41c12abdf3/3uCfEKb
This commit is contained in:
michaelschal committed 2025-08-13 09:11:48 +00:00
1 parent d418fda3c9
commit bc85434858
5 files changed
+528 -25

No files matched your search

+166 -4
View File
@@ -12,7 +12,7 @@ import { useStore } from "@/components/Layout";
import { useAuthUnified } from "@/hooks/useAuthUnified";
import { usePermissions } from "@shared/permissions";
import { Pagination, usePagination } from "@/components/ui/pagination";
import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter, Upload } from "lucide-react";
import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter, Upload, CheckCircle, AlertCircle } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
@@ -58,6 +58,10 @@ export default function BLReconciliation() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isUploading, setIsUploading] = useState(false);
// État pour la vérification des factures
const [verificationResults, setVerificationResults] = useState<Record<number, any>>({});
const [isVerifying, setIsVerifying] = useState<Record<number, boolean>>({});
// Récupérer les fournisseurs pour la logique automatique
const { data: suppliers = [] } = useQuery<any[]>({
queryKey: ['/api/suppliers'],
@@ -213,6 +217,116 @@ export default function BLReconciliation() {
}
};
// Fonction pour vérifier une référence de facture
const verifyInvoiceReference = async (delivery: any) => {
if (!delivery.invoiceReference || !delivery.supplier?.name || !delivery.group?.id) {
return;
}
setIsVerifying(prev => ({ ...prev, [delivery.id]: true }));
try {
const response = await apiRequest('/api/deliveries/verify-invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
groupId: delivery.group.id,
invoiceReference: delivery.invoiceReference,
supplierName: delivery.supplier.name
})
});
setVerificationResults(prev => ({
...prev,
[delivery.id]: response
}));
if (response.found && response.supplierMatch) {
toast({
title: "✅ Facture vérifiée",
description: `Référence ${delivery.invoiceReference} trouvée et fournisseur correct`,
});
} else if (response.found && !response.supplierMatch) {
toast({
title: "⚠️ Fournisseur incorrect",
description: `Référence trouvée mais fournisseur ne correspond pas`,
variant: "destructive"
});
} else {
toast({
title: "❌ Référence non trouvée",
description: `Référence ${delivery.invoiceReference} introuvable`,
variant: "destructive"
});
}
} catch (error) {
console.error('Erreur vérification:', error);
toast({
title: "Erreur de vérification",
description: "Impossible de vérifier la référence",
variant: "destructive"
});
} finally {
setIsVerifying(prev => ({ ...prev, [delivery.id]: false }));
}
};
// Fonction pour rechercher par numéro de BL et auto-remplir
const searchByBLAndFill = async (delivery: any) => {
if (!delivery.blNumber || !delivery.supplier?.name || !delivery.group?.id) {
return;
}
setIsVerifying(prev => ({ ...prev, [delivery.id]: true }));
try {
const response = await apiRequest('/api/deliveries/search-by-bl', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
groupId: delivery.group.id,
blNumber: delivery.blNumber,
supplierName: delivery.supplier.name
})
});
if (response.found && response.invoiceReference) {
// Auto-remplir les champs facture
const updateData: any = {
invoiceReference: response.invoiceReference
};
if (response.invoiceAmount) {
updateData.invoiceAmount = response.invoiceAmount;
}
await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", updateData);
queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] });
toast({
title: "✅ Auto-remplissage réussi",
description: `Référence ${response.invoiceReference} trouvée et ajoutée`,
});
} else {
toast({
title: "❌ BL non trouvé",
description: `Aucune facture trouvée pour le BL ${delivery.blNumber}`,
variant: "destructive"
});
}
} catch (error) {
console.error('Erreur recherche BL:', error);
toast({
title: "Erreur de recherche",
description: "Impossible de rechercher par BL",
variant: "destructive"
});
} finally {
setIsVerifying(prev => ({ ...prev, [delivery.id]: false }));
}
};
// Fonction pour déterminer si le bouton "Envoyer Facture" doit être affiché
const shouldShowInvoiceButton = (delivery: any) => {
// Conditions : ligne non validée OU pas de RefFacture renseignée
@@ -536,9 +650,57 @@ export default function BLReconciliation() {
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className={`text-sm ${delivery.reconciled !== true ? 'font-medium text-gray-900' : 'text-gray-600'}`}>
{delivery.invoiceReference || (
<span className="text-gray-400 italic">Non renseigné</span>
<div className="flex items-center space-x-2">
{/* Coche verte si vérification réussie */}
{verificationResults[delivery.id]?.found && verificationResults[delivery.id]?.supplierMatch && (
<CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0" title="Facture vérifiée avec succès" />
)}
{/* Indicateur d'erreur si vérification échouée */}
{verificationResults[delivery.id]?.found && !verificationResults[delivery.id]?.supplierMatch && (
<AlertCircle className="w-4 h-4 text-red-500 flex-shrink-0" title="Fournisseur ne correspond pas" />
)}
{/* Référence de facture */}
<div className={`text-sm ${delivery.reconciled !== true ? 'font-medium text-gray-900' : 'text-gray-600'}`}>
{delivery.invoiceReference || (
<span className="text-gray-400 italic">Non renseigné</span>
)}
</div>
{/* Bouton de vérification si référence existe */}
{delivery.invoiceReference && delivery.group?.nocodbConfigId && (
<Button
variant="ghost"
size="sm"
onClick={() => verifyInvoiceReference(delivery)}
disabled={isVerifying[delivery.id]}
className="h-6 w-6 p-0 ml-1 flex-shrink-0"
title="Vérifier la référence facture dans NocoDB"
>
{isVerifying[delivery.id] ? (
<div className="w-3 h-3 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
) : (
<Search className="w-3 h-3 text-blue-600" />
)}
</Button>
)}
{/* Bouton d'auto-remplissage si pas de référence mais BL existe */}
{!delivery.invoiceReference && delivery.blNumber && delivery.group?.nocodbConfigId && (
<Button
variant="ghost"
size="sm"
onClick={() => searchByBLAndFill(delivery)}
disabled={isVerifying[delivery.id]}
className="h-6 w-6 p-0 ml-1 flex-shrink-0"
title="Rechercher et auto-remplir par numéro BL"
>
{isVerifying[delivery.id] ? (
<div className="w-3 h-3 border-2 border-green-500 border-t-transparent rounded-full animate-spin"></div>
) : (
<CheckCircle className="w-3 h-3 text-green-600" />
)}
</Button>
)}
</div>
</td>