mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-23 06:58:19 +02:00
2 files changed
+191
-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
|
||||||
@@ -35,6 +39,10 @@ externalPort = 3001
|
|||||||
localPort = 41735
|
localPort = 41735
|
||||||
externalPort = 4200
|
externalPort = 4200
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 46559
|
||||||
|
externalPort = 5173
|
||||||
|
|
||||||
[workflows]
|
[workflows]
|
||||||
runButton = "Project"
|
runButton = "Project"
|
||||||
|
|
||||||
|
|||||||
@@ -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