fix: corrections et améliorations du 01-04-2026

## Recouvrement
- Liste triée par date décroissante
- Pop-up de confirmation anti-doublons
- Affichage avec permission "Voir" seule

## Mise à jour produit
- Correction erreur "Column count doesn't match" (triggers MySQL corrigés)
- Formulaire corrigé (action, catégorie, champs null)
- Catégorie et date d'arrivage correctement préremplis
- Historique affiche le nom de l'utilisateur qui modifie

## Espace commercial
- Colonne "Disponibilité" ajoutée avec statut "En attente de livraison"
- Bouton panier caché pour les motos commandées
- Surbrillance jaune pour les motos en attente

## Notifications
- Caissière reçoit les notifications via notifCommande
- notifSortieCaisse réservé à Direction/Admin

## Avances
- Colonne "N° Série" ajoutée dans toutes les listes
- Compteurs sur les boutons Incomplètes/Complètes

## Facture / BL
- Total, Remise, Total à payer affichés
- "Ariary" ne se répète plus
- Prix individuel par moto
- Impression automatique : 1 produit = Facture, 2+ = BL
- Remise multiple : colonne product changée en TEXT

## Rapports
- Filtre par date dans le rapport stock
- Filtre par commercial et mécanicien dans les performances
- Correction rapport stock (GROUP BY marque)
- Liens absolus (correction erreur 404)

## Sidebar
- Marge en haut supprimée (production)
- Padding en bas ajouté pour scroll complet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
andrymodeste 2026-04-01 19:56:34 +02:00
parent a195d24e78
commit fe80b9c4f8
23 changed files with 534 additions and 313 deletions

View File

@ -226,7 +226,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('detail/stock', [ReportController::class, 'stockDetail']); $routes->get('detail/stock', [ReportController::class, 'stockDetail']);
// Corrections fetct → fetch // Corrections fetct → fetch
$routes->get('detail/fetchData/(:num)', [ReportController::class, 'fetchProductSold/$1']); $routes->get('detail/fetchData/(:num)', [ReportController::class, 'fetchProductSodled/$1']);
$routes->get('detail/fetchDataStock/(:num)', [ReportController::class, 'fetchProductStock/$1']); $routes->get('detail/fetchDataStock/(:num)', [ReportController::class, 'fetchProductStock/$1']);
$routes->get('detail/fetchDataStock2/(:num)', [ReportController::class, 'fetchProductStock2/$1']); $routes->get('detail/fetchDataStock2/(:num)', [ReportController::class, 'fetchProductStock2/$1']);

View File

@ -24,7 +24,7 @@ abstract class AdminController extends BaseController
$group_data = $Groups->getUserGroupByUserId($userId); $group_data = $Groups->getUserGroupByUserId($userId);
$this->permission = unserialize($group_data['permission']); $this->permission = unserialize($group_data['permission']);
} }
} }

View File

@ -129,7 +129,8 @@ public function loginPost()
$buttons .= " <a href='/ventes/show/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-eye'></i></a>"; $buttons .= " <a href='/ventes/show/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-eye'></i></a>";
} }
if (is_array($this->permission) && in_array('createOrder', $this->permission)) { $productSoldStatus = (int)($value['product_sold'] ?? 0);
if (is_array($this->permission) && in_array('createOrder', $this->permission) && $productSoldStatus === 0) {
$buttons .= ($value['qty'] == 1) $buttons .= ($value['qty'] == 1)
? " <a href='/orders/createFromEspace/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-shopping-cart'></i></a>" ? " <a href='/orders/createFromEspace/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-shopping-cart'></i></a>"
: " <button class='btn btn-default' title='0 en stock'><i class='fa fa-shopping-cart'></i></button>"; : " <button class='btn btn-default' title='0 en stock'><i class='fa fa-shopping-cart'></i></button>";
@ -139,6 +140,16 @@ public function loginPost()
$img = '<img src="' . base_url('assets/images/product_image/' . $value['image']) . '" alt="' . $value['image'] . '" class="img-circle" width="50" height="50" />'; $img = '<img src="' . base_url('assets/images/product_image/' . $value['image']) . '" alt="' . $value['image'] . '" class="img-circle" width="50" height="50" />';
// Statut basé sur product_sold
$productSold = (int)($value['product_sold'] ?? 0);
if ($productSold === 2) {
$statut = '<span class="label label-warning">En attente de livraison</span>';
} elseif ($productSold === 1) {
$statut = '<span class="label label-default">Livré</span>';
} else {
$statut = '<span class="label label-success">Disponible</span>';
}
// Populate the result data // Populate the result data
$result['data'][] = [ $result['data'][] = [
$img, $img,
@ -147,6 +158,7 @@ public function loginPost()
number_format($value['prix_vente'], 0, ',', ' '), number_format($value['prix_vente'], 0, ',', ' '),
$value['puissance'] . ' CC', $value['puissance'] . ' CC',
$value['numero_de_moteur'], $value['numero_de_moteur'],
$statut,
$buttons $buttons
]; ];
} }

View File

@ -116,21 +116,23 @@ private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCai
{ {
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); $date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
// ✅ Gestion sécurisée du nom du produit // Gestion du nom du produit et numéro de série
$productSku = '';
if ($value['type_avance'] === 'mere') { if ($value['type_avance'] === 'mere') {
$productName = $value['product_name'] ?? 'Produit sur mer'; $productName = $value['product_name'] ?? 'Produit sur mer';
} else { } else {
$productName = !empty($value['product_name']) $productData = !empty($value['product_id']) ? $product->find($value['product_id']) : null;
? $value['product_name'] $productName = $productData['name'] ?? ($value['product_name'] ?? 'N/A');
: $product->getProductNameById($value['product_id'] ?? 0); $productSku = $productData['sku'] ?? '';
} }
if ($isAdmin) { if ($isAdmin) {
return [ return [
$value['customer_name'], $value['customer_name'],
$value['customer_phone'], $value['customer_phone'],
$value['customer_address'], $value['customer_address'],
$productName, $productName,
$productSku,
number_format((int)$value['gross_amount'], 0, ',', ' '), number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '), number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '), number_format((int)$value['amount_due'], 0, ',', ' '),
@ -141,6 +143,7 @@ private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCai
return [ return [
$value['avance_id'], $value['avance_id'],
$productName, $productName,
$productSku,
number_format((int)$value['avance_amount'], 0, ',', ' '), number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '), number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time, $date_time,
@ -228,12 +231,13 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); $date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
$productSku = '';
if ($value['type_avance'] === 'mere') { if ($value['type_avance'] === 'mere') {
$productName = $value['product_name'] ?? 'Produit sur mer'; $productName = $value['product_name'] ?? 'Produit sur mer';
} else { } else {
$productName = !empty($value['product_name']) $productData = !empty($value['product_id']) ? $product->find($value['product_id']) : null;
? $value['product_name'] $productName = $productData['name'] ?? ($value['product_name'] ?? 'N/A');
: $product->getProductNameById($value['product_id'] ?? 0); $productSku = $productData['sku'] ?? '';
} }
if ($isAdmin) { if ($isAdmin) {
@ -242,6 +246,7 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
$value['customer_phone'], $value['customer_phone'],
$value['customer_address'], $value['customer_address'],
$productName, $productName,
$productSku,
number_format((int)$value['gross_amount'], 0, ',', ' '), number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '), number_format((int)$value['avance_amount'], 0, ',', ' '),
$status, $status,
@ -252,6 +257,7 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
$result['data'][] = [ $result['data'][] = [
$value['avance_id'], $value['avance_id'],
$productName, $productName,
$productSku,
number_format((int)$value['avance_amount'], 0, ',', ' '), number_format((int)$value['avance_amount'], 0, ',', ' '),
$status, $status,
$date_time, $date_time,

View File

@ -71,6 +71,7 @@ class HistoriqueController extends AdminController
$row['product_name'] ?? 'N/A', $row['product_name'] ?? 'N/A',
$row['sku'] ?? 'N/A', $row['sku'] ?? 'N/A',
$row['store_name'] ?? 'N/A', $row['store_name'] ?? 'N/A',
$row['user_name'] ?? 'N/A',
$this->getActionBadge($row['action']), $this->getActionBadge($row['action']),
$row['description'] ?? '' $row['description'] ?? ''
]; ];

View File

@ -298,7 +298,7 @@ class MecanicienController extends AdminController
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut'])); $date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin'])); $date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
$storeName = $repa['store_name'] ?? 'N/A'; $storeName = $repa['store_name'] ?? 'N/A';
$result['data'][$key] = [ $result['data'][$key] = [
$user_name, $user_name,
$image, $image,

View File

@ -663,16 +663,21 @@ class OrderController extends AdminController
$Notification->notifyGroupsByPermissionAllStores('notifRemise', $message, 'remise/'); $Notification->notifyGroupsByPermissionAllStores('notifRemise', $message, 'remise/');
} else { // Notifier la Caissière (notifCommande) et l'admin/direction (notifSortieCaisse)
// Commande sans remise $messageCommande = "📦 Nouvelle commande (avec remise) à traiter : {$bill_no}<br>" .
$Notification->notifyGroupsByPermission(
'notifSortieCaisse',
"📦 Nouvelle commande à valider : {$bill_no}<br>" .
"Client : {$data['customer_name']}<br>" . "Client : {$data['customer_name']}<br>" .
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar", "Montant demandé : " . number_format($discount, 0, ',', ' ') . " Ar<br>" .
(int)$users['store_id'], "En attente de validation remise";
"orders" $Notification->notifyGroupsByPermission('notifCommande', $messageCommande, (int)$users['store_id'], "orders");
); $Notification->notifyGroupsByPermission('notifSortieCaisse', $messageCommande, (int)$users['store_id'], "orders");
} else {
// Commande sans remise - notifier Caissière (notifCommande) et admin/direction (notifSortieCaisse)
$messageCommande = "📦 Nouvelle commande à traiter : {$bill_no}<br>" .
"Client : {$data['customer_name']}<br>" .
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar";
$Notification->notifyGroupsByPermission('notifCommande', $messageCommande, (int)$users['store_id'], "orders");
$Notification->notifyGroupsByPermission('notifSortieCaisse', $messageCommande, (int)$users['store_id'], "orders");
} }
if ($users["group_name"] != "COMMERCIALE") { if ($users["group_name"] != "COMMERCIALE") {
@ -1721,23 +1726,18 @@ public function update(int $id)
} }
public function printDiv(int $id) public function printDiv(int $id)
{ {
$Orders = new Orders(); $Orders = new Orders();
$OrderItems = new OrderItems();
$order = $Orders->getOrdersData($id); $order = $Orders->getOrdersData($id);
$items = $OrderItems->getOrdersItemData($id);
$docType = $order['document_type'] ?? 'facture';
// Plus d'1 produit = BL, 1 seul produit = Facture
// Rediriger vers la bonne méthode selon le type if (count($items) > 1) {
switch($docType) { return $this->print7($id); // Bon de livraison
case 'facture': } else {
return $this->print31($id); // Factures individuelles return $this->print5($id); // Facture
case 'bl':
return $this->print7($id); // Bon de livraison
case 'both':
return $this->print31($id); // Factures + Bon de livraison
default:
return $this->print31($id);
} }
} }
@ -2007,23 +2007,26 @@ public function print5(int $id)
$discount = (float) $order['discount']; $discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount']; $grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalAPayer = ($discount > 0) ? $discount : $grossAmount;
$inWords = strtoupper($this->numberToWords((int) round($totalTTC))); $remiseAmount = ($discount > 0) ? ($grossAmount - $discount) : 0;
$inWords = strtoupper($this->numberToWords((int) round($totalAPayer)));
// Construire les lignes du tableau // Construire les lignes du tableau
$tableRows = ''; $tableRows = '';
$totalPrixIndividuels = 0;
foreach ($items as $it) { foreach ($items as $it) {
$details = $this->getOrderItemDetails($it); $details = $this->getOrderItemDetails($it);
if (!$details) continue; if (!$details) continue;
$qty = isset($it['qty']) ? (int)$it['qty'] : 1; $qty = isset($it['qty']) ? (int)$it['qty'] : 1;
$prix = ($discount > 0) ? ($discount / $qty) : $details['prix']; $prixItem = $details['prix'] * $qty;
$totalPrixIndividuels += $prixItem;
$tableRows .= '<tr> $tableRows .= '<tr>
<td>'.esc($details['marque']).'</td> <td>'.esc($details['marque']).'</td>
<td>'.esc($details['numero_chassis']).'</td> <td>'.esc($details['numero_chassis']).'</td>
<td>'.esc($details['puissance']).'</td> <td>'.esc($details['puissance']).'</td>
<td>'.number_format($prix * $qty, 0, ' ', ' ').' MGA</td> <td>'.number_format($prixItem, 0, ' ', ' ').'</td>
</tr>'; </tr>';
} }
@ -2043,7 +2046,7 @@ public function print5(int $id)
$companyPhone = esc($company['phone']); $companyPhone = esc($company['phone']);
$companyPhone2 = esc($company['phone2']); $companyPhone2 = esc($company['phone2']);
$companyAddress = esc($company['address'] ?? ''); $companyAddress = esc($company['address'] ?? '');
$totalFormatted = number_format($totalTTC, 0, ' ', ' '); $totalFormatted = number_format($totalAPayer, 0, ' ', ' ');
$qrValue = "FACTURE N° {$billNo} | Client: {$customerName} | Montant: {$totalFormatted} MGA | Date: {$today} | Facebook: https://www.facebook.com/MOTORBIKESTORE2021/"; $qrValue = "FACTURE N° {$billNo} | Client: {$customerName} | Montant: {$totalFormatted} MGA | Date: {$today} | Facebook: https://www.facebook.com/MOTORBIKESTORE2021/";
$html = '<!DOCTYPE html> $html = '<!DOCTYPE html>
@ -2058,7 +2061,8 @@ public function print5(int $id)
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 11px; font-size: 13px;
line-height: 1.3;
color: #000; color: #000;
} }
@ -2084,32 +2088,33 @@ public function print5(int $id)
/* Header */ /* Header */
.f-title { .f-title {
font-size: 22px; font-size: 24px;
font-weight: bold; font-weight: bold;
text-decoration: underline; text-decoration: underline;
margin-bottom: 10px; margin-bottom: 12px;
} }
.f-company { .f-company {
font-size: 10px; font-size: 12px;
line-height: 1.5; line-height: 1.3;
margin-bottom: 8px; margin-bottom: 8px;
} }
.f-company strong { font-size: 12px; } .f-company strong { font-size: 14px; }
.f-doit { .f-doit {
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 14px;
margin-bottom: 5px; margin-bottom: 6px;
} }
.f-client { .f-client {
font-size: 10px; font-size: 12px;
line-height: 1.3;
margin-bottom: 10px; margin-bottom: 10px;
} }
.f-client .field { margin: 2px 0; } .f-client .field { margin: 3px 0; }
/* Table */ /* Table */
.f-table { .f-table {
@ -2121,9 +2126,9 @@ public function print5(int $id)
.f-table th, .f-table td { .f-table th, .f-table td {
border-left: 2px solid #000; border-left: 2px solid #000;
border-right: 2px solid #000; border-right: 2px solid #000;
padding: 6px 8px; padding: 8px 10px;
text-align: center; text-align: center;
font-size: 10px; font-size: 12px;
} }
.f-table th { .f-table th {
@ -2132,7 +2137,7 @@ public function print5(int $id)
border-bottom: 2px solid #000; border-bottom: 2px solid #000;
} }
.f-table td { height: 28px; } .f-table td { height: 30px; }
.f-table tbody tr:last-child td { .f-table tbody tr:last-child td {
border-bottom: 2px solid #000; border-bottom: 2px solid #000;
@ -2140,7 +2145,8 @@ public function print5(int $id)
/* Total en lettres */ /* Total en lettres */
.f-words { .f-words {
font-size: 10px; font-size: 12px;
line-height: 1.3;
margin-bottom: 12px; margin-bottom: 12px;
} }
@ -2160,7 +2166,7 @@ public function print5(int $id)
.f-sig-box .sig-label { .f-sig-box .sig-label {
font-weight: bold; font-weight: bold;
font-size: 11px; font-size: 13px;
} }
/* Date + N° + QR en bas */ /* Date + N° + QR en bas */
@ -2172,7 +2178,7 @@ public function print5(int $id)
} }
.f-bottom .f-date-info { .f-bottom .f-date-info {
font-size: 10px; font-size: 12px;
} }
.f-bottom canvas { .f-bottom canvas {
@ -2196,13 +2202,13 @@ public function print5(int $id)
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-bottom: 2px dashed #aaa; border-bottom: 2px dashed #aaa;
line-height: 1.6; line-height: 1.3;
} }
.cond-box:last-child { border-bottom: none; } .cond-box:last-child { border-bottom: none; }
.cond-title { .cond-title {
font-size: 14px; font-size: 16px;
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
text-decoration: underline; text-decoration: underline;
@ -2212,7 +2218,8 @@ public function print5(int $id)
.cond-box ul { .cond-box ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
font-size: 10px; font-size: 12px;
line-height: 1.3;
} }
.cond-box li { .cond-box li {
@ -2261,6 +2268,8 @@ public function print5(int $id)
$qrId = 'qr_recto_'.$i; $qrId = 'qr_recto_'.$i;
$html .= ' $html .= '
<div class="facture-box"> <div class="facture-box">
<div class="f-title" style="text-align:center;">FACTURE</div>
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:8px;"> <div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:8px;">
<div class="f-company" style="margin-bottom:0;"> <div class="f-company" style="margin-bottom:0;">
<strong>'.$companyName.'</strong><br> <strong>'.$companyName.'</strong><br>
@ -2269,15 +2278,13 @@ public function print5(int $id)
Contact : '.$companyPhone.' / '.$companyPhone2.'<br> Contact : '.$companyPhone.' / '.$companyPhone2.'<br>
'.$companyAddress.' '.$companyAddress.'
</div> </div>
<div style="text-align:right; font-size:10px;"> <div style="text-align:right; font-size:12px;">
<div>DATE : '.$today.'</div> <div>DATE : '.$today.'</div>
<div> : '.$billNo.'</div> <div> : '.$billNo.'</div>
<canvas id="'.$qrId.'" style="margin-top:5px;"></canvas> <canvas id="'.$qrId.'" style="margin-top:5px;"></canvas>
</div> </div>
</div> </div>
<div class="f-title" style="text-align:center;">FACTURE</div>
<div class="f-doit">DOIT</div> <div class="f-doit">DOIT</div>
<div class="f-client"> <div class="f-client">
@ -2302,7 +2309,14 @@ public function print5(int $id)
</table> </table>
<div class="f-words"> <div class="f-words">
<strong>Arrête à la somme de :</strong> '.$inWords.' Ariary <strong>Total :</strong> '.number_format($totalPrixIndividuels, 0, ' ', ' ').' Ariary<br>';
if ($remiseAmount > 0) {
$html .= '<strong>Remise :</strong> -'.number_format($remiseAmount, 0, ' ', ' ').' Ariary<br>
<strong>Total à payer :</strong> '.number_format($totalAPayer, 0, ' ', ' ').' Ariary<br>';
}
$html .= '<strong>Arrête à la somme de :</strong> '.$inWords.'
</div> </div>
<div class="f-signatures"> <div class="f-signatures">
@ -2532,22 +2546,26 @@ public function print7(int $id)
$discount = (float) $order['discount']; $discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount']; $grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalAPayer = ($discount > 0) ? $discount : $grossAmount;
$remiseAmount = ($discount > 0) ? ($grossAmount - $discount) : 0;
$inWords = strtoupper($this->numberToWords((int) round($totalAPayer)));
// Construire les lignes du tableau // Construire les lignes du tableau - prix individuel de chaque moto
$tableRows = ''; $tableRows = '';
$totalPrixIndividuels = 0;
foreach ($items as $item) { foreach ($items as $item) {
$details = $this->getOrderItemDetails($item); $details = $this->getOrderItemDetails($item);
if (!$details) continue; if (!$details) continue;
$qty = isset($item['qty']) ? (int)$item['qty'] : 1; $qty = isset($item['qty']) ? (int)$item['qty'] : 1;
$prix = ($discount > 0) ? ($discount / $qty) : $details['prix']; $prixItem = $details['prix'] * $qty;
$totalPrixIndividuels += $prixItem;
$tableRows .= '<tr> $tableRows .= '<tr>
<td>'.esc($details['marque']).'</td> <td>'.esc($details['marque']).'</td>
<td>'.esc($details['numero_moteur']).'</td> <td>'.esc($details['numero_moteur']).'</td>
<td>'.esc($details['puissance']).'</td> <td>'.esc($details['puissance']).'</td>
<td>'.number_format($prix * $qty, 0, '', ' ').'</td> <td>'.number_format($prixItem, 0, '', ' ').'</td>
</tr>'; </tr>';
} }
@ -2573,8 +2591,8 @@ public function print7(int $id)
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 13px; font-size: 14px;
line-height: 1.5; line-height: 1.3;
background: #fff; background: #fff;
} }
@ -2596,12 +2614,12 @@ public function print7(int $id)
} }
.bl-company { .bl-company {
font-size: 12px; font-size: 13px;
line-height: 1.6; line-height: 1.3;
} }
.bl-company strong { .bl-company strong {
font-size: 15px; font-size: 16px;
} }
.bl-title-block { .bl-title-block {
@ -2629,11 +2647,12 @@ public function print7(int $id)
/* Client info */ /* Client info */
.bl-client { .bl-client {
margin-bottom: 12px; margin-bottom: 12px;
font-size: 13px; font-size: 14px;
line-height: 1.3;
} }
.bl-client .field { .bl-client .field {
margin: 3px 0; margin: 4px 0;
} }
.bl-client .field strong { .bl-client .field strong {
@ -2643,7 +2662,7 @@ public function print7(int $id)
.bl-doit { .bl-doit {
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 15px;
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -2651,29 +2670,24 @@ public function print7(int $id)
.bl-table { .bl-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
flex: 1;
} }
.bl-table th, .bl-table td { .bl-table th, .bl-table td {
border-left: 2px solid #000; border-left: 2px solid #000;
border-right: 2px solid #000; border-right: 2px solid #000;
padding: 10px 15px; padding: 5px 10px;
text-align: center; text-align: center;
font-size: 13px; font-size: 14px;
} }
.bl-table th { .bl-table th {
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 14px;
background: #fff; background: #fff;
border-top: 2px solid #000; border-top: 2px solid #000;
border-bottom: 2px solid #000; border-bottom: 2px solid #000;
} }
.bl-table td {
height: 35px;
}
.bl-table tbody tr:last-child td { .bl-table tbody tr:last-child td {
border-bottom: 2px solid #000; border-bottom: 2px solid #000;
} }
@ -2684,7 +2698,8 @@ public function print7(int $id)
} }
.bl-total { .bl-total {
font-size: 14px; font-size: 15px;
line-height: 1.3;
margin-bottom: 15px; margin-bottom: 15px;
} }
@ -2758,7 +2773,14 @@ public function print7(int $id)
<!-- Footer --> <!-- Footer -->
<div class="bl-footer"> <div class="bl-footer">
<div class="bl-total"> <div class="bl-total">
<strong>Arrête à la somme de :</strong> '.number_format($totalTTC, 0, '', ' ').' Ariary <strong>Total :</strong> '.number_format($totalPrixIndividuels, 0, '', ' ').' Ariary<br>';
if ($remiseAmount > 0) {
$html .= '<strong>Remise :</strong> -'.number_format($remiseAmount, 0, '', ' ').' Ariary<br>
<strong>Total à payer :</strong> '.number_format($totalAPayer, 0, '', ' ').' Ariary<br>';
}
$html .= '<strong>Arrête à la somme de :</strong> '.$inWords.'
</div> </div>
<div class="bl-signatures"> <div class="bl-signatures">
<div class="bl-sig-box"> <div class="bl-sig-box">
@ -2775,7 +2797,7 @@ public function print7(int $id)
<script> <script>
new QRious({ new QRious({
element: document.getElementById("qrcode"), element: document.getElementById("qrcode"),
value: "BON DE LIVRAISON\\nN° '.esc($order['bill_no']).'\\nClient: '.esc($order['customer_name']).'\\nMontant: '.number_format($totalTTC, 0, '', ' ').' Ar\\nDate: '.$today.'\\nFacebook: https://www.facebook.com/MOTORBIKESTORE2021/", value: "BON DE LIVRAISON\\nN° '.esc($order['bill_no']).'\\nClient: '.esc($order['customer_name']).'\\nMontant: '.number_format($totalAPayer, 0, '', ' ').' Ar\\nDate: '.$today.'\\nFacebook: https://www.facebook.com/MOTORBIKESTORE2021/",
size: 90, size: 90,
level: "H" level: "H"
}); });

View File

@ -101,15 +101,20 @@ class ProductCOntroller extends AdminController
$store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu"; $store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu";
} }
// Disponibilité basée sur qty ET availability // Disponibilité basée sur qty, availability et product_sold
$productSold = (int)($value['product_sold'] ?? 0);
$isInStock = ((int)$value['qty'] > 0); $isInStock = ((int)$value['qty'] > 0);
$isAvailable = ((int)$value['availability'] === 1); $isAvailable = ((int)$value['availability'] === 1);
$isProductAvailable = $isInStock && $isAvailable; if ($productSold === 2) {
$availability = '<span class="label label-warning">En attente de livraison</span>';
$availability = $isProductAvailable ? } elseif ($productSold === 1) {
'<span class="label label-success">En stock</span>' : $availability = '<span class="label label-default">Livré</span>';
'<span class="label label-danger">Rupture</span>'; } elseif ($isInStock && $isAvailable) {
$availability = '<span class="label label-success">En stock</span>';
} else {
$availability = '<span class="label label-danger">Rupture</span>';
}
// Construction des boutons // Construction des boutons
$buttons = ''; $buttons = '';
@ -224,7 +229,7 @@ class ProductCOntroller extends AdminController
'date_arivage' => $this->request->getPost('datea'), 'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'), 'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'), 'cler' => $this->request->getPost('cler'),
'categorie_id' => json_encode($this->request->getPost('categorie[]')), 'categorie_id' => $this->request->getPost('categorie') ? implode(',', $this->request->getPost('categorie')) : '',
'etats' => $this->request->getPost('etats'), 'etats' => $this->request->getPost('etats'),
'infoManquekit' => $this->request->getPost('infoManquekit'), 'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'), 'info' => $this->request->getPost('info'),
@ -297,6 +302,11 @@ class ProductCOntroller extends AdminController
public function update(int $id) public function update(int $id)
{ {
log_message('error', '=== PRODUCT UPDATE CALLED === ID: ' . $id . ' METHOD: ' . $_SERVER['REQUEST_METHOD']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
log_message('error', 'POST DATA KEYS: ' . implode(', ', array_keys($_POST)));
}
$Products = new Products(); $Products = new Products();
$Stores = new Stores(); $Stores = new Stores();
$Category = new Category(); $Category = new Category();
@ -315,33 +325,33 @@ class ProductCOntroller extends AdminController
$product_data = $Products->getProductData($id); $product_data = $Products->getProductData($id);
$prix_minimal_data = $FourchettePrix->where('product_id', $id)->first(); $prix_minimal_data = $FourchettePrix->where('product_id', $id)->first();
$prix_minimal = $prix_minimal_data['prix_minimal'] ?? ''; $prix_minimal = $prix_minimal_data['prix_minimal'] ?? '';
if (strtolower($this->request->getMethod()) === 'post' && $validation->withRequest($this->request)->run()) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && $validation->withRequest($this->request)->run()) {
$availabilityValue = (int)$this->request->getPost('availability'); $availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0; $availability = ($availabilityValue === 1) ? 1 : 0;
$data = [ $data = [
'name' => $this->request->getPost('nom_de_produit'), 'name' => $this->request->getPost('nom_de_produit') ?? '',
'sku' => $this->request->getPost('numero_de_serie'), 'sku' => $this->request->getPost('numero_de_serie') ?? '',
'price' => $this->request->getPost('price'), 'price' => $this->request->getPost('price') ?? '0',
'qty' => 1, 'qty' => 1,
'description' => $this->request->getPost('description'), 'description' => $this->request->getPost('description') ?? '',
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'), 'numero_de_moteur' => $this->request->getPost('numero_de_moteur') ?? '',
'marque' => $this->request->getPost('marque'), 'marque' => $this->request->getPost('marque') ?? '0',
'chasis' => $this->request->getPost('chasis'), 'chasis' => $this->request->getPost('chasis') ?? '',
'store_id' => (int)$this->request->getPost('store'), 'store_id' => (int)$this->request->getPost('store'),
'availability'=> $availability, 'availability'=> $availability,
'prix_vente' => $this->request->getPost('price_vente'), 'prix_vente' => $this->request->getPost('price_vente') ?? '0',
'date_arivage'=> $this->request->getPost('datea'), 'date_arivage'=> $this->request->getPost('datea') ?: date('Y-m-d'),
'puissance' => $this->request->getPost('puissance'), 'puissance' => $this->request->getPost('puissance') ?? '',
'cler' => $this->request->getPost('cler'), 'cler' => $this->request->getPost('cler') ?? '',
'categorie_id'=> json_encode($this->request->getPost('categorie[]')), 'categorie_id'=> $this->request->getPost('categorie') ? implode(',', $this->request->getPost('categorie')) : '',
'etats' => $this->request->getPost('etats'), 'etats' => $this->request->getPost('etats') ?? '',
'infoManquekit'=> $this->request->getPost('infoManquekit'), 'infoManquekit'=> $this->request->getPost('infoManquekit') ?? '',
'info' => $this->request->getPost('info'), 'info' => $this->request->getPost('info') ?? '',
'infoManque' => $this->request->getPost('infoManque'), 'infoManque' => $this->request->getPost('infoManque') ?? '',
'type' => $this->request->getPost('type'), 'type' => $this->request->getPost('type') ?? '',
]; ];
// ---- GESTION PRIX MINIMAL ---- // ---- GESTION PRIX MINIMAL ----
@ -367,17 +377,39 @@ class ProductCOntroller extends AdminController
} }
// Upload image si fournie // Upload image si fournie
if ($this->request->getFile('product_image')->isValid()) { $productImage = $this->request->getFile('product_image');
if ($productImage && $productImage->isValid() && !$productImage->hasMoved()) {
$uploadImage = $this->uploadImage(); $uploadImage = $this->uploadImage();
$Products->update($id, ['image' => $uploadImage]); $Products->update($id, ['image' => $uploadImage]);
} }
// Mise à jour du produit // Mise à jour du produit
if ($Products->updateProduct($data, $id)) { try {
session()->setFlashdata('success', 'Produit mis à jour avec succès.'); $db = \Config\Database::connect();
return redirect()->to('/products'); $session = session();
} else { $currentUser = $session->get('user');
session()->setFlashdata('errors', 'Une erreur est survenue lors de la mise à jour.'); $userName = ($currentUser['firstname'] ?? '') . ' ' . ($currentUser['lastname'] ?? '');
$userId = $currentUser['id'] ?? 0;
// Passer l'utilisateur au trigger via variables MySQL
$db->query("SET @current_user_id = ?", [$userId]);
$db->query("SET @current_user_name = ?", [trim($userName)]);
$builder = $db->table('products');
$result = $builder->where('id', $id)->update($data);
if ($result) {
session()->setFlashdata('success', 'Produit mis à jour avec succès.');
return redirect()->to('/products');
} else {
log_message('error', 'Update produit failed - DB error: ' . $db->error()['message']);
session()->setFlashdata('errors', 'Une erreur est survenue lors de la mise à jour.');
return redirect()->to('/products/update/' . $id);
}
} catch (\Exception $e) {
log_message('error', 'Erreur update produit: ' . $e->getMessage());
log_message('error', 'Data: ' . json_encode($data));
session()->setFlashdata('errors', 'Erreur: ' . $e->getMessage());
return redirect()->to('/products/update/' . $id); return redirect()->to('/products/update/' . $id);
} }

View File

@ -138,24 +138,30 @@ class RecouvrementController extends AdminController
"data" => [] "data" => []
]; ];
foreach ($data as $key => $value) { $hasActions = in_array('updateRecouvrement', $this->permission) || in_array('deleteRecouvrement', $this->permission);
$buttons = '';
if (in_array('updateRecouvrement', $this->permission)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#updateModal"><i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteRecouvrement', $this->permission)) { foreach ($data as $key => $value) {
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>'; $row = [
}
$result['data'][$key] = [
$value['recouvrement_id'], $value['recouvrement_id'],
number_format($value['recouvrement_montant'], 0, '.', ' '), number_format($value['recouvrement_montant'], 0, '.', ' '),
$value['recouvrement_date'], $value['recouvrement_date'],
$value['recouvrement_personnel'], $value['recouvrement_personnel'],
$value['send_money'], $value['send_money'],
$value['get_money'], $value['get_money'],
$buttons
]; ];
if ($hasActions) {
$buttons = '';
if (in_array('updateRecouvrement', $this->permission)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#updateModal"><i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteRecouvrement', $this->permission)) {
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
}
$row[] = $buttons;
}
$result['data'][$key] = $row;
} }
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }
@ -170,24 +176,30 @@ class RecouvrementController extends AdminController
"data" => [] "data" => []
]; ];
foreach ($data as $key => $value) { $hasActions = in_array('updateRecouvrement', $this->permission) || in_array('deleteRecouvrement', $this->permission);
$buttons = '';
if (in_array('updateRecouvrement', $this->permission)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#updateModal"><i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteRecouvrement', $this->permission)) { foreach ($data as $key => $value) {
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>'; $row = [
}
$result['data'][$key] = [
$value['recouvrement_id'], $value['recouvrement_id'],
number_format($value['recouvrement_montant'], 0, '.', ' '), number_format($value['recouvrement_montant'], 0, '.', ' '),
$value['recouvrement_date'], $value['recouvrement_date'],
$value['recouvrement_personnel'], $value['recouvrement_personnel'],
$value['send_money'], $value['send_money'],
$value['get_money'], $value['get_money'],
$buttons
]; ];
if ($hasActions) {
$buttons = '';
if (in_array('updateRecouvrement', $this->permission)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#updateModal"><i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteRecouvrement', $this->permission)) {
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['recouvrement_id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
}
$row[] = $buttons;
}
$result['data'][$key] = $row;
} }
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }

View File

@ -201,7 +201,7 @@ class ReportController extends AdminController
{ {
$Products = new Products(); $Products = new Products();
$produitStock = $Products->getProductData2($id); $produitStock = $Products->getStockByBrand($id);
$result = ['data' => []]; $result = ['data' => []];
foreach ($produitStock as $key => $value) { foreach ($produitStock as $key => $value) {
@ -251,10 +251,24 @@ class ReportController extends AdminController
$data['page_title'] = $this->pageTitle; $data['page_title'] = $this->pageTitle;
$Stores = new Stores(); $Stores = new Stores();
// echo '<pre>';
// die(var_dump($orderTest));
$data['stores'] = $Stores->getActiveStore(); $data['stores'] = $Stores->getActiveStore();
// Récupérer les commerciaux et mécaniciens pour les filtres
$db = \Config\Database::connect();
$data['commerciaux'] = $db->table('users u')
->select('u.id, u.firstname, u.lastname')
->join('user_group ug', 'u.id = ug.user_id')
->join('groups g', 'ug.group_id = g.id')
->where('g.group_name', 'COMMERCIALE')
->get()->getResultArray();
$data['mecaniciens'] = $db->table('users u')
->select('u.id, u.firstname, u.lastname')
->join('user_group ug', 'u.id = ug.user_id')
->join('groups g', 'ug.group_id = g.id')
->where('g.group_name', 'MECANICIEN')
->get()->getResultArray();
return $this->render_template('reports/performance', $data); return $this->render_template('reports/performance', $data);
} }
@ -265,32 +279,27 @@ class ReportController extends AdminController
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRE // Récupérer les paramètres de filtre
$startDate = $this->request->getGet('startDate'); $startDate = $this->request->getGet('startDate');
$endDate = $this->request->getGet('endDate'); $endDate = $this->request->getGet('endDate');
$pvente = $this->request->getGet('pvente'); $pvente = $this->request->getGet('pvente');
$commercialId = $this->request->getGet('commercial');
// ✅ CORRECTION : Bonne concaténation des chaînes
log_message('debug', 'Filtres Commercial reçus - startDate: ' . $startDate . ', endDate: ' . $endDate . ', pvente: ' . $pvente);
// Pour Direction et Conseil : afficher TOUTES les performances AVEC FILTRES // Pour Direction et Conseil : afficher TOUTES les performances AVEC FILTRES
if ($users['group_name'] === "DAF" || $users['group_name'] === "Direction" || $users['group_name'] === "SuperAdmin") { if ($users['group_name'] === "DAF" || $users['group_name'] === "Direction" || $users['group_name'] === "SuperAdmin") {
// ✅ PASSER LES FILTRES AU MODÈLE - UNIQUEMENT POUR L'ADMIN $orderPaid = $Orders->getPerformanceByOrders($startDate, $endDate, $pvente, $commercialId);
$orderPaid = $Orders->getPerformanceByOrders($startDate, $endDate, $pvente);
foreach ($orderPaid as $key => $value) { foreach ($orderPaid as $key => $value) {
// Déterminer le prix de vente réel $prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0) ? $value['discount']
? $value['discount']
: $value['prix_vente']; : $value['prix_vente'];
// Calculer le bénéfice
$benefice = $prix_vente_reel - $value['price']; $benefice = $prix_vente_reel - $value['price'];
$result['data'][$key] = [ $result['data'][$key] = [
$value['firstname'] . ' ' . $value['lastname'], $value['firstname'] . ' ' . $value['lastname'],
$value['email'], $value['email'],
($value['sku'] == "" ? $value['motoname'] : $value['sku']), ($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('Y-m-d'), (new \DateTime($value['datevente']))->format('Y-m-d'),
number_format($value['price'], 0, '.', ' '), number_format($value['price'], 0, '.', ' '),
number_format($prix_vente_reel, 0, '.', ' '), number_format($prix_vente_reel, 0, '.', ' '),
$this->returnName($value['store_id']), $this->returnName($value['store_id']),

View File

@ -274,15 +274,15 @@ class Orders extends Model
$orderItemModel->where('order_id', $id)->delete(); $orderItemModel->where('order_id', $id)->delete();
// Insert new items // Insert new items
$count_product = count($data['product']); $count_product = is_array($data['product']) ? count($data['product']) : 0;
for ($x = 0; $x < $count_product; $x++) { for ($x = 0; $x < $count_product; $x++) {
$items = [ $items = [
'order_id' => $id, 'order_id' => $id,
'product_id' => $data['product'][$x], 'product_id' => is_array($data['product']) ? $data['product'][$x] : $data['product'],
'rate' => $data['rate_value'][$x], 'rate' => is_array($data['rate_value']) ? ($data['rate_value'][$x] ?? 0) : ($data['rate_value'] ?? 0),
'qty' => isset($data['qty'][$x]) ? (int)$data['qty'][$x] : 1, 'qty' => is_array($data['qty']) ? (int)($data['qty'][$x] ?? 1) : 1,
'puissance' => $data['puissance'][$x], 'puissance' => is_array($data['puissance']) ? ($data['puissance'][$x] ?? '') : ($data['puissance'] ?? ''),
'amount' => $data['amount_value'][$x], 'amount' => is_array($data['amount_value']) ? ($data['amount_value'][$x] ?? 0) : ($data['amount_value'] ?? 0),
]; ];
$orderItemModel->insert($items); $orderItemModel->insert($items);
@ -605,7 +605,7 @@ class Orders extends Model
return $order; return $order;
} }
public function getPerformanceByOrders($startDate = null, $endDate = null, $pvente = null) public function getPerformanceByOrders($startDate = null, $endDate = null, $pvente = null, $commercialId = null)
{ {
$builder = $this->db->table('orders') $builder = $this->db->table('orders')
->select('orders.id as orderid, orders.net_amount, orders.date_time as datevente, orders.discount, products.price, products.sku, products.prix_vente, products.name as motoname, stores.id as store_id, users.firstname, users.lastname, users.email') ->select('orders.id as orderid, orders.net_amount, orders.date_time as datevente, orders.discount, products.price, products.sku, products.prix_vente, products.name as motoname, stores.id as store_id, users.firstname, users.lastname, users.email')
@ -614,8 +614,7 @@ class Orders extends Model
->join('products', 'products.id = orders_item.product_id') ->join('products', 'products.id = orders_item.product_id')
->join('users', 'users.id = orders.user_id') ->join('users', 'users.id = orders.user_id')
->whereIn('orders.paid_status', [1, 3]); ->whereIn('orders.paid_status', [1, 3]);
// ✅ AJOUT : FILTRES PAR DATE
if (!empty($startDate) && !empty($endDate)) { if (!empty($startDate) && !empty($endDate)) {
$builder->where('DATE(orders.date_time) >=', $startDate); $builder->where('DATE(orders.date_time) >=', $startDate);
$builder->where('DATE(orders.date_time) <=', $endDate); $builder->where('DATE(orders.date_time) <=', $endDate);
@ -624,14 +623,17 @@ class Orders extends Model
} elseif (!empty($endDate)) { } elseif (!empty($endDate)) {
$builder->where('DATE(orders.date_time) <=', $endDate); $builder->where('DATE(orders.date_time) <=', $endDate);
} }
// ✅ AJOUT : FILTRE PAR POINT DE VENTE
if (!empty($pvente) && $pvente !== 'TOUS') { if (!empty($pvente) && $pvente !== 'TOUS') {
$builder->where('stores.name', $pvente); $builder->where('stores.name', $pvente);
} }
if (!empty($commercialId) && $commercialId !== 'TOUS') {
$builder->where('orders.user_id', $commercialId);
}
$builder->orderBy('orders.date_time', 'DESC'); $builder->orderBy('orders.date_time', 'DESC');
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();
} }

View File

@ -62,6 +62,23 @@ class Products extends Model
->findAll(); ->findAll();
} }
public function getStockByBrand(int $id = 0)
{
$builder = $this->db->table('products')
->select('brands.name as brand_name, products.store_id, COUNT(*) as total_product')
->join('brands', 'brands.id = products.marque')
->where('products.is_piece', 0)
->where('products.product_sold', 0);
if ($id > 0) {
$builder->where('products.store_id', $id);
}
return $builder->groupBy('brands.name, products.store_id')
->orderBy('total_product', 'DESC')
->get()->getResultArray();
}
public function getProductData3(int $id) public function getProductData3(int $id)
{ {
if ($id == 0) { if ($id == 0) {

View File

@ -42,12 +42,16 @@ $isCaissier = isset($users['group_name']) && in_array($users['group_name'], ['Ca
<div class="col-md-3"> <div class="col-md-3">
<button id="avance_no_order" class="btn btn-info w-100 rounded-pill shadow-sm py-2"> <button id="avance_no_order" class="btn btn-info w-100 rounded-pill shadow-sm py-2">
<i class="fa fa-hourglass-half me-2"></i> Avances Incomplètes <i class="fa fa-hourglass-half me-2"></i>
<span class="badge badge-light" id="incomplete-count">0</span>
Avances Incomplètes
</button> </button>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<button id="avance_order" class="btn btn-success w-100 rounded-pill shadow-sm py-2"> <button id="avance_order" class="btn btn-success w-100 rounded-pill shadow-sm py-2">
<i class="fa fa-check-circle me-2"></i> Avances Complètes <i class="fa fa-check-circle me-2"></i>
<span class="badge badge-light" id="complete-count">0</span>
Avances Complètes
</button> </button>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
@ -82,6 +86,7 @@ $isCaissier = isset($users['group_name']) && in_array($users['group_name'], ['Ca
<th>Téléphone</th> <th>Téléphone</th>
<th>Adresse</th> <th>Adresse</th>
<th>Produit</th> <th>Produit</th>
<th> Série</th>
<th>Prix</th> <th>Prix</th>
<th>Avance</th> <th>Avance</th>
<th>Reste à payer</th> <th>Reste à payer</th>
@ -91,11 +96,12 @@ $isCaissier = isset($users['group_name']) && in_array($users['group_name'], ['Ca
<?php endif;?> <?php endif;?>
</tr> </tr>
<?php endif;?> <?php endif;?>
<?php if ($isCommerciale || $isCaissier): ?> <?php if ($isCommerciale || $isCaissier): ?>
<tr> <tr>
<th>#</th> <th>#</th>
<th>Produit</th> <th>Produit</th>
<th> Série</th>
<th>Avance</th> <th>Avance</th>
<th>Reste à payer</th> <th>Reste à payer</th>
<th>Date</th> <th>Date</th>
@ -500,6 +506,38 @@ $(document).ready(function() {
} }
} }
// Compteurs avances
function loadAvanceCounts() {
$.ajax({
url: base_url + 'avances/fetchAvanceData',
type: 'GET',
dataType: 'json',
success: function(response) {
var count = response.data ? response.data.length : 0;
$('#incomplete-count').text(count);
if (count > 0) {
$('#incomplete-count').removeClass('badge-light').addClass('badge-danger');
} else {
$('#incomplete-count').removeClass('badge-danger').addClass('badge-light');
}
}
});
$.ajax({
url: base_url + 'avances/fetchAvanceBecameOrder',
type: 'GET',
dataType: 'json',
success: function(response) {
var count = response.data ? response.data.length : 0;
$('#complete-count').text(count);
if (count > 0) {
$('#complete-count').removeClass('badge-light').addClass('badge-success');
} else {
$('#complete-count').removeClass('badge-success').addClass('badge-light');
}
}
});
}
// Variables de rôles // Variables de rôles
var isCaissier = <?php echo json_encode($isCaissier ?? false); ?>; var isCaissier = <?php echo json_encode($isCaissier ?? false); ?>;
var isCommerciale = <?php echo json_encode($isCommerciale ?? false); ?>; var isCommerciale = <?php echo json_encode($isCommerciale ?? false); ?>;
@ -516,19 +554,20 @@ $(document).ready(function() {
{ title: "Téléphone" }, { title: "Téléphone" },
{ title: "Adresse" }, { title: "Adresse" },
{ title: "Produit" }, { title: "Produit" },
{ { title: "N° Série" },
{
title: "Prix", title: "Prix",
render: function(data, type, row) { render: function(data, type, row) {
return type === 'display' ? formatNumber(data) : data; return type === 'display' ? formatNumber(data) : data;
} }
}, },
{ {
title: "Avance", title: "Avance",
render: function(data, type, row) { render: function(data, type, row) {
return type === 'display' ? formatNumber(data) : data; return type === 'display' ? formatNumber(data) : data;
} }
}, },
{ {
title: "Reste à payer", title: "Reste à payer",
render: function(data, type, row) { render: function(data, type, row) {
return type === 'display' ? formatNumber(data) : data; return type === 'display' ? formatNumber(data) : data;
@ -542,6 +581,10 @@ $(document).ready(function() {
var manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns); var manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
// Charger les compteurs
loadAvanceCounts();
setInterval(loadAvanceCounts, 30000);
$('#avance_no_order').on('click', function() { $('#avance_no_order').on('click', function() {
$('#table-title').text('Avances Incomplètes'); $('#table-title').text('Avances Incomplètes');
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns); manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
@ -552,6 +595,7 @@ $(document).ready(function() {
{ title: "Téléphone" }, { title: "Téléphone" },
{ title: "Adresse" }, { title: "Adresse" },
{ title: "Produit" }, { title: "Produit" },
{ title: "N° Série" },
{ {
title: "Prix", title: "Prix",
render: function(data, type, row) { render: function(data, type, row) {
@ -587,7 +631,8 @@ $(document).ready(function() {
var userColumns = [ var userColumns = [
{ title: "#" }, { title: "#" },
{ title: "Produit" }, { title: "Produit" },
{ { title: "N° Série" },
{
title: "Avance", title: "Avance",
render: function(data, type, row) { render: function(data, type, row) {
return type === 'display' ? formatNumber(data) : data; return type === 'display' ? formatNumber(data) : data;
@ -617,12 +662,13 @@ $(document).ready(function() {
$('#avanceTable').DataTable().destroy(); $('#avanceTable').DataTable().destroy();
} }
// Reconstruire les headers (format caissière/commercial) // Reconstruire les headers (format caissière/commercial)
rebuildTableHeaders([ rebuildTableHeaders([
'#', '#',
'Produit', 'Produit',
'Avance', 'N° Série',
'Reste à payer', 'Avance',
'Reste à payer',
'Date', 'Date',
'Action' 'Action'
]); ]);
@ -653,6 +699,7 @@ $('#avance_order').on('click', function() {
rebuildTableHeaders([ rebuildTableHeaders([
'#', '#',
'Produit', 'Produit',
'N° Série',
'Avance', 'Avance',
'Statut', 'Statut',
'Date', 'Date',
@ -662,6 +709,7 @@ $('#avance_order').on('click', function() {
var completedUserColumns = [ var completedUserColumns = [
{ title: "#" }, { title: "#" },
{ title: "Produit" }, { title: "Produit" },
{ title: "N° Série" },
{ title: "Avance" }, { title: "Avance" },
{ title: "Statut" }, { title: "Statut" },
{ title: "Date" } { title: "Date" }
@ -693,14 +741,15 @@ $('#avance_expired').on('click', function() {
} }
rebuildTableHeaders([ rebuildTableHeaders([
'#', '#',
'Produit', 'Produit',
'Avance', 'N° Série',
'Reste à payer', 'Avance',
'Reste à payer',
'Date', 'Date',
'Action' 'Action'
]); ]);
manageTable = $('#avanceTable').DataTable({ manageTable = $('#avanceTable').DataTable({
ajax: { ajax: {
url: base_url + 'avances/fetchExpiredAvance', url: base_url + 'avances/fetchExpiredAvance',
@ -714,8 +763,10 @@ $('#avance_expired').on('click', function() {
console.log('✅ DataTable Expirées initialisée'); console.log('✅ DataTable Expirées initialisée');
}); });
// Charger le compteur d'avances en attente // Charger les compteurs d'avances
loadPendingCount(); loadPendingCount();
loadAvanceCounts();
setInterval(loadAvanceCounts, 30000);
setInterval(loadPendingCount, 30000); setInterval(loadPendingCount, 30000);
console.log('✅ Module caissière initialisé'); console.log('✅ Module caissière initialisé');
@ -1582,18 +1633,17 @@ window.validateAvanceFunc = function(avance_id) {
}; };
// ===================================================== // =====================================================
// 🔥 CHARGER COMPTEUR AVANCES EN ATTENTE // CHARGER COMPTEURS AVANCES
// ===================================================== // =====================================================
function loadPendingCount() { function loadPendingCount() {
$.ajax({ $.ajax({
url: base_url + 'avances/fetchPendingValidation', url: base_url + 'avances/fetchPendingValidation',
type: 'POST', // ✅ CORRECTION type: 'POST',
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
var count = response.data ? response.data.length : 0; var count = response.data ? response.data.length : 0;
$('#pending-count').text(count); $('#pending-count').text(count);
if (count > 0) { if (count > 0) {
$('#pending-count').removeClass('badge-light').addClass('badge-danger'); $('#pending-count').removeClass('badge-light').addClass('badge-danger');
$('#avance_pending').addClass('btn-pulse'); $('#avance_pending').addClass('btn-pulse');
@ -1601,13 +1651,12 @@ function loadPendingCount() {
$('#pending-count').removeClass('badge-danger').addClass('badge-light'); $('#pending-count').removeClass('badge-danger').addClass('badge-light');
$('#avance_pending').removeClass('btn-pulse'); $('#avance_pending').removeClass('btn-pulse');
} }
},
error: function() {
console.error('Erreur chargement compteur avances en attente');
} }
}); });
} }
// loadAvanceCounts defini plus haut
// ===================================================== // =====================================================
// 🔥 TRAITER AVANCES EXPIRÉES (ADMIN) // 🔥 TRAITER AVANCES EXPIRÉES (ADMIN)
// ===================================================== // =====================================================

View File

@ -27,6 +27,7 @@
<th>Prix</th> <th>Prix</th>
<th>Puissances</th> <th>Puissances</th>
<th> Moteur</th> <th> Moteur</th>
<th>Disponibilité</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@ -72,10 +73,15 @@ $.extend(true, $.fn.dataTable.defaults, {
'ajax': `<?= base_url('ventes/fetchProductVente') ?>/${id}`, 'ajax': `<?= base_url('ventes/fetchProductVente') ?>/${id}`,
'order': [], 'order': [],
'columnDefs': [{ 'columnDefs': [{
targets: 6, targets: 7,
className: 'text-right' className: 'text-right'
} // Column index 3 corresponds to "Prix" }
] ],
'createdRow': function(row, data, dataIndex) {
if (data[6] && data[6].indexOf('En attente de livraison') !== -1) {
$(row).css({'background-color': '#fff3cd', 'opacity': '0.85'});
}
}
}); });
}) })
</script> </script>

View File

@ -75,6 +75,7 @@
<th>Produit</th> <th>Produit</th>
<th>SKU</th> <th>SKU</th>
<th>Magasin</th> <th>Magasin</th>
<th>Utilisateur</th>
<th>Action</th> <th>Action</th>
<th>Description</th> <th>Description</th>
</tr> </tr>
@ -238,13 +239,14 @@ $(document).ready(function() {
"complete": function() { $("#loading").hide(); } "complete": function() { $("#loading").hide(); }
}, },
"columns": [ "columns": [
{ "data": 0, "width": "15%" }, { "data": 0, "width": "13%" },
{ "data": 1, "width": "20%" }, { "data": 1, "width": "15%" },
{ "data": 2, "width": "10%" }, { "data": 2, "width": "10%" },
{ "data": 3, "width": "12%" }, { "data": 3, "width": "12%" },
{ { "data": 4, "width": "10%" },
"data": 4, {
"width": "10%", "data": 5,
"width": "8%",
"render": function(data) { "render": function(data) {
let badgeClass = "badge bg-gray"; let badgeClass = "badge bg-gray";
if (data === "CREATE") badgeClass = "badge bg-green"; if (data === "CREATE") badgeClass = "badge bg-green";
@ -254,7 +256,7 @@ $(document).ready(function() {
return '<span class="'+badgeClass+'">'+data+'</span>'; return '<span class="'+badgeClass+'">'+data+'</span>';
} }
}, },
{ "data": 5, "width": "33%" } { "data": 6, "width": "32%" }
], ],
"order": [[0, "desc"]], "order": [[0, "desc"]],
"pageLength": 25, "pageLength": 25,

View File

@ -43,7 +43,7 @@
<h3 class="box-title">Mise à jours Moto</h3> <h3 class="box-title">Mise à jours Moto</h3>
</div> </div>
<form role="form" action="<?php base_url('users/update') ?>" method="post" enctype="multipart/form-data"> <form role="form" action="<?php echo base_url('products/update/' . $product_data['id']); ?>" method="post" enctype="multipart/form-data">
<div class="box-body"> <div class="box-body">
<!-- Image actuelle --> <!-- Image actuelle -->
<div class="form-group"> <div class="form-group">
@ -111,7 +111,7 @@
<!-- Date d'arrivage --> <!-- Date d'arrivage -->
<div class="form-group"> <div class="form-group">
<label for="datea">Date d'arrivage</label> <label for="datea">Date d'arrivage</label>
<input type="date" class="form-control" id="datea" name="datea" autocomplete="off" value="<?php echo $product_data['date_arivage']; ?>" /> <input type="date" class="form-control" id="datea" name="datea" autocomplete="off" value="<?php echo !empty($product_data['date_arivage']) ? date('Y-m-d', strtotime($product_data['date_arivage'])) : ''; ?>" />
</div> </div>
<!-- Puissance --> <!-- Puissance -->
@ -143,19 +143,23 @@
<label for="categorie">Catégories</label> <label for="categorie">Catégories</label>
<?php <?php
$rawCats = $product_data['categorie_id'] ?? null; $rawCats = $product_data['categorie_id'] ?? null;
$catIds = [];
if (is_array($rawCats)) { if (is_array($rawCats)) {
$catIds = array_map('intval', $rawCats); $catIds = array_map('intval', $rawCats);
} elseif (is_string($rawCats)) { } elseif (is_string($rawCats)) {
$catIds = array_filter(array_map('intval', explode(',', $rawCats)), fn($id) => $id > 0); $decoded = json_decode($rawCats, true);
if (is_array($decoded)) {
$catIds = array_map('intval', $decoded);
} else {
$catIds = array_filter(array_map('intval', explode(',', $rawCats)), fn($id) => $id > 0);
}
} elseif (is_int($rawCats) || ctype_digit((string)$rawCats)) { } elseif (is_int($rawCats) || ctype_digit((string)$rawCats)) {
$catIds = [(int) $rawCats]; $catIds = [(int) $rawCats];
} else {
$catIds = [];
} }
?> ?>
<select class="form-control select_group" id="categorie" name="categorie[]" multiple="multiple"> <select class="form-control select_group" id="categorie" name="categorie[]" multiple="multiple">
<?php foreach ($categorie as $k => $v): ?> <?php foreach ($categorie as $k => $v): ?>
<option value="<?= $v['id']; ?>" <?= in_array($v['id'], $catIds, true) ? 'selected="selected"' : '' ?>><?= esc($v['name']); ?></option> <option value="<?= $v['id']; ?>" <?= in_array((int)$v['id'], $catIds) ? 'selected="selected"' : '' ?>><?= esc($v['name']); ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>

View File

@ -155,7 +155,7 @@
<thead> <thead>
<tr> <tr>
<th>Image</th> <th>Image</th>
<th>UGS</th> <th>SERIE </th>
<th>Nom de produit</th> <th>Nom de produit</th>
<th>Prix</th> <th>Prix</th>
<th>Magasin</th> <th>Magasin</th>
@ -340,7 +340,12 @@
'columnDefs': [{ 'columnDefs': [{
targets: 3, targets: 3,
className: 'text-right' className: 'text-right'
}] }],
'createdRow': function(row, data, dataIndex) {
if (data[5] && data[5].indexOf('En attente de livraison') !== -1) {
$(row).css({'background-color': '#fff3cd', 'opacity': '0.85'});
}
}
}); });
// CORRECTION: Utilisation de la délégation d'événements pour les boutons // CORRECTION: Utilisation de la délégation d'événements pour les boutons

View File

@ -67,7 +67,9 @@
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <?php if (in_array('updateRecouvrement', $user_permission) || in_array('deleteRecouvrement', $user_permission)): ?>
<td></td>
<?php endif; ?>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -275,7 +277,7 @@
// Initialisation du DataTable // Initialisation du DataTable
manageTable = $('#manageTable').DataTable({ manageTable = $('#manageTable').DataTable({
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>', 'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',
'order': [], 'order': [[2, 'desc']],
'columnDefs': [{ 'columnDefs': [{
targets: 1, targets: 1,
className: 'text-right rowmontant' className: 'text-right rowmontant'
@ -306,18 +308,36 @@
}) })
// ====== CRÉATION DE RECOUVREMENT ====== // ====== CRÉATION DE RECOUVREMENT ======
$("#create_form").unbind('submit').on('submit', function() { $("#create_form").unbind('submit').on('submit', function(e) {
e.preventDefault();
var form = $(this); var form = $(this);
var submitBtn = form.find('button[type="submit"]');
// Supprimer les messages d'erreur // Supprimer les messages d'erreur
$(".text-danger").remove(); $(".text-danger").remove();
$.ajax({ // Pop-up de confirmation
url: form.attr('action'), Swal.fire({
type: form.attr('method'), title: 'Confirmer la création',
data: form.serialize(), text: 'Voulez-vous vraiment enregistrer ce recouvrement ?',
dataType: 'json', icon: 'question',
success: function(response) { showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Oui, enregistrer',
cancelButtonText: 'Annuler'
}).then((result) => {
if (!result.isConfirmed) return;
// Désactiver le bouton pour éviter les doublons
submitBtn.prop('disabled', true).text('Enregistrement...');
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
dataType: 'json',
success: function(response) {
if (response.success === true) { if (response.success === true) {
// Recharger immédiatement les données // Recharger immédiatement les données
manageTable.ajax.reload(null, false); manageTable.ajax.reload(null, false);
@ -374,9 +394,15 @@ $("#create_form").unbind('submit').on('submit', function() {
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Erreur!</strong> Une erreur est survenue lors de la création.' + '<strong>Erreur!</strong> Une erreur est survenue lors de la création.' +
'</div>'); '</div>');
},
complete: function() {
// Réactiver le bouton
submitBtn.prop('disabled', false).text('Enregistrer');
} }
}); });
}); // fin Swal.then
return false; return false;
}); });
// ====== SUPPRESSION DE RECOUVREMENT ====== // ====== SUPPRESSION DE RECOUVREMENT ======

View File

@ -25,7 +25,8 @@
</select> </select>
</div> </div>
<button type="submit" class="btn btn-default">Envoyer</button> <button type="submit" class="btn btn-default">Envoyer</button>
<a href="detail/stock" class="btn btn-sm btn-success">Détails</a> <a href="<?= base_url('reports/detail/stock') ?>" class="btn btn-sm btn-success">Details Stock</a>
<a href="<?= base_url('reports/detail/performance') ?>" class="btn btn-sm btn-primary">Performances</a>
</form> </form>
</div> </div>

View File

@ -73,27 +73,28 @@
<label for="endDate" class="form-label">Date de fin</label> <label for="endDate" class="form-label">Date de fin</label>
<input type="date" id="endDate" class="form-control"> <input type="date" id="endDate" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="pvente" class="form-label">Points de ventes</label> <label for="pvente" class="form-label">Points de ventes</label>
<select name="" id="pvente" class="form-control"> <select id="pvente" class="form-control">
<option value="TOUS">TOUS</option> <option value="TOUS">TOUS</option>
<?php <?php foreach ($stores as $value): ?>
foreach ($stores as $value) { <option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
?> <?php endforeach; ?>
<option value="<?= $value['name']; ?>"><?= $value['name']; ?>
</option>
<?php
}
?>
</select> </select>
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-2">
<label for="commercialFilter" class="form-label">Commercial</label>
<select id="commercialFilter" class="form-control">
<option value="TOUS">TOUS</option>
<?php foreach ($commerciaux as $com): ?>
<option value="<?= $com['id']; ?>"><?= $com['firstname'] . ' ' . $com['lastname']; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<br> <br>
<button id="filteredB1" class="btn btn-primary w-100">Filtrer <button id="filteredB1" class="btn btn-primary w-100">Filtrer</button>
🔍</button> <button id="ExportBTN1" class="btn btn-success w-100">Exporter</button>
<button id="ExportBTN1" class="btn btn-success w-100">Exporter
📤</button>
</div> </div>
</div> </div>
<table id="commperformance" class="table table-hover table-striped"> <table id="commperformance" class="table table-hover table-striped">
@ -102,7 +103,7 @@
<th>Nom et prénom</th> <th>Nom et prénom</th>
<th>Email</th> <th>Email</th>
<th>Motos vendue</th> <th>Motos vendue</th>
<th>Date de vente</th><!-- return 2025-04-18 --> <th>Date de vente</th>
<th>Prix d'achat</th> <th>Prix d'achat</th>
<th>Prix de vente</th> <th>Prix de vente</th>
<th>Point de ventes</th> <th>Point de ventes</th>
@ -112,9 +113,9 @@
<tfoot> <tfoot>
<tr> <tr>
<th colspan="5" style="text-align:right">Total:</th> <th colspan="5" style="text-align:right">Total:</th>
<th></th> <!-- total Prix de vente -->
<th></th> <th></th>
<th></th> <!-- total Bénéfices --> <th></th>
<th></th>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
@ -135,48 +136,48 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;"> <div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
<div class="col-md-3"> <div class="col-md-2">
<label for="startDate2" class="form-label">Date de début</label> <label for="startDate2" class="form-label">Date de début</label>
<input type="date" id="startDate" class="form-control"> <input type="date" id="startDate2" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="endDate2" class="form-label">Date de fin</label> <label for="endDate2" class="form-label">Date de fin</label>
<input type="date" id="endDate" class="form-control"> <input type="date" id="endDate2" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="pvente" class="form-label">Points de ventes</label> <label for="pvente2" class="form-label">Points de ventes</label>
<select name="" id="pvente2" class="form-control"> <select id="pvente2" class="form-control">
<option value="TOUS">TOUS</option> <option value="TOUS">TOUS</option>
<?php <?php foreach ($stores as $value): ?>
foreach ($stores as $value) { <option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
?> <?php endforeach; ?>
<option value="<?= $value['name']; ?>"><?= $value['name']; ?>
</option>
<?php
}
?>
</select> </select>
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-2">
<label for="mecanicienFilter" class="form-label">Mécanicien</label>
<select id="mecanicienFilter" class="form-control">
<option value="TOUS">TOUS</option>
<?php foreach ($mecaniciens as $mec): ?>
<option value="<?= $mec['id']; ?>"><?= $mec['firstname'] . ' ' . $mec['lastname']; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<br> <br>
<button id="filteredB2" class="btn btn-primary w-100">Filtrer <button id="filteredB2" class="btn btn-primary w-100">Filtrer</button>
🔍</button> <button id="ExportBTN2" class="btn btn-success w-100">Exporter</button>
<button id="ExportBTN2" class="btn btn-success w-100">Exporter
📤</button>
</div> </div>
</div> </div>
<table id="mecperformance" class="table table-hover table-striped"> <table id="mecperformance" class="table table-hover table-striped">
<thead> <thead>
<tr> <tr>
<th>Nom et prénom</th> <th>Mécanicien</th>
<th>Email</th> <th>Image</th>
<th>Motos vendue</th> <th>Moto réparée</th>
<th>Date de vente</th><!-- return 2025-04-18 --> <th> Série</th>
<th>Prix d'achat</th> <th>Point de vente</th>
<th>Prix de vente</th> <th>Date début</th>
<th>Point de ventes</th> <th>Date fin</th>
<th>Bénefices</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@ -200,8 +201,10 @@
// Initialize the datatable // Initialize the datatable
var baseUrl = '<?= base_url() ?>';
manageTable = $('#commperformance').DataTable({ manageTable = $('#commperformance').DataTable({
'ajax': 'fetchPerformances', 'ajax': baseUrl + 'reports/detail/fetchPerformances',
'order': [], 'order': [],
'pageLength': 5, 'pageLength': 5,
'lengthMenu': [ 'lengthMenu': [
@ -241,49 +244,44 @@
} }
}); });
// Filtre commercial - côté serveur
$('#filteredB1').on('click', function () { $('#filteredB1').on('click', function () {
const startDate = $('#startDate').val(); var startDate = $('#startDate').val();
const endDate = $('#endDate').val(); var endDate = $('#endDate').val();
const pvente = $('#pvente').val(); var pvente = $('#pvente').val();
var commercial = $('#commercialFilter').val();
// Get all original data (you may need to fetch from server or already loaded) var url = baseUrl + 'reports/detail/fetchPerformances?startDate=' + startDate + '&endDate=' + endDate + '&pvente=' + pvente + '&commercial=' + commercial;
manageTable.ajax.url('fetchPerformances').load(function () { manageTable.ajax.url(url).load();
const filteredData = []; });
manageTable.rows().every(function () { // DataTable mécanicien
const data = this.data(); var mecTable = $('#mecperformance').DataTable({
const saleDate = data[3].split(' ')[0]; // extract YYYY-MM-DD from date 'ajax': baseUrl + 'mecanicien/fetchMecanicienPerformances',
const store = data[6]; 'order': [],
'pageLength': 10
});
// Filter logic // Filtre mécanicien - côté serveur
const dateMatch = (!startDate && !endDate) || $('#filteredB2').on('click', function () {
(startDate && endDate && saleDate >= startDate && saleDate <= endDate) || var startDate = $('#startDate2').val();
(startDate && !endDate && saleDate >= startDate) || var endDate = $('#endDate2').val();
(!startDate && endDate && saleDate <= endDate); var mecanic = $('#mecanicienFilter').val();
const storeMatch = (pvente === 'TOUS' || pvente === store); var url = baseUrl + 'mecanicien/fetchMecanicienPerformances?startDate=' + startDate + '&endDate=' + endDate + '&mecanic_id=' + (mecanic === 'TOUS' ? '' : mecanic);
mecTable.ajax.url(url).load();
if (dateMatch && storeMatch) {
filteredData.push(data);
}
});
// Clear and reload table with filtered data
manageTable.clear().rows.add(filteredData).draw();
});
}); });
}); });
// Export commercial
document.getElementById('ExportBTN1').addEventListener('click', function () { document.getElementById('ExportBTN1').addEventListener('click', function () {
// Select your table var wb = XLSX.utils.table_to_book(document.getElementById('commperformance'), { sheet: "Commercial" });
var table = document.getElementById('commperformance');
// Convert it to a workbook
var wb = XLSX.utils.table_to_book(table, {
sheet: "Feuille1"
});
// Export it
XLSX.writeFile(wb, 'export-commercial-performance.xlsx'); XLSX.writeFile(wb, 'export-commercial-performance.xlsx');
}); });
// Export mécanicien
document.getElementById('ExportBTN2').addEventListener('click', function () {
var wb = XLSX.utils.table_to_book(document.getElementById('mecperformance'), { sheet: "Mecanicien" });
XLSX.writeFile(wb, 'export-mecanicien-performance.xlsx');
});
</script> </script>

View File

@ -57,9 +57,17 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<!-- Filter Bar --> <!-- Filter Bar -->
<div class="row g-3"> <div class="row g-3 align-items-center mb-3" style="margin: 5px 0;">
<div class="col-md-2">
<label for="startDateStock" class="form-label fw-bold">Date de début</label>
<input type="date" id="startDateStock" class="form-control">
</div>
<div class="col-md-2">
<label for="endDateStock" class="form-label fw-bold">Date de fin</label>
<input type="date" id="endDateStock" class="form-control">
</div>
<div class="col-md-3"> <div class="col-md-3">
<label for="storeFilter" class="form-label fw-bold">🏪 Points de ventes</label> <label for="storeFilter" class="form-label fw-bold">Points de ventes</label>
<select id="storeFilter" class="form-control"> <select id="storeFilter" class="form-control">
<option value="TOUS">TOUS</option> <option value="TOUS">TOUS</option>
<?php foreach ($stores as $value) { ?> <?php foreach ($stores as $value) { ?>
@ -67,9 +75,9 @@
<?php } ?> <?php } ?>
</select> </select>
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-2 d-flex align-items-end">
<br> <br>
<button id="filterBtn" class="btn btn-primary w-100">🔍 Filtrer</button> <button id="filterBtn" class="btn btn-primary w-100">Filtrer</button>
</div> </div>
</div> </div>
@ -188,7 +196,7 @@
manageTable = $('#venteTable').DataTable({ manageTable = $('#venteTable').DataTable({
'ajax': 'fetctData/' + 0, 'ajax': 'fetchData/' + 0,
'order': [], 'order': [],
'pageLength': 5, // Set default rows per page 'pageLength': 5, // Set default rows per page
'lengthMenu': [ 'lengthMenu': [
@ -198,7 +206,7 @@
}); });
manageTable2 = $('#stockTable').DataTable({ manageTable2 = $('#stockTable').DataTable({
'ajax': 'fetctDataStock/' + 0, 'ajax': 'fetchDataStock/' + 0,
'order': [], 'order': [],
'pageLength': 5, // Set default rows per page 'pageLength': 5, // Set default rows per page
'lengthMenu': [ 'lengthMenu': [
@ -209,7 +217,7 @@
manageTable3 = $('#export1').DataTable({ manageTable3 = $('#export1').DataTable({
ajax: { ajax: {
url: 'fetctDataStock2/' + 0, url: 'fetchDataStock2/' + 0,
dataSrc: 'data' dataSrc: 'data'
}, },
order: [], order: [],
@ -226,8 +234,8 @@
let filterValue = filterBtn.value === "TOUS" ? "0" : filterBtn.value; let filterValue = filterBtn.value === "TOUS" ? "0" : filterBtn.value;
// Update the DataTable dynamically without reinitialization // Update the DataTable dynamically without reinitialization
manageTable.ajax.url('fetctData/' + filterValue).load(); manageTable.ajax.url('fetchData/' + filterValue).load();
manageTable2.ajax.url('fetctDataStock/' + filterValue).load(); manageTable2.ajax.url('fetchDataStock/' + filterValue).load();
}); });
let productsSold = <?= $ventes ?>; let productsSold = <?= $ventes ?>;
@ -249,6 +257,14 @@
// Trigger the filter on button click // Trigger the filter on button click
$('#filterBtn').click(function () { $('#filterBtn').click(function () {
let storeId = $('#storeFilter').val(); let storeId = $('#storeFilter').val();
let filterValue = storeId === "TOUS" ? "0" : storeId;
let startDate = $('#startDateStock').val();
let endDate = $('#endDateStock').val();
let params = '?startDate=' + startDate + '&endDate=' + endDate;
manageTable.ajax.url('fetchData/' + filterValue + params).load();
manageTable2.ajax.url('fetchDataStock/' + filterValue + params).load();
manageTable3.ajax.url('fetchDataStock2/' + filterValue + params).load();
applyFilter(storeId); applyFilter(storeId);
}); });

View File

@ -200,7 +200,7 @@
<thead> <thead>
<tr> <tr>
<th>Image</th> <th>Image</th>
<th>UGS</th> <th>SERIE </th>
<th>Désignation</th> <th>Désignation</th>
<th>Statut</th> <th>Statut</th>
<th>Action</th> <th>Action</th>
@ -272,7 +272,7 @@
<th>Image</th> <th>Image</th>
<th> Facture</th> <th> Facture</th>
<th>Désignation</th> <th>Désignation</th>
<th>UGS</th> <th>SERIE </th>
<th>Marque</th> <th>Marque</th>
<th>Client</th> <th>Client</th>
<th>Magasin</th> <th>Magasin</th>

View File

@ -278,6 +278,7 @@ body {
transition: width 0.3s ease; transition: width 0.3s ease;
box-shadow: 2px 0 20px rgba(0, 0, 0, 0.3); box-shadow: 2px 0 20px rgba(0, 0, 0, 0.3);
padding-top: 0 !important; padding-top: 0 !important;
padding-bottom: 80px;
} }
.main-sidebar .logo { .main-sidebar .logo {