mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Improve task reconciliation by separating validated and unvalidated deliveries
Refactors the BLReconciliation page to distinguish between manually reconciled and validated deliveries, updates the "Verify All Invoices" logic to only process unvalidated items, and simplifies the filtering to rely solely on the search term. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 1e4433b5-47ab-464c-b663-fea2e53367dd Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/1e4433b5-47ab-464c-b663-fea2e53367dd/awJgdDR
This commit is contained in:
1 parent
72f2632f01
commit
78d41738a5
2 files changed
+187
-201
No files matched your search
@@ -19,6 +19,10 @@ externalPort = 80
|
|||||||
localPort = 33873
|
localPort = 33873
|
||||||
externalPort = 3003
|
externalPort = 3003
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 38213
|
||||||
|
externalPort = 5000
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 38343
|
localPort = 38343
|
||||||
externalPort = 3000
|
externalPort = 3000
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export default function BLReconciliation() {
|
|||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [selectedDelivery, setSelectedDelivery] = useState<any>(null);
|
const [selectedDelivery, setSelectedDelivery] = useState<any>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [filterStatus, setFilterStatus] = useState<"all" | "validated" | "not_validated">("all");
|
|
||||||
|
|
||||||
// État pour le modal d'envoi de facture
|
// État pour le modal d'envoi de facture
|
||||||
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
|
const [showInvoiceModal, setShowInvoiceModal] = useState(false);
|
||||||
@@ -190,7 +189,7 @@ export default function BLReconciliation() {
|
|||||||
|
|
||||||
// Fonction pour vérifier toutes les factures avec un bouton
|
// Fonction pour vérifier toutes les factures avec un bouton
|
||||||
const handleVerifyAllInvoices = () => {
|
const handleVerifyAllInvoices = () => {
|
||||||
const deliveriesToVerify = manualReconciliationDeliveries.filter(delivery =>
|
const deliveriesToVerify = manualNotValidatedDeliveries.filter((delivery: any) =>
|
||||||
(delivery.invoiceReference?.trim() || delivery.blNumber?.trim()) &&
|
(delivery.invoiceReference?.trim() || delivery.blNumber?.trim()) &&
|
||||||
(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl)
|
(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl)
|
||||||
);
|
);
|
||||||
@@ -203,7 +202,7 @@ export default function BLReconciliation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deliveriesToVerify.forEach((delivery, index) => {
|
deliveriesToVerify.forEach((delivery: any, index: number) => {
|
||||||
// Délai échelonné pour éviter la surcharge
|
// Délai échelonné pour éviter la surcharge
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handleVerifyInvoice(delivery, true); // Force refresh pour toutes
|
handleVerifyInvoice(delivery, true); // Force refresh pour toutes
|
||||||
@@ -310,15 +309,16 @@ export default function BLReconciliation() {
|
|||||||
});
|
});
|
||||||
}, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]);
|
}, [deliveriesWithBL, suppliers, verificationResults, verifyingDeliveries]);
|
||||||
|
|
||||||
// Séparer les livraisons par mode de rapprochement
|
// Séparer les livraisons : non validées manuelles et toutes les validées
|
||||||
const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
const manualNotValidatedDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
||||||
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
||||||
return supplier?.automaticReconciliation !== true;
|
const isManual = supplier?.automaticReconciliation !== true;
|
||||||
|
const isNotValidated = delivery.reconciled !== true && delivery.reconciled !== 1;
|
||||||
|
return isManual && isNotValidated;
|
||||||
});
|
});
|
||||||
|
|
||||||
const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
const allValidatedDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
||||||
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
return delivery.reconciled === true || delivery.reconciled === 1;
|
||||||
return supplier?.automaticReconciliation === true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fonctions de gestion
|
// Fonctions de gestion
|
||||||
@@ -626,45 +626,23 @@ export default function BLReconciliation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filtrage des livraisons par recherche et statut de validation
|
// Filtrage des livraisons par recherche uniquement
|
||||||
const filterDeliveries = (deliveries: any[]) => {
|
const filterDeliveries = (deliveries: any[]) => {
|
||||||
|
if (!searchTerm) return deliveries;
|
||||||
|
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
return deliveries.filter((delivery: any) => {
|
return deliveries.filter((delivery: any) => {
|
||||||
// Conversion sûre de reconciled - gestion de tous les cas possibles
|
|
||||||
const isReconciled =
|
|
||||||
delivery.reconciled === true ||
|
|
||||||
delivery.reconciled === 1 ||
|
|
||||||
delivery.reconciled === "t" ||
|
|
||||||
delivery.reconciled === "true" ||
|
|
||||||
(typeof delivery.reconciled === 'string' && delivery.reconciled.toLowerCase() === 'true');
|
|
||||||
|
|
||||||
// Vérifier aussi si une facture a été trouvée ET validée automatiquement
|
|
||||||
// (certaines livraisons ont une coche verte mais reconciled = false)
|
|
||||||
const hasVerifiedInvoice = verificationResults[delivery.id]?.exists === true;
|
|
||||||
const isValidated = isReconciled || (hasVerifiedInvoice && verificationResults[delivery.id]?.reconciled === true);
|
|
||||||
|
|
||||||
// Filtre par statut validé
|
|
||||||
if (filterStatus === "validated") {
|
|
||||||
// Afficher seulement les livraisons vraiment validées (reconciled = true)
|
|
||||||
return isReconciled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterStatus === "not_validated") {
|
|
||||||
// Afficher seulement les livraisons non validées (reconciled = false)
|
|
||||||
return !isReconciled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtre par recherche
|
|
||||||
const searchLower = searchTerm.toLowerCase();
|
|
||||||
return (
|
return (
|
||||||
delivery.supplier?.name?.toLowerCase().includes(searchLower) ||
|
delivery.supplier?.name?.toLowerCase().includes(searchLower) ||
|
||||||
delivery.blNumber?.toLowerCase().includes(searchLower) ||
|
delivery.blNumber?.toLowerCase().includes(searchLower) ||
|
||||||
delivery.invoiceReference?.toLowerCase().includes(searchLower)
|
delivery.invoiceReference?.toLowerCase().includes(searchLower) ||
|
||||||
|
delivery.group?.name?.toLowerCase().includes(searchLower)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries);
|
const filteredManualDeliveries = filterDeliveries(manualNotValidatedDeliveries);
|
||||||
const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries);
|
const filteredValidatedDeliveries = filterDeliveries(allValidatedDeliveries);
|
||||||
|
|
||||||
// Pagination pour les rapprochements manuels
|
// Pagination pour les rapprochements manuels
|
||||||
const {
|
const {
|
||||||
@@ -677,16 +655,16 @@ export default function BLReconciliation() {
|
|||||||
totalItems: manualTotalItems
|
totalItems: manualTotalItems
|
||||||
} = usePagination(filteredManualDeliveries, 20);
|
} = usePagination(filteredManualDeliveries, 20);
|
||||||
|
|
||||||
// Pagination pour les rapprochements automatiques
|
// Pagination pour les livraisons validées
|
||||||
const {
|
const {
|
||||||
currentPage: autoCurrentPage,
|
currentPage: validatedCurrentPage,
|
||||||
setCurrentPage: setAutoCurrentPage,
|
setCurrentPage: setValidatedCurrentPage,
|
||||||
itemsPerPage: autoItemsPerPage,
|
itemsPerPage: validatedItemsPerPage,
|
||||||
setItemsPerPage: setAutoItemsPerPage,
|
setItemsPerPage: setValidatedItemsPerPage,
|
||||||
totalPages: autoTotalPages,
|
totalPages: validatedTotalPages,
|
||||||
paginatedData: paginatedAutoDeliveries,
|
paginatedData: paginatedValidatedDeliveries,
|
||||||
totalItems: autoTotalItems
|
totalItems: validatedTotalItems
|
||||||
} = usePagination(filteredAutomaticDeliveries, 20);
|
} = usePagination(filteredValidatedDeliveries, 20);
|
||||||
|
|
||||||
const canModify = user?.role === 'directeur' || user?.role === 'admin';
|
const canModify = user?.role === 'directeur' || user?.role === 'admin';
|
||||||
|
|
||||||
@@ -710,10 +688,10 @@ export default function BLReconciliation() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2 sm:gap-4">
|
<div className="flex flex-wrap items-center gap-2 sm:gap-4">
|
||||||
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300">
|
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300">
|
||||||
{manualReconciliationDeliveries.length} manuels
|
{manualNotValidatedDeliveries.length} à traiter
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300 bg-blue-50">
|
<Badge variant="outline" className="text-xs sm:text-sm border border-gray-300 bg-green-50">
|
||||||
{automaticReconciliationDeliveries.length} automatiques
|
{allValidatedDeliveries.length} validées
|
||||||
</Badge>
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleVerifyAllInvoices}
|
onClick={handleVerifyAllInvoices}
|
||||||
@@ -734,59 +712,30 @@ export default function BLReconciliation() {
|
|||||||
<Edit className="w-4 h-4" />
|
<Edit className="w-4 h-4" />
|
||||||
<span>Rapprochement Manuel</span>
|
<span>Rapprochement Manuel</span>
|
||||||
<Badge variant="secondary" className="ml-2">
|
<Badge variant="secondary" className="ml-2">
|
||||||
{manualReconciliationDeliveries.length}
|
{manualNotValidatedDeliveries.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="automatic" className="flex items-center space-x-2">
|
<TabsTrigger value="validated" className="flex items-center space-x-2">
|
||||||
<Settings className="w-4 h-4" />
|
<CheckCircle className="w-4 h-4" />
|
||||||
<span>Rapprochement Automatique</span>
|
<span>Livraisons Validées</span>
|
||||||
<Badge variant="secondary" className="ml-2">
|
<Badge variant="secondary" className="ml-2">
|
||||||
{automaticReconciliationDeliveries.length}
|
{allValidatedDeliveries.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Barre de recherche */}
|
||||||
<div className="bg-gray-50 border border-gray-200 p-4 rounded-lg">
|
<div className="bg-gray-50 border border-gray-200 p-4 rounded-lg">
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
|
<div className="relative">
|
||||||
<div className="flex-1 min-w-0">
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
<div className="relative">
|
<Input
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
placeholder="Rechercher par fournisseur, BL, facture ou magasin..."
|
||||||
<Input
|
value={searchTerm}
|
||||||
placeholder="Rechercher par fournisseur, BL ou facture..."
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
value={searchTerm}
|
className="pl-10 border border-gray-300 shadow-sm w-full"
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
/>
|
||||||
className="pl-10 border border-gray-300 shadow-sm w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full sm:w-48">
|
|
||||||
<Select value={filterStatus} onValueChange={(value: "all" | "validated" | "not_validated") => setFilterStatus(value)}>
|
|
||||||
<SelectTrigger className="border border-gray-300 shadow-sm">
|
|
||||||
<Filter className="w-4 h-4 mr-2" />
|
|
||||||
<SelectValue placeholder="Filtrer par statut" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">
|
|
||||||
Tous les rapprochements
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="validated">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Check className="w-4 h-4 mr-2 text-green-600" />
|
|
||||||
Validés seulement
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="not_validated">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<X className="w-4 h-4 mr-2 text-orange-600" />
|
|
||||||
Non validés seulement
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1098,15 +1047,15 @@ export default function BLReconciliation() {
|
|||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="automatic" className="space-y-6">
|
<TabsContent value="validated" className="space-y-6">
|
||||||
{/* Message d'information */}
|
{/* Message d'information */}
|
||||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<AlertTriangle className="w-5 h-5 text-blue-600 mt-0.5" />
|
<CheckCircle className="w-5 h-5 text-green-600 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium text-blue-900">Mode rapprochement automatique</h4>
|
<h4 className="text-sm font-medium text-green-900">Livraisons validées</h4>
|
||||||
<p className="text-sm text-blue-700 mt-1">
|
<p className="text-sm text-green-700 mt-1">
|
||||||
Les livraisons de fournisseurs en mode automatique sont validées automatiquement lorsqu'elles ont le statut "delivered" et un numéro de BL.
|
Cette section regroupe toutes les livraisons validées (manuellement ou automatiquement).
|
||||||
{(permissions.canEdit('reconciliation') || permissions.canValidate('reconciliation')) ? (
|
{(permissions.canEdit('reconciliation') || permissions.canValidate('reconciliation')) ? (
|
||||||
" Vous pouvez dévalider ces rapprochements si nécessaire."
|
" Vous pouvez dévalider ces rapprochements si nécessaire."
|
||||||
) : (
|
) : (
|
||||||
@@ -1117,14 +1066,14 @@ export default function BLReconciliation() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filteredAutomaticDeliveries.length === 0 ? (
|
{filteredValidatedDeliveries.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<Settings className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<CheckCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||||
Aucun rapprochement automatique trouvé
|
Aucune livraison validée trouvée
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Les livraisons de fournisseurs en mode automatique apparaîtront ici.
|
Les livraisons validées apparaîtront ici.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -1133,22 +1082,21 @@ export default function BLReconciliation() {
|
|||||||
{/* Pagination du haut */}
|
{/* Pagination du haut */}
|
||||||
<div className="p-4 border-b border-gray-200">
|
<div className="p-4 border-b border-gray-200">
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={autoCurrentPage}
|
currentPage={validatedCurrentPage}
|
||||||
totalPages={autoTotalPages}
|
totalPages={validatedTotalPages}
|
||||||
totalItems={autoTotalItems}
|
totalItems={validatedTotalItems}
|
||||||
itemsPerPage={autoItemsPerPage}
|
itemsPerPage={validatedItemsPerPage}
|
||||||
onPageChange={setAutoCurrentPage}
|
onPageChange={setValidatedCurrentPage}
|
||||||
onItemsPerPageChange={setAutoItemsPerPage}
|
onItemsPerPageChange={setValidatedItemsPerPage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<table className="w-full min-w-[700px]">
|
<table className="w-full min-w-[900px]">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Fournisseur
|
Fournisseur
|
||||||
<Badge variant="secondary" className="ml-2 text-xs">AUTO</Badge>
|
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
N° BL
|
N° BL
|
||||||
@@ -1157,7 +1105,16 @@ export default function BLReconciliation() {
|
|||||||
Date Livr.
|
Date Livr.
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Date Valid.
|
Ref. Facture
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Montant BL
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Montant Fact.
|
||||||
|
</th>
|
||||||
|
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
|
Écart
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Magasin
|
Magasin
|
||||||
@@ -1168,91 +1125,116 @@ export default function BLReconciliation() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{paginatedAutoDeliveries.map((delivery: any) => (
|
{paginatedValidatedDeliveries.map((delivery: any) => {
|
||||||
<tr key={delivery.id} className="hover:bg-gray-50 bg-green-50">
|
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
||||||
<td className="px-3 py-2 text-sm">
|
const isAutomatic = supplier?.automaticReconciliation === true;
|
||||||
<div className="font-medium text-gray-900 truncate max-w-32">
|
const ecart = delivery.blAmount && delivery.invoiceAmount ?
|
||||||
{delivery.supplier?.name}
|
((parseFloat(delivery.invoiceAmount) - parseFloat(delivery.blAmount)) / parseFloat(delivery.blAmount) * 100).toFixed(1) :
|
||||||
</div>
|
null;
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
return (
|
||||||
<div className="text-gray-900">
|
<tr key={delivery.id} className="hover:bg-gray-50 bg-green-50">
|
||||||
{delivery.blNumber || (
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
<span
|
<div className="flex items-center space-x-2">
|
||||||
className="text-gray-400 italic text-xs hover:text-blue-500 cursor-pointer hover:underline transition-colors"
|
<div className="font-medium text-gray-900 truncate max-w-32">
|
||||||
|
{delivery.supplier?.name}
|
||||||
|
</div>
|
||||||
|
{isAutomatic && (
|
||||||
|
<Badge variant="secondary" className="text-xs">AUTO</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{delivery.blNumber || (
|
||||||
|
<span className="text-gray-400 italic text-xs">Non renseigné</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{safeFormat(delivery.scheduledDate, 'dd/MM/yy')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{delivery.invoiceReference || (
|
||||||
|
<span className="text-gray-400 italic text-xs">Non renseignée</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{delivery.blAmount ?
|
||||||
|
`${parseFloat(delivery.blAmount).toFixed(2)}€` :
|
||||||
|
<span className="text-gray-400 italic text-xs">Non renseigné</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{delivery.invoiceAmount ?
|
||||||
|
`${parseFloat(delivery.invoiceAmount).toFixed(2)}€` :
|
||||||
|
<span className="text-gray-400 italic text-xs">Non renseigné</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-center">
|
||||||
|
{ecart !== null ? (
|
||||||
|
<Badge
|
||||||
|
variant={parseFloat(ecart) === 0 ? "outline" : Math.abs(parseFloat(ecart)) > 5 ? "destructive" : "secondary"}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{parseFloat(ecart) > 0 ? '+' : ''}{ecart}%
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-400 text-xs">-</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{delivery.group?.name}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-right">
|
||||||
|
<div className="flex items-center justify-end space-x-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleOpenCommentModal(delivery)}
|
||||||
|
className="transition-colors duration-200 p-1 rounded text-gray-600 hover:text-gray-700 hover:bg-gray-50 opacity-70"
|
||||||
|
title="Voir/Gérer les commentaires"
|
||||||
|
>
|
||||||
|
<MessageSquare className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
{(permissions.canEdit('reconciliation') || permissions.canValidate('reconciliation')) && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleDevalidateReconciliation(delivery.id)}
|
||||||
|
className="text-gray-600 hover:text-orange-600 transition-colors duration-200 p-1 hover:bg-orange-50 rounded"
|
||||||
|
title="Dévalider le rapprochement"
|
||||||
|
>
|
||||||
|
<Ban className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{permissions.canDelete('reconciliation') && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteDelivery(delivery.id)}
|
||||||
|
className="text-gray-600 hover:text-red-600 transition-colors duration-200 p-1 hover:bg-red-50 rounded"
|
||||||
|
title="Supprimer la livraison"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
onClick={() => handleOpenModal(delivery)}
|
onClick={() => handleOpenModal(delivery)}
|
||||||
title="Cliquer pour modifier"
|
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||||
|
title="Voir les détails"
|
||||||
>
|
>
|
||||||
Non renseigné
|
<Eye className="w-4 h-4" />
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
{safeFormat(delivery.scheduledDate, 'dd/MM/yy')}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
{delivery.validatedAt ?
|
|
||||||
safeFormat(delivery.validatedAt, 'dd/MM/yy HH:mm') :
|
|
||||||
<span className="text-gray-400 italic text-xs">Non validé</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
{delivery.group?.name}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
{shouldShowInvoiceButton(delivery) && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleOpenInvoiceModal(delivery)}
|
|
||||||
className="text-green-600 hover:text-green-700 transition-colors duration-200 p-1 hover:bg-green-50 rounded"
|
|
||||||
title="Envoyer Facture/Avoir"
|
|
||||||
>
|
|
||||||
<Upload className="h-4 w-4" />
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
</div>
|
||||||
<button
|
</td>
|
||||||
onClick={() => handleOpenCommentModal(delivery)}
|
</tr>
|
||||||
className={`transition-colors duration-200 p-1 rounded text-gray-600 hover:text-gray-700 hover:bg-gray-50 ${delivery.reconciled ? 'opacity-70' : ''}`}
|
);
|
||||||
title="Voir/Gérer les commentaires"
|
})}
|
||||||
>
|
|
||||||
<MessageSquare className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
{(permissions.canEdit('reconciliation') || permissions.canValidate('reconciliation')) && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleDevalidateReconciliation(delivery.id)}
|
|
||||||
className="text-gray-600 hover:text-orange-600 transition-colors duration-200 p-1 hover:bg-orange-50 rounded"
|
|
||||||
title="Dévalider le rapprochement automatique"
|
|
||||||
>
|
|
||||||
<Ban className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{permissions.canDelete('reconciliation') && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleDeleteDelivery(delivery.id)}
|
|
||||||
className="text-gray-600 hover:text-red-600 transition-colors duration-200 p-1 hover:bg-red-50 rounded"
|
|
||||||
title="Supprimer la livraison"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => handleOpenModal(delivery)}
|
|
||||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
|
||||||
title="Voir les détails"
|
|
||||||
>
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -1260,12 +1242,12 @@ export default function BLReconciliation() {
|
|||||||
{/* Pagination du bas */}
|
{/* Pagination du bas */}
|
||||||
<div className="p-4 border-t border-gray-200">
|
<div className="p-4 border-t border-gray-200">
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={autoCurrentPage}
|
currentPage={validatedCurrentPage}
|
||||||
totalPages={autoTotalPages}
|
totalPages={validatedTotalPages}
|
||||||
totalItems={autoTotalItems}
|
totalItems={validatedTotalItems}
|
||||||
itemsPerPage={autoItemsPerPage}
|
itemsPerPage={validatedItemsPerPage}
|
||||||
onPageChange={setAutoCurrentPage}
|
onPageChange={setValidatedCurrentPage}
|
||||||
onItemsPerPageChange={setAutoItemsPerPage}
|
onItemsPerPageChange={setValidatedItemsPerPage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user