mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-14 02:38:58 +02:00
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:
1 parent
d418fda3c9
commit
bc85434858
5 files changed
+528
-25
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user