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:
parent
a195d24e78
commit
fe80b9c4f8
@ -226,7 +226,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
$routes->get('detail/stock', [ReportController::class, 'stockDetail']);
|
||||
|
||||
// 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/fetchDataStock2/(:num)', [ReportController::class, 'fetchProductStock2/$1']);
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ abstract class AdminController extends BaseController
|
||||
$group_data = $Groups->getUserGroupByUserId($userId);
|
||||
|
||||
$this->permission = unserialize($group_data['permission']);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>";
|
||||
}
|
||||
|
||||
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)
|
||||
? " <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>";
|
||||
@ -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" />';
|
||||
|
||||
// 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
|
||||
$result['data'][] = [
|
||||
$img,
|
||||
@ -147,6 +158,7 @@ public function loginPost()
|
||||
number_format($value['prix_vente'], 0, ',', ' '),
|
||||
$value['puissance'] . ' CC',
|
||||
$value['numero_de_moteur'],
|
||||
$statut,
|
||||
$buttons
|
||||
];
|
||||
}
|
||||
|
||||
@ -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']));
|
||||
|
||||
// ✅ 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') {
|
||||
$productName = $value['product_name'] ?? 'Produit sur mer';
|
||||
} else {
|
||||
$productName = !empty($value['product_name'])
|
||||
? $value['product_name']
|
||||
: $product->getProductNameById($value['product_id'] ?? 0);
|
||||
$productData = !empty($value['product_id']) ? $product->find($value['product_id']) : null;
|
||||
$productName = $productData['name'] ?? ($value['product_name'] ?? 'N/A');
|
||||
$productSku = $productData['sku'] ?? '';
|
||||
}
|
||||
|
||||
|
||||
if ($isAdmin) {
|
||||
return [
|
||||
$value['customer_name'],
|
||||
$value['customer_phone'],
|
||||
$value['customer_address'],
|
||||
$productName,
|
||||
$productSku,
|
||||
number_format((int)$value['gross_amount'], 0, ',', ' '),
|
||||
number_format((int)$value['avance_amount'], 0, ',', ' '),
|
||||
number_format((int)$value['amount_due'], 0, ',', ' '),
|
||||
@ -141,6 +143,7 @@ private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCai
|
||||
return [
|
||||
$value['avance_id'],
|
||||
$productName,
|
||||
$productSku,
|
||||
number_format((int)$value['avance_amount'], 0, ',', ' '),
|
||||
number_format((int)$value['amount_due'], 0, ',', ' '),
|
||||
$date_time,
|
||||
@ -228,12 +231,13 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
|
||||
|
||||
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
|
||||
|
||||
$productSku = '';
|
||||
if ($value['type_avance'] === 'mere') {
|
||||
$productName = $value['product_name'] ?? 'Produit sur mer';
|
||||
} else {
|
||||
$productName = !empty($value['product_name'])
|
||||
? $value['product_name']
|
||||
: $product->getProductNameById($value['product_id'] ?? 0);
|
||||
$productData = !empty($value['product_id']) ? $product->find($value['product_id']) : null;
|
||||
$productName = $productData['name'] ?? ($value['product_name'] ?? 'N/A');
|
||||
$productSku = $productData['sku'] ?? '';
|
||||
}
|
||||
|
||||
if ($isAdmin) {
|
||||
@ -242,6 +246,7 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
|
||||
$value['customer_phone'],
|
||||
$value['customer_address'],
|
||||
$productName,
|
||||
$productSku,
|
||||
number_format((int)$value['gross_amount'], 0, ',', ' '),
|
||||
number_format((int)$value['avance_amount'], 0, ',', ' '),
|
||||
$status,
|
||||
@ -252,6 +257,7 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
|
||||
$result['data'][] = [
|
||||
$value['avance_id'],
|
||||
$productName,
|
||||
$productSku,
|
||||
number_format((int)$value['avance_amount'], 0, ',', ' '),
|
||||
$status,
|
||||
$date_time,
|
||||
|
||||
@ -71,6 +71,7 @@ class HistoriqueController extends AdminController
|
||||
$row['product_name'] ?? 'N/A',
|
||||
$row['sku'] ?? 'N/A',
|
||||
$row['store_name'] ?? 'N/A',
|
||||
$row['user_name'] ?? 'N/A',
|
||||
$this->getActionBadge($row['action']),
|
||||
$row['description'] ?? ''
|
||||
];
|
||||
|
||||
@ -298,7 +298,7 @@ class MecanicienController extends AdminController
|
||||
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
|
||||
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
|
||||
$storeName = $repa['store_name'] ?? 'N/A';
|
||||
|
||||
|
||||
$result['data'][$key] = [
|
||||
$user_name,
|
||||
$image,
|
||||
|
||||
@ -663,16 +663,21 @@ class OrderController extends AdminController
|
||||
|
||||
$Notification->notifyGroupsByPermissionAllStores('notifRemise', $message, 'remise/');
|
||||
|
||||
} else {
|
||||
// Commande sans remise
|
||||
$Notification->notifyGroupsByPermission(
|
||||
'notifSortieCaisse',
|
||||
"📦 Nouvelle commande à valider : {$bill_no}<br>" .
|
||||
// Notifier la Caissière (notifCommande) et l'admin/direction (notifSortieCaisse)
|
||||
$messageCommande = "📦 Nouvelle commande (avec remise) à traiter : {$bill_no}<br>" .
|
||||
"Client : {$data['customer_name']}<br>" .
|
||||
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar",
|
||||
(int)$users['store_id'],
|
||||
"orders"
|
||||
);
|
||||
"Montant demandé : " . number_format($discount, 0, ',', ' ') . " Ar<br>" .
|
||||
"En attente de validation remise";
|
||||
$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") {
|
||||
@ -1721,23 +1726,18 @@ public function update(int $id)
|
||||
}
|
||||
|
||||
|
||||
public function printDiv(int $id)
|
||||
public function printDiv(int $id)
|
||||
{
|
||||
$Orders = new Orders();
|
||||
$OrderItems = new OrderItems();
|
||||
$order = $Orders->getOrdersData($id);
|
||||
|
||||
$docType = $order['document_type'] ?? 'facture';
|
||||
|
||||
// Rediriger vers la bonne méthode selon le type
|
||||
switch($docType) {
|
||||
case 'facture':
|
||||
return $this->print31($id); // Factures individuelles
|
||||
case 'bl':
|
||||
return $this->print7($id); // Bon de livraison
|
||||
case 'both':
|
||||
return $this->print31($id); // Factures + Bon de livraison
|
||||
default:
|
||||
return $this->print31($id);
|
||||
$items = $OrderItems->getOrdersItemData($id);
|
||||
|
||||
// Plus d'1 produit = BL, 1 seul produit = Facture
|
||||
if (count($items) > 1) {
|
||||
return $this->print7($id); // Bon de livraison
|
||||
} else {
|
||||
return $this->print5($id); // Facture
|
||||
}
|
||||
}
|
||||
|
||||
@ -2007,23 +2007,26 @@ public function print5(int $id)
|
||||
|
||||
$discount = (float) $order['discount'];
|
||||
$grossAmount = (float) $order['gross_amount'];
|
||||
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
|
||||
$inWords = strtoupper($this->numberToWords((int) round($totalTTC)));
|
||||
$totalAPayer = ($discount > 0) ? $discount : $grossAmount;
|
||||
$remiseAmount = ($discount > 0) ? ($grossAmount - $discount) : 0;
|
||||
$inWords = strtoupper($this->numberToWords((int) round($totalAPayer)));
|
||||
|
||||
// Construire les lignes du tableau
|
||||
$tableRows = '';
|
||||
$totalPrixIndividuels = 0;
|
||||
foreach ($items as $it) {
|
||||
$details = $this->getOrderItemDetails($it);
|
||||
if (!$details) continue;
|
||||
|
||||
$qty = isset($it['qty']) ? (int)$it['qty'] : 1;
|
||||
$prix = ($discount > 0) ? ($discount / $qty) : $details['prix'];
|
||||
$prixItem = $details['prix'] * $qty;
|
||||
$totalPrixIndividuels += $prixItem;
|
||||
|
||||
$tableRows .= '<tr>
|
||||
<td>'.esc($details['marque']).'</td>
|
||||
<td>'.esc($details['numero_chassis']).'</td>
|
||||
<td>'.esc($details['puissance']).'</td>
|
||||
<td>'.number_format($prix * $qty, 0, ' ', ' ').' MGA</td>
|
||||
<td>'.number_format($prixItem, 0, ' ', ' ').'</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
@ -2043,7 +2046,7 @@ public function print5(int $id)
|
||||
$companyPhone = esc($company['phone']);
|
||||
$companyPhone2 = esc($company['phone2']);
|
||||
$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/";
|
||||
|
||||
$html = '<!DOCTYPE html>
|
||||
@ -2058,7 +2061,8 @@ public function print5(int $id)
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@ -2084,32 +2088,33 @@ public function print5(int $id)
|
||||
|
||||
/* Header */
|
||||
.f-title {
|
||||
font-size: 22px;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.f-company {
|
||||
font-size: 10px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.f-company strong { font-size: 12px; }
|
||||
.f-company strong { font-size: 14px; }
|
||||
|
||||
.f-doit {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.f-client {
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.f-client .field { margin: 2px 0; }
|
||||
.f-client .field { margin: 3px 0; }
|
||||
|
||||
/* Table */
|
||||
.f-table {
|
||||
@ -2121,9 +2126,9 @@ public function print5(int $id)
|
||||
.f-table th, .f-table td {
|
||||
border-left: 2px solid #000;
|
||||
border-right: 2px solid #000;
|
||||
padding: 6px 8px;
|
||||
padding: 8px 10px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.f-table th {
|
||||
@ -2132,7 +2137,7 @@ public function print5(int $id)
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
|
||||
.f-table td { height: 28px; }
|
||||
.f-table td { height: 30px; }
|
||||
|
||||
.f-table tbody tr:last-child td {
|
||||
border-bottom: 2px solid #000;
|
||||
@ -2140,7 +2145,8 @@ public function print5(int $id)
|
||||
|
||||
/* Total en lettres */
|
||||
.f-words {
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@ -2160,7 +2166,7 @@ public function print5(int $id)
|
||||
|
||||
.f-sig-box .sig-label {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Date + N° + QR en bas */
|
||||
@ -2172,7 +2178,7 @@ public function print5(int $id)
|
||||
}
|
||||
|
||||
.f-bottom .f-date-info {
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.f-bottom canvas {
|
||||
@ -2196,13 +2202,13 @@ public function print5(int $id)
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 2px dashed #aaa;
|
||||
line-height: 1.6;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cond-box:last-child { border-bottom: none; }
|
||||
|
||||
.cond-title {
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
text-decoration: underline;
|
||||
@ -2212,7 +2218,8 @@ public function print5(int $id)
|
||||
.cond-box ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cond-box li {
|
||||
@ -2261,6 +2268,8 @@ public function print5(int $id)
|
||||
$qrId = 'qr_recto_'.$i;
|
||||
$html .= '
|
||||
<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 class="f-company" style="margin-bottom:0;">
|
||||
<strong>'.$companyName.'</strong><br>
|
||||
@ -2269,15 +2278,13 @@ public function print5(int $id)
|
||||
Contact : '.$companyPhone.' / '.$companyPhone2.'<br>
|
||||
'.$companyAddress.'
|
||||
</div>
|
||||
<div style="text-align:right; font-size:10px;">
|
||||
<div style="text-align:right; font-size:12px;">
|
||||
<div>DATE : '.$today.'</div>
|
||||
<div>N° : '.$billNo.'</div>
|
||||
<canvas id="'.$qrId.'" style="margin-top:5px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="f-title" style="text-align:center;">FACTURE</div>
|
||||
|
||||
<div class="f-doit">DOIT</div>
|
||||
|
||||
<div class="f-client">
|
||||
@ -2302,7 +2309,14 @@ public function print5(int $id)
|
||||
</table>
|
||||
|
||||
<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 class="f-signatures">
|
||||
@ -2532,22 +2546,26 @@ public function print7(int $id)
|
||||
|
||||
$discount = (float) $order['discount'];
|
||||
$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 = '';
|
||||
$totalPrixIndividuels = 0;
|
||||
foreach ($items as $item) {
|
||||
$details = $this->getOrderItemDetails($item);
|
||||
if (!$details) continue;
|
||||
|
||||
$qty = isset($item['qty']) ? (int)$item['qty'] : 1;
|
||||
$prix = ($discount > 0) ? ($discount / $qty) : $details['prix'];
|
||||
$prixItem = $details['prix'] * $qty;
|
||||
$totalPrixIndividuels += $prixItem;
|
||||
|
||||
$tableRows .= '<tr>
|
||||
<td>'.esc($details['marque']).'</td>
|
||||
<td>'.esc($details['numero_moteur']).'</td>
|
||||
<td>'.esc($details['puissance']).'</td>
|
||||
<td>'.number_format($prix * $qty, 0, '', ' ').'</td>
|
||||
<td>'.number_format($prixItem, 0, '', ' ').'</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
@ -2573,8 +2591,8 @@ public function print7(int $id)
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@ -2596,12 +2614,12 @@ public function print7(int $id)
|
||||
}
|
||||
|
||||
.bl-company {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.bl-company strong {
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.bl-title-block {
|
||||
@ -2629,11 +2647,12 @@ public function print7(int $id)
|
||||
/* Client info */
|
||||
.bl-client {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.bl-client .field {
|
||||
margin: 3px 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.bl-client .field strong {
|
||||
@ -2643,7 +2662,7 @@ public function print7(int $id)
|
||||
|
||||
.bl-doit {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@ -2651,29 +2670,24 @@ public function print7(int $id)
|
||||
.bl-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bl-table th, .bl-table td {
|
||||
border-left: 2px solid #000;
|
||||
border-right: 2px solid #000;
|
||||
padding: 10px 15px;
|
||||
padding: 5px 10px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.bl-table th {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
border-top: 2px solid #000;
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
|
||||
.bl-table td {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.bl-table tbody tr:last-child td {
|
||||
border-bottom: 2px solid #000;
|
||||
}
|
||||
@ -2684,7 +2698,8 @@ public function print7(int $id)
|
||||
}
|
||||
|
||||
.bl-total {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@ -2758,7 +2773,14 @@ public function print7(int $id)
|
||||
<!-- Footer -->
|
||||
<div class="bl-footer">
|
||||
<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 class="bl-signatures">
|
||||
<div class="bl-sig-box">
|
||||
@ -2775,7 +2797,7 @@ public function print7(int $id)
|
||||
<script>
|
||||
new QRious({
|
||||
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,
|
||||
level: "H"
|
||||
});
|
||||
|
||||
@ -101,15 +101,20 @@ class ProductCOntroller extends AdminController
|
||||
$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);
|
||||
$isAvailable = ((int)$value['availability'] === 1);
|
||||
|
||||
$isProductAvailable = $isInStock && $isAvailable;
|
||||
|
||||
$availability = $isProductAvailable ?
|
||||
'<span class="label label-success">En stock</span>' :
|
||||
'<span class="label label-danger">Rupture</span>';
|
||||
|
||||
if ($productSold === 2) {
|
||||
$availability = '<span class="label label-warning">En attente de livraison</span>';
|
||||
} elseif ($productSold === 1) {
|
||||
$availability = '<span class="label label-default">Livré</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
|
||||
$buttons = '';
|
||||
@ -224,7 +229,7 @@ class ProductCOntroller extends AdminController
|
||||
'date_arivage' => $this->request->getPost('datea'),
|
||||
'puissance' => $this->request->getPost('puissance'),
|
||||
'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'),
|
||||
'infoManquekit' => $this->request->getPost('infoManquekit'),
|
||||
'info' => $this->request->getPost('info'),
|
||||
@ -297,6 +302,11 @@ class ProductCOntroller extends AdminController
|
||||
|
||||
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();
|
||||
$Stores = new Stores();
|
||||
$Category = new Category();
|
||||
@ -315,33 +325,33 @@ class ProductCOntroller extends AdminController
|
||||
$product_data = $Products->getProductData($id);
|
||||
$prix_minimal_data = $FourchettePrix->where('product_id', $id)->first();
|
||||
$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');
|
||||
$availability = ($availabilityValue === 1) ? 1 : 0;
|
||||
|
||||
$data = [
|
||||
'name' => $this->request->getPost('nom_de_produit'),
|
||||
'sku' => $this->request->getPost('numero_de_serie'),
|
||||
'price' => $this->request->getPost('price'),
|
||||
'name' => $this->request->getPost('nom_de_produit') ?? '',
|
||||
'sku' => $this->request->getPost('numero_de_serie') ?? '',
|
||||
'price' => $this->request->getPost('price') ?? '0',
|
||||
'qty' => 1,
|
||||
'description' => $this->request->getPost('description'),
|
||||
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'),
|
||||
'marque' => $this->request->getPost('marque'),
|
||||
'chasis' => $this->request->getPost('chasis'),
|
||||
'description' => $this->request->getPost('description') ?? '',
|
||||
'numero_de_moteur' => $this->request->getPost('numero_de_moteur') ?? '',
|
||||
'marque' => $this->request->getPost('marque') ?? '0',
|
||||
'chasis' => $this->request->getPost('chasis') ?? '',
|
||||
'store_id' => (int)$this->request->getPost('store'),
|
||||
'availability'=> $availability,
|
||||
'prix_vente' => $this->request->getPost('price_vente'),
|
||||
'date_arivage'=> $this->request->getPost('datea'),
|
||||
'puissance' => $this->request->getPost('puissance'),
|
||||
'cler' => $this->request->getPost('cler'),
|
||||
'categorie_id'=> json_encode($this->request->getPost('categorie[]')),
|
||||
'etats' => $this->request->getPost('etats'),
|
||||
'infoManquekit'=> $this->request->getPost('infoManquekit'),
|
||||
'info' => $this->request->getPost('info'),
|
||||
'infoManque' => $this->request->getPost('infoManque'),
|
||||
'type' => $this->request->getPost('type'),
|
||||
'prix_vente' => $this->request->getPost('price_vente') ?? '0',
|
||||
'date_arivage'=> $this->request->getPost('datea') ?: date('Y-m-d'),
|
||||
'puissance' => $this->request->getPost('puissance') ?? '',
|
||||
'cler' => $this->request->getPost('cler') ?? '',
|
||||
'categorie_id'=> $this->request->getPost('categorie') ? implode(',', $this->request->getPost('categorie')) : '',
|
||||
'etats' => $this->request->getPost('etats') ?? '',
|
||||
'infoManquekit'=> $this->request->getPost('infoManquekit') ?? '',
|
||||
'info' => $this->request->getPost('info') ?? '',
|
||||
'infoManque' => $this->request->getPost('infoManque') ?? '',
|
||||
'type' => $this->request->getPost('type') ?? '',
|
||||
];
|
||||
|
||||
// ---- GESTION PRIX MINIMAL ----
|
||||
@ -367,17 +377,39 @@ class ProductCOntroller extends AdminController
|
||||
}
|
||||
|
||||
// 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();
|
||||
$Products->update($id, ['image' => $uploadImage]);
|
||||
}
|
||||
|
||||
// Mise à jour du produit
|
||||
if ($Products->updateProduct($data, $id)) {
|
||||
session()->setFlashdata('success', 'Produit mis à jour avec succès.');
|
||||
return redirect()->to('/products');
|
||||
} else {
|
||||
session()->setFlashdata('errors', 'Une erreur est survenue lors de la mise à jour.');
|
||||
try {
|
||||
$db = \Config\Database::connect();
|
||||
$session = session();
|
||||
$currentUser = $session->get('user');
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
@ -138,24 +138,30 @@ class RecouvrementController extends AdminController
|
||||
"data" => []
|
||||
];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$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>';
|
||||
}
|
||||
$hasActions = in_array('updateRecouvrement', $this->permission) || in_array('deleteRecouvrement', $this->permission);
|
||||
|
||||
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>';
|
||||
}
|
||||
$result['data'][$key] = [
|
||||
foreach ($data as $key => $value) {
|
||||
$row = [
|
||||
$value['recouvrement_id'],
|
||||
number_format($value['recouvrement_montant'], 0, '.', ' '),
|
||||
$value['recouvrement_date'],
|
||||
$value['recouvrement_personnel'],
|
||||
$value['send_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);
|
||||
}
|
||||
@ -170,24 +176,30 @@ class RecouvrementController extends AdminController
|
||||
"data" => []
|
||||
];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$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>';
|
||||
}
|
||||
$hasActions = in_array('updateRecouvrement', $this->permission) || in_array('deleteRecouvrement', $this->permission);
|
||||
|
||||
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>';
|
||||
}
|
||||
$result['data'][$key] = [
|
||||
foreach ($data as $key => $value) {
|
||||
$row = [
|
||||
$value['recouvrement_id'],
|
||||
number_format($value['recouvrement_montant'], 0, '.', ' '),
|
||||
$value['recouvrement_date'],
|
||||
$value['recouvrement_personnel'],
|
||||
$value['send_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);
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ class ReportController extends AdminController
|
||||
{
|
||||
$Products = new Products();
|
||||
|
||||
$produitStock = $Products->getProductData2($id);
|
||||
$produitStock = $Products->getStockByBrand($id);
|
||||
$result = ['data' => []];
|
||||
|
||||
foreach ($produitStock as $key => $value) {
|
||||
@ -251,10 +251,24 @@ class ReportController extends AdminController
|
||||
$data['page_title'] = $this->pageTitle;
|
||||
$Stores = new Stores();
|
||||
|
||||
// echo '<pre>';
|
||||
// die(var_dump($orderTest));
|
||||
$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);
|
||||
}
|
||||
|
||||
@ -265,32 +279,27 @@ class ReportController extends AdminController
|
||||
$session = session();
|
||||
$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');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$pvente = $this->request->getGet('pvente');
|
||||
|
||||
// ✅ CORRECTION : Bonne concaténation des chaînes
|
||||
log_message('debug', 'Filtres Commercial reçus - startDate: ' . $startDate . ', endDate: ' . $endDate . ', pvente: ' . $pvente);
|
||||
$commercialId = $this->request->getGet('commercial');
|
||||
|
||||
// Pour Direction et Conseil : afficher TOUTES les performances AVEC FILTRES
|
||||
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);
|
||||
$orderPaid = $Orders->getPerformanceByOrders($startDate, $endDate, $pvente, $commercialId);
|
||||
foreach ($orderPaid as $key => $value) {
|
||||
// Déterminer le prix de vente réel
|
||||
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
|
||||
? $value['discount']
|
||||
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
|
||||
? $value['discount']
|
||||
: $value['prix_vente'];
|
||||
|
||||
// Calculer le bénéfice
|
||||
|
||||
$benefice = $prix_vente_reel - $value['price'];
|
||||
|
||||
|
||||
$result['data'][$key] = [
|
||||
$value['firstname'] . ' ' . $value['lastname'],
|
||||
$value['email'],
|
||||
($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($prix_vente_reel, 0, '.', ' '),
|
||||
$this->returnName($value['store_id']),
|
||||
|
||||
@ -274,15 +274,15 @@ class Orders extends Model
|
||||
$orderItemModel->where('order_id', $id)->delete();
|
||||
|
||||
// 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++) {
|
||||
$items = [
|
||||
'order_id' => $id,
|
||||
'product_id' => $data['product'][$x],
|
||||
'rate' => $data['rate_value'][$x],
|
||||
'qty' => isset($data['qty'][$x]) ? (int)$data['qty'][$x] : 1,
|
||||
'puissance' => $data['puissance'][$x],
|
||||
'amount' => $data['amount_value'][$x],
|
||||
'product_id' => is_array($data['product']) ? $data['product'][$x] : $data['product'],
|
||||
'rate' => is_array($data['rate_value']) ? ($data['rate_value'][$x] ?? 0) : ($data['rate_value'] ?? 0),
|
||||
'qty' => is_array($data['qty']) ? (int)($data['qty'][$x] ?? 1) : 1,
|
||||
'puissance' => is_array($data['puissance']) ? ($data['puissance'][$x] ?? '') : ($data['puissance'] ?? ''),
|
||||
'amount' => is_array($data['amount_value']) ? ($data['amount_value'][$x] ?? 0) : ($data['amount_value'] ?? 0),
|
||||
];
|
||||
|
||||
$orderItemModel->insert($items);
|
||||
@ -605,7 +605,7 @@ class Orders extends Model
|
||||
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')
|
||||
->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('users', 'users.id = orders.user_id')
|
||||
->whereIn('orders.paid_status', [1, 3]);
|
||||
|
||||
// ✅ AJOUT : FILTRES PAR DATE
|
||||
|
||||
if (!empty($startDate) && !empty($endDate)) {
|
||||
$builder->where('DATE(orders.date_time) >=', $startDate);
|
||||
$builder->where('DATE(orders.date_time) <=', $endDate);
|
||||
@ -624,14 +623,17 @@ class Orders extends Model
|
||||
} elseif (!empty($endDate)) {
|
||||
$builder->where('DATE(orders.date_time) <=', $endDate);
|
||||
}
|
||||
|
||||
// ✅ AJOUT : FILTRE PAR POINT DE VENTE
|
||||
|
||||
if (!empty($pvente) && $pvente !== 'TOUS') {
|
||||
$builder->where('stores.name', $pvente);
|
||||
}
|
||||
|
||||
|
||||
if (!empty($commercialId) && $commercialId !== 'TOUS') {
|
||||
$builder->where('orders.user_id', $commercialId);
|
||||
}
|
||||
|
||||
$builder->orderBy('orders.date_time', 'DESC');
|
||||
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
|
||||
@ -62,6 +62,23 @@ class Products extends Model
|
||||
->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)
|
||||
{
|
||||
if ($id == 0) {
|
||||
|
||||
@ -42,12 +42,16 @@ $isCaissier = isset($users['group_name']) && in_array($users['group_name'], ['Ca
|
||||
|
||||
<div class="col-md-3">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<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>
|
||||
</div>
|
||||
<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>Adresse</th>
|
||||
<th>Produit</th>
|
||||
<th>N° Série</th>
|
||||
<th>Prix</th>
|
||||
<th>Avance</th>
|
||||
<th>Reste à payer</th>
|
||||
@ -91,11 +96,12 @@ $isCaissier = isset($users['group_name']) && in_array($users['group_name'], ['Ca
|
||||
<?php endif;?>
|
||||
</tr>
|
||||
<?php endif;?>
|
||||
|
||||
|
||||
<?php if ($isCommerciale || $isCaissier): ?>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Produit</th>
|
||||
<th>N° Série</th>
|
||||
<th>Avance</th>
|
||||
<th>Reste à payer</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
|
||||
var isCaissier = <?php echo json_encode($isCaissier ?? false); ?>;
|
||||
var isCommerciale = <?php echo json_encode($isCommerciale ?? false); ?>;
|
||||
@ -516,19 +554,20 @@ $(document).ready(function() {
|
||||
{ title: "Téléphone" },
|
||||
{ title: "Adresse" },
|
||||
{ title: "Produit" },
|
||||
{
|
||||
{ title: "N° Série" },
|
||||
{
|
||||
title: "Prix",
|
||||
render: function(data, type, row) {
|
||||
return type === 'display' ? formatNumber(data) : data;
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
title: "Avance",
|
||||
render: function(data, type, row) {
|
||||
return type === 'display' ? formatNumber(data) : data;
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
title: "Reste à payer",
|
||||
render: function(data, type, row) {
|
||||
return type === 'display' ? formatNumber(data) : data;
|
||||
@ -542,6 +581,10 @@ $(document).ready(function() {
|
||||
|
||||
var manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
|
||||
|
||||
// Charger les compteurs
|
||||
loadAvanceCounts();
|
||||
setInterval(loadAvanceCounts, 30000);
|
||||
|
||||
$('#avance_no_order').on('click', function() {
|
||||
$('#table-title').text('Avances Incomplètes');
|
||||
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
|
||||
@ -552,6 +595,7 @@ $(document).ready(function() {
|
||||
{ title: "Téléphone" },
|
||||
{ title: "Adresse" },
|
||||
{ title: "Produit" },
|
||||
{ title: "N° Série" },
|
||||
{
|
||||
title: "Prix",
|
||||
render: function(data, type, row) {
|
||||
@ -587,7 +631,8 @@ $(document).ready(function() {
|
||||
var userColumns = [
|
||||
{ title: "#" },
|
||||
{ title: "Produit" },
|
||||
{
|
||||
{ title: "N° Série" },
|
||||
{
|
||||
title: "Avance",
|
||||
render: function(data, type, row) {
|
||||
return type === 'display' ? formatNumber(data) : data;
|
||||
@ -617,12 +662,13 @@ $(document).ready(function() {
|
||||
$('#avanceTable').DataTable().destroy();
|
||||
}
|
||||
|
||||
// ✅ Reconstruire les headers (format caissière/commercial)
|
||||
// Reconstruire les headers (format caissière/commercial)
|
||||
rebuildTableHeaders([
|
||||
'#',
|
||||
'Produit',
|
||||
'Avance',
|
||||
'Reste à payer',
|
||||
'#',
|
||||
'Produit',
|
||||
'N° Série',
|
||||
'Avance',
|
||||
'Reste à payer',
|
||||
'Date',
|
||||
'Action'
|
||||
]);
|
||||
@ -653,6 +699,7 @@ $('#avance_order').on('click', function() {
|
||||
rebuildTableHeaders([
|
||||
'#',
|
||||
'Produit',
|
||||
'N° Série',
|
||||
'Avance',
|
||||
'Statut',
|
||||
'Date',
|
||||
@ -662,6 +709,7 @@ $('#avance_order').on('click', function() {
|
||||
var completedUserColumns = [
|
||||
{ title: "#" },
|
||||
{ title: "Produit" },
|
||||
{ title: "N° Série" },
|
||||
{ title: "Avance" },
|
||||
{ title: "Statut" },
|
||||
{ title: "Date" }
|
||||
@ -693,14 +741,15 @@ $('#avance_expired').on('click', function() {
|
||||
}
|
||||
|
||||
rebuildTableHeaders([
|
||||
'#',
|
||||
'Produit',
|
||||
'Avance',
|
||||
'Reste à payer',
|
||||
'#',
|
||||
'Produit',
|
||||
'N° Série',
|
||||
'Avance',
|
||||
'Reste à payer',
|
||||
'Date',
|
||||
'Action'
|
||||
]);
|
||||
|
||||
|
||||
manageTable = $('#avanceTable').DataTable({
|
||||
ajax: {
|
||||
url: base_url + 'avances/fetchExpiredAvance',
|
||||
@ -714,8 +763,10 @@ $('#avance_expired').on('click', function() {
|
||||
console.log('✅ DataTable Expirées initialisée');
|
||||
});
|
||||
|
||||
// Charger le compteur d'avances en attente
|
||||
// Charger les compteurs d'avances
|
||||
loadPendingCount();
|
||||
loadAvanceCounts();
|
||||
setInterval(loadAvanceCounts, 30000);
|
||||
setInterval(loadPendingCount, 30000);
|
||||
|
||||
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() {
|
||||
$.ajax({
|
||||
url: base_url + 'avances/fetchPendingValidation',
|
||||
type: 'POST', // ✅ CORRECTION
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
var count = response.data ? response.data.length : 0;
|
||||
$('#pending-count').text(count);
|
||||
|
||||
if (count > 0) {
|
||||
$('#pending-count').removeClass('badge-light').addClass('badge-danger');
|
||||
$('#avance_pending').addClass('btn-pulse');
|
||||
@ -1601,13 +1651,12 @@ function loadPendingCount() {
|
||||
$('#pending-count').removeClass('badge-danger').addClass('badge-light');
|
||||
$('#avance_pending').removeClass('btn-pulse');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
console.error('Erreur chargement compteur avances en attente');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// loadAvanceCounts defini plus haut
|
||||
|
||||
// =====================================================
|
||||
// 🔥 TRAITER AVANCES EXPIRÉES (ADMIN)
|
||||
// =====================================================
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
<th>Prix</th>
|
||||
<th>Puissances</th>
|
||||
<th>N° Moteur</th>
|
||||
<th>Disponibilité</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -72,10 +73,15 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
'ajax': `<?= base_url('ventes/fetchProductVente') ?>/${id}`,
|
||||
'order': [],
|
||||
'columnDefs': [{
|
||||
targets: 6,
|
||||
targets: 7,
|
||||
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>
|
||||
@ -75,6 +75,7 @@
|
||||
<th>Produit</th>
|
||||
<th>SKU</th>
|
||||
<th>Magasin</th>
|
||||
<th>Utilisateur</th>
|
||||
<th>Action</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
@ -238,13 +239,14 @@ $(document).ready(function() {
|
||||
"complete": function() { $("#loading").hide(); }
|
||||
},
|
||||
"columns": [
|
||||
{ "data": 0, "width": "15%" },
|
||||
{ "data": 1, "width": "20%" },
|
||||
{ "data": 0, "width": "13%" },
|
||||
{ "data": 1, "width": "15%" },
|
||||
{ "data": 2, "width": "10%" },
|
||||
{ "data": 3, "width": "12%" },
|
||||
{
|
||||
"data": 4,
|
||||
"width": "10%",
|
||||
{ "data": 4, "width": "10%" },
|
||||
{
|
||||
"data": 5,
|
||||
"width": "8%",
|
||||
"render": function(data) {
|
||||
let badgeClass = "badge bg-gray";
|
||||
if (data === "CREATE") badgeClass = "badge bg-green";
|
||||
@ -254,7 +256,7 @@ $(document).ready(function() {
|
||||
return '<span class="'+badgeClass+'">'+data+'</span>';
|
||||
}
|
||||
},
|
||||
{ "data": 5, "width": "33%" }
|
||||
{ "data": 6, "width": "32%" }
|
||||
],
|
||||
"order": [[0, "desc"]],
|
||||
"pageLength": 25,
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
<h3 class="box-title">Mise à jours Moto</h3>
|
||||
</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">
|
||||
<!-- Image actuelle -->
|
||||
<div class="form-group">
|
||||
@ -111,7 +111,7 @@
|
||||
<!-- Date d'arrivage -->
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
<!-- Puissance -->
|
||||
@ -143,19 +143,23 @@
|
||||
<label for="categorie">Catégories</label>
|
||||
<?php
|
||||
$rawCats = $product_data['categorie_id'] ?? null;
|
||||
$catIds = [];
|
||||
if (is_array($rawCats)) {
|
||||
$catIds = array_map('intval', $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)) {
|
||||
$catIds = [(int) $rawCats];
|
||||
} else {
|
||||
$catIds = [];
|
||||
}
|
||||
?>
|
||||
<select class="form-control select_group" id="categorie" name="categorie[]" multiple="multiple">
|
||||
<?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; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -155,7 +155,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>UGS</th>
|
||||
<th>N° SERIE </th>
|
||||
<th>Nom de produit</th>
|
||||
<th>Prix</th>
|
||||
<th>Magasin</th>
|
||||
@ -340,7 +340,12 @@
|
||||
'columnDefs': [{
|
||||
targets: 3,
|
||||
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
|
||||
|
||||
@ -67,7 +67,9 @@
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -275,7 +277,7 @@
|
||||
// Initialisation du DataTable
|
||||
manageTable = $('#manageTable').DataTable({
|
||||
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',
|
||||
'order': [],
|
||||
'order': [[2, 'desc']],
|
||||
'columnDefs': [{
|
||||
targets: 1,
|
||||
className: 'text-right rowmontant'
|
||||
@ -306,18 +308,36 @@
|
||||
})
|
||||
|
||||
// ====== 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 submitBtn = form.find('button[type="submit"]');
|
||||
|
||||
// Supprimer les messages d'erreur
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
// Pop-up de confirmation
|
||||
Swal.fire({
|
||||
title: 'Confirmer la création',
|
||||
text: 'Voulez-vous vraiment enregistrer ce recouvrement ?',
|
||||
icon: 'question',
|
||||
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) {
|
||||
// Recharger immédiatement les données
|
||||
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">×</span></button>' +
|
||||
'<strong>Erreur!</strong> Une erreur est survenue lors de la création.' +
|
||||
'</div>');
|
||||
},
|
||||
complete: function() {
|
||||
// Réactiver le bouton
|
||||
submitBtn.prop('disabled', false).text('Enregistrer');
|
||||
}
|
||||
});
|
||||
|
||||
}); // fin Swal.then
|
||||
|
||||
return false;
|
||||
});
|
||||
// ====== SUPPRESSION DE RECOUVREMENT ======
|
||||
|
||||
@ -25,7 +25,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@ -73,27 +73,28 @@
|
||||
<label for="endDate" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-2">
|
||||
<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>
|
||||
<?php
|
||||
foreach ($stores as $value) {
|
||||
?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?>
|
||||
</option>
|
||||
<?php
|
||||
}
|
||||
|
||||
?>
|
||||
<?php foreach ($stores as $value): ?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</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>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer
|
||||
🔍</button>
|
||||
<button id="ExportBTN1" class="btn btn-success w-100">Exporter
|
||||
📤</button>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer</button>
|
||||
<button id="ExportBTN1" class="btn btn-success w-100">Exporter</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="commperformance" class="table table-hover table-striped">
|
||||
@ -102,7 +103,7 @@
|
||||
<th>Nom et prénom</th>
|
||||
<th>Email</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 de vente</th>
|
||||
<th>Point de ventes</th>
|
||||
@ -112,9 +113,9 @@
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="5" style="text-align:right">Total:</th>
|
||||
<th></th> <!-- total Prix de vente -->
|
||||
<th></th>
|
||||
<th></th> <!-- total Bénéfices -->
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -135,48 +136,48 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<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>
|
||||
<input type="date" id="startDate" class="form-control">
|
||||
<input type="date" id="startDate2" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-2">
|
||||
<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 class="col-md-3">
|
||||
<label for="pvente" class="form-label">Points de ventes</label>
|
||||
<select name="" id="pvente2" class="form-control">
|
||||
<div class="col-md-2">
|
||||
<label for="pvente2" class="form-label">Points de ventes</label>
|
||||
<select id="pvente2" class="form-control">
|
||||
<option value="TOUS">TOUS</option>
|
||||
<?php
|
||||
foreach ($stores as $value) {
|
||||
?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?>
|
||||
</option>
|
||||
<?php
|
||||
}
|
||||
|
||||
?>
|
||||
<?php foreach ($stores as $value): ?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</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>
|
||||
<button id="filteredB2" class="btn btn-primary w-100">Filtrer
|
||||
🔍</button>
|
||||
<button id="ExportBTN2" class="btn btn-success w-100">Exporter
|
||||
📤</button>
|
||||
<button id="filteredB2" class="btn btn-primary w-100">Filtrer</button>
|
||||
<button id="ExportBTN2" class="btn btn-success w-100">Exporter</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="mecperformance" class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom et prénom</th>
|
||||
<th>Email</th>
|
||||
<th>Motos vendue</th>
|
||||
<th>Date de vente</th><!-- return 2025-04-18 -->
|
||||
<th>Prix d'achat</th>
|
||||
<th>Prix de vente</th>
|
||||
<th>Point de ventes</th>
|
||||
<th>Bénefices</th>
|
||||
<th>Mécanicien</th>
|
||||
<th>Image</th>
|
||||
<th>Moto réparée</th>
|
||||
<th>N° Série</th>
|
||||
<th>Point de vente</th>
|
||||
<th>Date début</th>
|
||||
<th>Date fin</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@ -200,8 +201,10 @@
|
||||
|
||||
// Initialize the datatable
|
||||
|
||||
var baseUrl = '<?= base_url() ?>';
|
||||
|
||||
manageTable = $('#commperformance').DataTable({
|
||||
'ajax': 'fetchPerformances',
|
||||
'ajax': baseUrl + 'reports/detail/fetchPerformances',
|
||||
'order': [],
|
||||
'pageLength': 5,
|
||||
'lengthMenu': [
|
||||
@ -241,49 +244,44 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Filtre commercial - côté serveur
|
||||
$('#filteredB1').on('click', function () {
|
||||
const startDate = $('#startDate').val();
|
||||
const endDate = $('#endDate').val();
|
||||
const pvente = $('#pvente').val();
|
||||
var startDate = $('#startDate').val();
|
||||
var endDate = $('#endDate').val();
|
||||
var pvente = $('#pvente').val();
|
||||
var commercial = $('#commercialFilter').val();
|
||||
|
||||
// Get all original data (you may need to fetch from server or already loaded)
|
||||
manageTable.ajax.url('fetchPerformances').load(function () {
|
||||
const filteredData = [];
|
||||
var url = baseUrl + 'reports/detail/fetchPerformances?startDate=' + startDate + '&endDate=' + endDate + '&pvente=' + pvente + '&commercial=' + commercial;
|
||||
manageTable.ajax.url(url).load();
|
||||
});
|
||||
|
||||
manageTable.rows().every(function () {
|
||||
const data = this.data();
|
||||
const saleDate = data[3].split(' ')[0]; // extract YYYY-MM-DD from date
|
||||
const store = data[6];
|
||||
// DataTable mécanicien
|
||||
var mecTable = $('#mecperformance').DataTable({
|
||||
'ajax': baseUrl + 'mecanicien/fetchMecanicienPerformances',
|
||||
'order': [],
|
||||
'pageLength': 10
|
||||
});
|
||||
|
||||
// Filter logic
|
||||
const dateMatch = (!startDate && !endDate) ||
|
||||
(startDate && endDate && saleDate >= startDate && saleDate <= endDate) ||
|
||||
(startDate && !endDate && saleDate >= startDate) ||
|
||||
(!startDate && endDate && saleDate <= endDate);
|
||||
// Filtre mécanicien - côté serveur
|
||||
$('#filteredB2').on('click', function () {
|
||||
var startDate = $('#startDate2').val();
|
||||
var endDate = $('#endDate2').val();
|
||||
var mecanic = $('#mecanicienFilter').val();
|
||||
|
||||
const storeMatch = (pvente === 'TOUS' || pvente === store);
|
||||
|
||||
if (dateMatch && storeMatch) {
|
||||
filteredData.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear and reload table with filtered data
|
||||
manageTable.clear().rows.add(filteredData).draw();
|
||||
});
|
||||
var url = baseUrl + 'mecanicien/fetchMecanicienPerformances?startDate=' + startDate + '&endDate=' + endDate + '&mecanic_id=' + (mecanic === 'TOUS' ? '' : mecanic);
|
||||
mecTable.ajax.url(url).load();
|
||||
});
|
||||
});
|
||||
|
||||
// Export commercial
|
||||
document.getElementById('ExportBTN1').addEventListener('click', function () {
|
||||
// Select your table
|
||||
var table = document.getElementById('commperformance');
|
||||
|
||||
// Convert it to a workbook
|
||||
var wb = XLSX.utils.table_to_book(table, {
|
||||
sheet: "Feuille1"
|
||||
});
|
||||
|
||||
// Export it
|
||||
var wb = XLSX.utils.table_to_book(document.getElementById('commperformance'), { sheet: "Commercial" });
|
||||
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>
|
||||
@ -57,9 +57,17 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 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">
|
||||
<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">
|
||||
<option value="TOUS">TOUS</option>
|
||||
<?php foreach ($stores as $value) { ?>
|
||||
@ -67,9 +75,9 @@
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<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>
|
||||
|
||||
@ -188,7 +196,7 @@
|
||||
|
||||
|
||||
manageTable = $('#venteTable').DataTable({
|
||||
'ajax': 'fetctData/' + 0,
|
||||
'ajax': 'fetchData/' + 0,
|
||||
'order': [],
|
||||
'pageLength': 5, // Set default rows per page
|
||||
'lengthMenu': [
|
||||
@ -198,7 +206,7 @@
|
||||
});
|
||||
|
||||
manageTable2 = $('#stockTable').DataTable({
|
||||
'ajax': 'fetctDataStock/' + 0,
|
||||
'ajax': 'fetchDataStock/' + 0,
|
||||
'order': [],
|
||||
'pageLength': 5, // Set default rows per page
|
||||
'lengthMenu': [
|
||||
@ -209,7 +217,7 @@
|
||||
|
||||
manageTable3 = $('#export1').DataTable({
|
||||
ajax: {
|
||||
url: 'fetctDataStock2/' + 0,
|
||||
url: 'fetchDataStock2/' + 0,
|
||||
dataSrc: 'data'
|
||||
},
|
||||
order: [],
|
||||
@ -226,8 +234,8 @@
|
||||
let filterValue = filterBtn.value === "TOUS" ? "0" : filterBtn.value;
|
||||
|
||||
// Update the DataTable dynamically without reinitialization
|
||||
manageTable.ajax.url('fetctData/' + filterValue).load();
|
||||
manageTable2.ajax.url('fetctDataStock/' + filterValue).load();
|
||||
manageTable.ajax.url('fetchData/' + filterValue).load();
|
||||
manageTable2.ajax.url('fetchDataStock/' + filterValue).load();
|
||||
});
|
||||
|
||||
let productsSold = <?= $ventes ?>;
|
||||
@ -249,6 +257,14 @@
|
||||
// Trigger the filter on button click
|
||||
$('#filterBtn').click(function () {
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>UGS</th>
|
||||
<th>N° SERIE </th>
|
||||
<th>Désignation</th>
|
||||
<th>Statut</th>
|
||||
<th>Action</th>
|
||||
@ -272,7 +272,7 @@
|
||||
<th>Image</th>
|
||||
<th>N° Facture</th>
|
||||
<th>Désignation</th>
|
||||
<th>UGS</th>
|
||||
<th>N° SERIE </th>
|
||||
<th>Marque</th>
|
||||
<th>Client</th>
|
||||
<th>Magasin</th>
|
||||
|
||||
@ -278,6 +278,7 @@ body {
|
||||
transition: width 0.3s ease;
|
||||
box-shadow: 2px 0 20px rgba(0, 0, 0, 0.3);
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.main-sidebar .logo {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user