feat: refonte gestion commandes, sécurité, impressions et notifications

## Gestion Moto & Commandes
- Correction des notifications pour la Caissière
- Le bouton "Enregistrer" changé en "Payer"
- La moto commandée reste visible dans la liste jusqu'à livraison par la sécurité

## Espace Sécurité
- Ajout des notifications de livraison
- Transfert vers l'espace commande après livraison

## Espace SuperAdmin
- Rejet de commande : le produit redevient disponible en stock automatiquement
- Correction de la gestion des rôles (permissions inversées)
- Avance complète : s'affiche directement chez la Caissière

## Historique des Actions
- Ajout de l'historique des actions pour SuperAdmin (traçabilité)

## Dashboard
- Filtre par date ajouté (par défaut : aujourd'hui)
- Affichage uniquement des données du site concerné

## Espace Commercial
- Liste des produits disponibles sur la liste déroulante dans l'ajout des commandes
- Le bouton "+" se cache après le premier clic pour les clients particuliers

## Impression Documents
- Refonte facture, bon de livraison, facture d'acompte (QR codes, infos dynamiques)

## Sidebar
- Correction des animations et du logo dynamique

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
andrymodeste 2026-03-31 07:28:05 +02:00
parent a18ccbf34b
commit a195d24e78
32 changed files with 2152 additions and 1033 deletions

View File

@ -49,6 +49,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
* dashboard route
*/
$routes->get('/', [Dashboard::class, 'index']);
$routes->get('/dashboard/getTresorerieData', [Dashboard::class, 'getTresorerieData']);
$routes->get('/ventes', [Auth::class, 'ventes']);
$routes->get('/ventes/(:num)', [Auth::class, 'addImage']);
$routes->get('/ventes/fetchProductVente/(:num)', [Auth::class, 'fetchProductVente']);
@ -388,6 +389,13 @@ $routes->group('avances', function ($routes) {
$routes->post('validateAvance', [AvanceController::class, 'validateAvance']);
});
// Historique des actions (SuperAdmin)
$routes->group('action-log', ['filter' => 'auth'], static function ($routes) {
$routes->get('/', 'ActionLogController::index');
$routes->get('fetchData', 'ActionLogController::fetchData');
$routes->get('export', 'ActionLogController::export');
});
// historique
$routes->group('historique', ['filter' => 'auth'], static function ($routes) {
$routes->get('/', 'HistoriqueController::index');

View File

@ -0,0 +1,154 @@
<?php
namespace App\Controllers;
use App\Models\Historique;
use App\Models\Stores;
class ActionLogController extends AdminController
{
private $pageTitle = 'Historique des Actions';
public function __construct()
{
parent::__construct();
helper(['form', 'url']);
}
public function index()
{
$session = session();
$user = $session->get('user');
if ($user['group_name'] !== 'SuperAdmin') {
return redirect()->to('/');
}
$storesModel = new Stores();
$data['page_title'] = $this->pageTitle;
$data['stores'] = $storesModel->getActiveStore();
return $this->render_template('action_log/index', $data);
}
public function fetchData()
{
$session = session();
$user = $session->get('user');
if ($user['group_name'] !== 'SuperAdmin') {
return $this->response->setJSON(['data' => []]);
}
$historiqueModel = new Historique();
$filters = [
'action' => $this->request->getGet('action'),
'store_name' => $this->request->getGet('store_name'),
'product_name' => $this->request->getGet('product'),
'sku' => $this->request->getGet('sku'),
'date_from' => $this->request->getGet('date_from'),
'date_to' => $this->request->getGet('date_to'),
];
$allData = $historiqueModel->getHistoriqueWithFilters($filters);
$result = ['data' => []];
foreach ($allData as $row) {
$result['data'][] = [
date('d/m/Y H:i', strtotime($row['created_at'])),
$row['user_name'] ?? '<em>Système</em>',
$this->getActionBadge($row['action']),
$this->getTableLabel($row['table_name']),
$row['description'] ?? '',
];
}
return $this->response->setJSON($result);
}
private function getActionBadge($action)
{
$badges = [
'CREATE' => '<span class="label label-success">Création</span>',
'UPDATE' => '<span class="label label-warning">Modification</span>',
'DELETE' => '<span class="label label-danger">Suppression</span>',
'PAYMENT' => '<span class="label label-primary">Paiement</span>',
'VALIDATE' => '<span class="label label-info">Validation</span>',
'REFUSE' => '<span class="label label-danger">Refus</span>',
'DELIVERY' => '<span class="label label-success">Livraison</span>',
'ASSIGN_STORE' => '<span class="label label-info">Assignation</span>',
'ENTRER' => '<span class="label label-primary">Entrée</span>',
'SORTIE' => '<span class="label label-default">Sortie</span>',
'IMPORT' => '<span class="label label-success">Import</span>',
'LOGIN' => '<span class="label label-default">Connexion</span>',
];
return $badges[$action] ?? '<span class="label label-secondary">' . $action . '</span>';
}
private function getTableLabel($tableName)
{
$labels = [
'orders' => 'Commande',
'products' => 'Produit',
'users' => 'Utilisateur',
'groups' => 'Rôle',
'avances' => 'Avance',
'securite' => 'Sécurité',
'remise' => 'Remise',
'sortie_caisse' => 'Décaissement',
'autres_encaissements' => 'Encaissement',
'recouvrement' => 'Recouvrement',
'stores' => 'Point de vente',
'brands' => 'Marque',
'categories' => 'Catégorie',
];
return $labels[$tableName] ?? $tableName;
}
public function export()
{
$session = session();
$user = $session->get('user');
if ($user['group_name'] !== 'SuperAdmin') {
return redirect()->to('/');
}
$historiqueModel = new Historique();
$filters = [
'action' => $this->request->getGet('action'),
'store_name' => $this->request->getGet('store_name'),
'date_from' => $this->request->getGet('date_from'),
'date_to' => $this->request->getGet('date_to'),
];
$data = $historiqueModel->getHistoriqueWithFilters($filters);
$csv = "\xEF\xBB\xBF"; // BOM UTF-8 pour Excel
$csv .= "Date;Heure;Utilisateur;Action;Module;Description\n";
foreach ($data as $row) {
$date = date('d-m-Y', strtotime($row['created_at']));
$heure = date('H:i', strtotime($row['created_at']));
$userName = $row['user_name'] ?? 'Système';
$action = $row['action'];
$module = $this->getTableLabel($row['table_name']);
$description = str_replace('"', '""', $row['description'] ?? '');
$csv .= "{$date};{$heure};{$userName};{$action};{$module};\"{$description}\"\n";
}
$filename = 'historique_actions_' . date('Y-m-d_H-i') . '.csv';
return $this->response
->setHeader('Content-Type', 'text/csv; charset=utf-8')
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
->setBody($csv);
}
}

View File

@ -44,6 +44,13 @@ abstract class AdminController extends BaseController
protected function render_template($page = null, $data = [])
{
$data['user_permission'] = $this->permission;
// Charger le logo dynamiquement
if (!isset($data['company_logo'])) {
$companyModel = new Company();
$companyData = $companyModel->getCompanyData(1);
$data['company_logo'] = $companyData['logo'] ?? 'assets/images/company_logo.jpg';
$data['company_name'] = $companyData['company_name'] ?? 'MotorBike';
}
echo view('templates/header', $data);
echo view('templates/header_menu', $data);
echo view('templates/side_menubar', $data);
@ -51,6 +58,14 @@ abstract class AdminController extends BaseController
echo view('templates/footer', $data);
}
// Get company logo path
protected function getCompanyLogo()
{
$model = new Company();
$data = $model->getCompanyData(1);
return $data['logo'] ?? 'assets/images/company_logo.jpg';
}
// Get company currency using model
public function company_currency()
{

View File

@ -6,6 +6,7 @@ use App\Models\ProductImage;
use App\Models\Users;
use App\Models\Stores;
use App\Models\Products;
use App\Models\Historique;
class Auth extends AdminController
{
@ -79,6 +80,10 @@ public function loginPost()
'logged_in' => true
]);
// Log connexion
$historique = new Historique();
$historique->logAction('users', 'LOGIN', $user['id'], "Connexion de {$user['firstname']} {$user['lastname']} ({$user['group_name']})");
// Redirect to dashboard
return redirect()->to('/');
}

View File

@ -5,6 +5,7 @@ namespace App\Controllers;
use App\Models\AutresEncaissements;
use App\Models\Stores;
use App\Models\Users;
use App\Models\Historique;
class AutresEncaissementsController extends AdminController
{
@ -106,7 +107,11 @@ class AutresEncaissementsController extends AdminController
$Notification->notifyGroupsByPermissionAllStores('notifEncaissement', $notificationMessage, 'encaissements');
log_message('info', "✅ Encaissement {$encaissementId} créé - Notifications envoyées");
// Log de l'action
$historique = new Historique();
$historique->logAction('autres_encaissements', 'CREATE', $encaissementId, "Encaissement {$finalType} - Montant: {$montantFormate} Ar");
return $this->response->setJSON([
'success' => true,
'messages' => 'Encaissement enregistré avec succès'

View File

@ -195,7 +195,72 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
public function fetchAvanceBecameOrder()
{
return $this->fetchAvanceDataGeneric('getCompletedAvances');
helper(['url', 'form']);
$Avance = new Avance();
$product = new Products();
$result = ['data' => []];
$data = $Avance->getCompletedAvances();
$session = session();
$users = $session->get('user');
$isAdmin = $this->isAdmin($users);
$isCommerciale = $this->isCommerciale($users);
$isCaissier = $this->isCaissier($users);
foreach ($data as $key => $value) {
$isOwner = $users['id'] === $value['user_id'];
$buttons = $this->buildActionButtons(
$value,
$isAdmin,
$isOwner,
$isCaissier,
$isCommerciale
);
// Déterminer le statut
if ($value['is_order'] == 1) {
$status = '<span class="label label-success"><i class="fa fa-check"></i> Payé</span>';
} else {
$status = '<span class="label label-warning"><i class="fa fa-clock-o"></i> En attente de paiement</span>';
}
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
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);
}
if ($isAdmin) {
$result['data'][] = [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$productName,
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
$status,
$date_time,
$buttons,
];
} elseif ($isCommerciale || $isCaissier) {
$result['data'][] = [
$value['avance_id'],
$productName,
number_format((int)$value['avance_amount'], 0, ',', ' '),
$status,
$date_time,
$buttons,
];
}
}
return $this->response->setJSON($result);
}
public function fetchExpiredAvance()
@ -998,24 +1063,29 @@ public function printInvoice($avance_id)
return redirect()->back()->with('error', 'Accès non autorisé');
}
// ✅ CORRECTION SIMPLIFIÉE
// Récupérer les données de l'entreprise
$companyModel = new Company();
$companyData = $companyModel->getCompanyData(1);
// Récupérer les détails du produit
$product = null;
$brandName = '';
if ($avance['type_avance'] === 'mere' && !empty($avance['product_name'])) {
$productName = $avance['product_name'];
$productDetails = [
'marque' => $avance['product_name'],
'numero_moteur' => '',
'puissance' => ''
'puissance' => '',
'couleur' => '',
'chasis' => '',
'type_avance' => 'mere',
];
} else {
$product = $Products->find($avance['product_id']);
if (!$product) {
return redirect()->back()->with('error', 'Produit non trouvé');
}
$productName = $product['name'] ?? 'N/A';
// ✅ Récupérer le nom de la marque
$brandName = 'N/A';
if (!empty($product['marque'])) {
$db = \Config\Database::connect();
@ -1028,15 +1098,18 @@ public function printInvoice($avance_id)
$brandName = $brandResult['name'];
}
}
$productDetails = [
'marque' => $brandName,
'numero_moteur' => $product['numero_de_moteur'] ?? '',
'puissance' => $product['puissance'] ?? ''
'puissance' => $product['puissance'] ?? '',
'couleur' => $product['cler'] ?? '',
'chasis' => $product['chasis'] ?? '',
'type_avance' => $avance['type_avance'] ?? 'terre',
];
}
$html = $this->generatePrintableInvoiceHTML($avance, $productName, $productDetails);
$html = $this->generatePrintableInvoiceHTML($avance, $productDetails, $companyData);
return $this->response->setBody($html);
@ -1821,27 +1894,42 @@ public function getFullInvoiceForPrint($avance_id)
/**
* Générer le HTML optimisé pour l'impression (version identique à printInvoice)
*/
private function generatePrintableInvoiceHTML($avance, $productName, $productDetails)
private function generatePrintableInvoiceHTML($avance, $productDetails, $companyData)
{
$avanceDate = date('d/m/Y', strtotime($avance['avance_date']));
$avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT);
$customerName = strtoupper(esc($avance['customer_name']));
$customerPhone = esc($avance['customer_phone']);
$customerCin = esc($avance['customer_cin']);
$customerPhone = esc($avance['customer_phone'] ?? '');
$grossAmount = number_format($avance['gross_amount'], 0, ',', ' ');
$avanceAmount = number_format($avance['avance_amount'], 0, ',', ' ');
$amountDue = number_format($avance['amount_due'], 0, ',', ' ');
$marque = esc($productDetails['marque']) ?: $productName;
$marque = esc($productDetails['marque']);
$numeroMoteur = esc($productDetails['numero_moteur']);
$puissance = esc($productDetails['puissance']);
$couleur = esc($productDetails['couleur'] ?? '');
$chasis = esc($productDetails['chasis'] ?? '');
$typeAvance = $productDetails['type_avance'] ?? 'terre';
$nSerieOuArrivage = ($typeAvance === 'mere') ? 'Arrivage' : ($chasis ?: $numeroMoteur);
$typePayment = esc($avance['type_payment'] ?? '');
$unite = '1';
// Company data
$companyName = esc($companyData['company_name'] ?? 'MOTORBIKE STORE');
$companyNIF = esc($companyData['NIF'] ?? '');
$companySTAT = esc($companyData['STAT'] ?? '');
$companyPhone = esc($companyData['phone'] ?? '');
$companyPhone2 = esc($companyData['phone2'] ?? '');
$companyAddress = esc($companyData['address'] ?? '');
$year = date('Y');
return <<<HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Facture Avance - KELY SCOOTERS</title>
<title>Facture d'Acompte - {$companyName}</title>
<style>
@media print {
body { margin: 0; padding: 0; }
@ -1849,281 +1937,352 @@ private function generatePrintableInvoiceHTML($avance, $productName, $productDet
.page { page-break-after: always; }
.page:last-child { page-break-after: auto; }
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
font-size: 12px;
line-height: 1.4;
font-size: 13px;
line-height: 1.6;
background: #fff;
}
.page {
width: 210mm;
height: 297mm;
padding: 15mm;
min-height: 297mm;
padding: 15mm 20mm;
margin: 0 auto;
background: white;
position: relative;
}
.invoice-container {
border: 2px solid #000;
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
/* === RECTO === */
.recto-title {
text-align: center;
border-bottom: 2px solid #000;
padding-bottom: 10px;
margin-bottom: 15px;
}
.header h1 {
font-size: 24px;
font-size: 20px;
font-weight: bold;
margin-bottom: 5px;
text-decoration: underline;
margin-bottom: 25px;
letter-spacing: 1px;
}
.header-info {
.recto-header {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 11px;
align-items: flex-start;
margin-bottom: 20px;
}
.header-left, .header-right {
text-align: left;
.recto-company {
font-size: 12px;
line-height: 1.5;
}
.invoice-title {
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px 0;
padding: 10px;
border: 1px solid #000;
.recto-company strong {
font-size: 14px;
}
.invoice-title h2 {
font-size: 18px;
.recto-date-qr {
text-align: right;
}
.recto-date-qr .date-block {
font-size: 13px;
margin-bottom: 10px;
}
.qr-code {
width: 100px;
height: 100px;
}
.watermark {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%) rotate(-25deg);
font-size: 60px;
color: rgba(0, 0, 0, 0.06);
font-weight: bold;
letter-spacing: 10px;
pointer-events: none;
z-index: 0;
}
.original-badge {
background: #000;
color: #fff;
padding: 5px 15px;
font-weight: bold;
transform: skewX(-10deg);
.recto-body {
position: relative;
z-index: 1;
}
.customer-info {
margin: 15px 0;
padding: 10px;
border: 1px solid #000;
.field-line {
margin: 12px 0;
font-size: 13px;
}
.customer-info div {
margin: 5px 0;
.field-line strong {
display: inline-block;
min-width: 180px;
}
.field-line .field-value {
border-bottom: 1px dotted #000;
display: inline-block;
min-width: 300px;
padding-bottom: 2px;
}
.field-indent {
margin-left: 30px;
}
.product-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
margin-top: 25px;
}
.product-table th, .product-table td {
border: 1px solid #000;
padding: 8px;
text-align: left;
border: 2px solid #000;
padding: 10px 12px;
text-align: center;
font-size: 13px;
}
.product-table th {
background: #f0f0f0;
font-weight: bold;
font-size: 12px;
}
.product-table td {
height: 40px;
}
/* === VERSO === */
.verso-header {
font-size: 12px;
line-height: 1.5;
margin-bottom: 30px;
}
.verso-header strong {
font-size: 14px;
}
.verso-title {
text-align: center;
font-size: 18px;
font-weight: bold;
text-decoration: underline;
margin-bottom: 25px;
}
.verso-intro {
margin-bottom: 15px;
font-size: 13px;
}
.article {
margin: 15px 0;
font-size: 13px;
}
.article-title {
font-weight: bold;
margin-bottom: 5px;
}
.article ul {
margin-left: 15px;
list-style: disc;
}
.article li {
margin: 3px 0;
}
.signature-section {
display: flex;
justify-content: space-between;
margin-top: auto;
padding: 20px 0;
margin-top: 50px;
}
.signature-box {
text-align: center;
width: 45%;
}
.signature-box p {
font-weight: bold;
margin-bottom: 50px;
}
/* Style spécifique pour le verso */
.contract-section {
margin-top: 20px;
padding: 15px;
width: 40%;
border: 2px solid #000;
font-size: 10px;
min-height: 80px;
padding: 10px;
}
.contract-section h3 {
text-align: center;
font-size: 14px;
margin-bottom: 10px;
text-decoration: underline;
}
.contract-article {
margin: 10px 0;
}
.contract-article strong {
text-decoration: underline;
}
.verso-content {
flex: 1;
display: flex;
flex-direction: column;
.signature-box .sig-title {
font-weight: bold;
font-size: 13px;
margin-bottom: 60px;
}
</style>
</head>
<body>
<!-- RECTO -->
<!-- ==================== RECTO ==================== -->
<div class="page">
<div class="invoice-container">
<!-- Header -->
<div class="header">
<h1>KELY SCOOTERS</h1>
<div class="header-info">
<div class="header-left">
<div>NIF: 401 840 5554</div>
<div>STAT: 46101 11 2024 00317</div>
</div>
<div class="header-right">
<div>Contact: +261 34 27 946 35 / +261 34 07 079 69</div>
<div>Antsakaviro en face WWF</div>
</div>
<div class="watermark">ORIGINAL</div>
<div class="recto-title">FACTURE D'ACOMPTE DE RESERVATION</div>
<div class="recto-header">
<div class="recto-company">
<strong>{$companyName}</strong><br>
NIF : {$companyNIF}<br>
STAT : {$companySTAT}<br>
Contact : {$companyPhone} / {$companyPhone2}<br>
{$companyAddress}
</div>
<div class="recto-date-qr">
<div class="date-block">
Date : <strong>{$avanceDate}</strong><br>
: <strong>{$avanceNumber}</strong>
</div>
<canvas id="qrcode" class="qr-code"></canvas>
</div>
<!-- Invoice Title -->
<div class="invoice-title">
<div>
<h2>FACTURE</h2>
<div>Date: {$avanceDate}</div>
<div>: {$avanceNumber}CI 2025</div>
</div>
<div class="original-badge">DOIT ORIGINAL</div>
</div>
<div class="recto-body">
<div class="field-line"><strong>DOIT</strong></div>
<div class="field-line">
<strong>NOM :</strong>
<span class="field-value">{$customerName}</span>
</div>
<!-- Customer Info -->
<div class="customer-info">
<div><strong>NOM:</strong> {$customerName} ({$customerPhone})</div>
<div><strong>CIN:</strong> {$customerCin}</div>
<div><strong>PC:</strong> {$grossAmount} Ar</div>
<div><strong>AVANCE:</strong> {$avanceAmount} Ar</div>
<div><strong>RAP:</strong> {$amountDue} Ar</div>
<div class="field-line">
<strong>N de Série ou Arrivage :</strong>
<span class="field-value">{$nSerieOuArrivage}</span>
</div>
<!-- Product Table -->
<div class="field-line field-indent">
Si Arrivage, préciser la caractéristique de la moto : Couleur
<span class="field-value">{$couleur}</span>
</div>
<div class="field-line">
<strong>MOTO :</strong>
<span class="field-value">{$marque}</span>
</div>
<div class="field-line field-indent">
<strong>Unité :</strong>
<span class="field-value">{$unite}</span>
</div>
<div class="field-line field-indent">
<strong>Prix Unitaire :</strong>
<span class="field-value">{$grossAmount} Ar</span>
</div>
<div class="field-line">
<strong>Montant Total :</strong>
<span class="field-value">{$grossAmount} Ar</span>
</div>
<div class="field-line">
<strong>Mode de Paiement :</strong>
<span class="field-value">{$typePayment}</span>
</div>
<div class="field-line">
<strong>AVANCE :</strong>
<span class="field-value">{$avanceAmount} Ar</span>
</div>
<div class="field-line">
<strong>Reste à payer :</strong>
<span class="field-value">{$amountDue} Ar</span>
</div>
<!-- Tableau récapitulatif -->
<table class="product-table">
<thead>
<tr>
<th>MARQUE</th>
<th>N°MOTEUR</th>
<th> Châssis/Arrivage</th>
<th>PUISSANCE</th>
<th>RAP (Ariary)</th>
<th>Reste à payer (Ariary)</th>
</tr>
</thead>
<tbody>
<tr>
<td>{$marque}</td>
<td>{$numeroMoteur}</td>
<td>{$chasis}</td>
<td>{$puissance}</td>
<td>{$amountDue}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- VERSO -->
<div class="page">
<div class="invoice-container">
<div class="invoice-title">
<div>
<h2>CONDITIONS GÉNÉRALES</h2>
<div>Date: {$avanceDate}</div>
<div>: {$avanceNumber}CI 2025</div>
<div class="verso-content">
<!-- Contract Section -->
<div class="contract-section">
<h3>FIFANEKENA ARA-BAROTRA (Réservations)</h3>
<p><strong>Ry mpanjifa hajaina,</strong></p>
<p>Natao ity fifanekena ity mba hialana amin'ny fivadihana hampitokisana amin'ny andaniny sy ankilany.</p>
<div class="contract-article">
<strong>Andininy faha-1: FAMANDRAHANA SY FANDOAVAM-BOLA</strong>
<p>Ny mpividy dia manao famandrahana amin'ny alalan'ny fandoavambola mihoatra ny 25 isan-jato amin'ny vidin'entana rehetra (avances).</p>
</div>
<div class="contract-article">
<strong>Andininy faha-2: FANDOAVAM-BOLA REHEFA TONGA NY ENTANA (ARRIVAGE)</strong>
<p>Rehefa tonga ny moto/pieces dia tsy maintsy mandoa ny 50 isan-jato ny vidin'entana ny mpamandrika.</p>
<p>Manana 15 andro kosa adoavana ny 25 isan-jato raha misy tsy fahafahana alohan'ny famoahana ny entana.</p>
</div>
<div class="contract-article">
<strong>Andininy faha-3: FAMERENANA VOLA</strong>
<p>Raha toa ka misy antony tsy hakana ny entana indray dia tsy mamerina ny vola efa voaloha (avance) ny société.</p>
</div>
<div class="contract-article">
<strong>Andininy faha-4: FEPETRA FANAMPINY</strong>
<ul style="margin-left: 20px;">
<li>Tsy misafidy raha toa ka mamafa no ifanarahana.</li>
<li>Tsy azo atao ny mamerina ny entana efa nofandrahana.</li>
<li>Tsy azo atao ny manakalo ny entana efa nofandrahana.</li>
</ul>
</div>
</div>
<!-- Additional space for notes -->
<div style="margin-top: 20px; padding: 10px; border: 1px solid #000; flex: 1;">
<strong>OBSERVATIONS / NOTES:</strong>
<div style="height: 100px; margin-top: 10px;"></div>
</div>
<!-- Signatures for verso -->
<div class="signature-section">
<div class="signature-box">
<p>NY MPAMANDRIKA</p>
<div style="border-top: 1px solid #000; padding-top: 5px;">Signature</div>
</div>
<div class="signature-box">
<p>NY MPIVAROTRA</p>
<div style="border-top: 1px solid #000; padding-top: 5px;">
<strong>KELY SCOOTERS</strong><br>
NIF: 401 840 5554
</div>
</div>
</div>
<!-- ==================== VERSO ==================== -->
<div class="page">
<div class="verso-header">
<strong>{$companyName}</strong><br>
NIF : {$companyNIF}<br>
STAT : {$companySTAT}<br>
Contact : {$companyPhone} / {$companyPhone2}<br>
{$companyAddress}
</div>
<div class="verso-title">FIFANEKENA ARA-BAROTRA (Reservations)</div>
<div class="verso-intro">
Ry mpanjifa hajaina,<br><br>
Natao ity fifanekena ity mba hialana amin'ny fivadiahampitokisana amin'ny andaniny sy ankilany.
</div>
<div class="article">
<div class="article-title">&bull; Andininy faha-1 : FAMANDRIHANA SY FANDOAVAM-BOLA</div>
<p>Ny mpividy dia manao famandrihana amin'ny alalan'ny fandoavam-bola mihoatra ny 25 isan-jato amin'ny vidin'entana rehetra (avances).</p>
</div>
<div class="article">
<div class="article-title">&bull; Andininy faha-2 : FANDOAVAM-BOLA REHEFA TONGA NY ENTANA (ARRIVAGE)</div>
<p>Rehefa tonga ny moto/pieces dia tsy maintsy mandoha ny 50 isan-jato ny vidin'entana ny mpamandrika.</p>
<p>Manana 15 andro kosa andoaovana ny 25 isan-jato raha misy tsy fahafahana alohan'ny famoahana ny entana.</p>
</div>
<div class="article">
<div class="article-title">&bull; Andininy faha-3 : FAMERENANA VOLA</div>
<p>Raha toa ka misy antony tsy hakana ny entana indray dia tsy mamerina ny vola efa voaloha (avance) ny société.</p>
</div>
<div class="article">
<div class="article-title">&bull; Andininy faha-4 : FEPETRA FANAMPINY</div>
<ul>
<li>Tsy misafidy raha toa ka mamafa no ifanarahana.</li>
<li>Tsy azo atao ny mamerina vola efa naloha.</li>
<li>Tsy azo atao ny manakalo ny entana efa nofandrihana.</li>
</ul>
</div>
<!-- Signatures -->
<div class="signature-section">
<div class="signature-box">
<div class="sig-title">NY MPAMANDRIKA</div>
</div>
<div class="signature-box" style="text-align: right;">
<div class="sig-title">NY MPIVAROTRA</div>
</div>
</div>
</div>
<!-- QR Code JS -->
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
<script>
new QRious({
element: document.getElementById('qrcode'),
value: "FACTURE D'ACOMPTE DE RESERVATION\\nN° {$avanceNumber}\\nClient: {$customerName}\\nMontant Total: {$grossAmount} Ar\\nAvance: {$avanceAmount} Ar\\nReste: {$amountDue} Ar\\nDate: {$avanceDate}\\nFacebook: https://www.facebook.com/MOTORBIKESTORE2021/",
size: 100,
level: 'H'
});
window.onload = function() { window.print(); };
</script>
</body>
</html>
HTML;

View File

@ -44,7 +44,15 @@ class CompanyController extends AdminController
'message' => $this->request->getPost('message'),
'currency' => $this->request->getPost('currency'),
];
// Upload du logo
$logoFile = $this->request->getFile('logo');
if ($logoFile && $logoFile->isValid() && !$logoFile->hasMoved()) {
$newName = 'company_logo.' . $logoFile->getExtension();
$logoFile->move(FCPATH . 'assets/images/', $newName, true);
$data['logo'] = 'assets/images/' . $newName;
}
if ($Company->updateCompany($data, 1)) {
session()->setFlashdata('success', 'Successfully updated');
return redirect()->to('/company');

View File

@ -284,4 +284,89 @@ class Dashboard extends AdminController
return $this->render_template('dashboard', $data);
}
/**
* AJAX : Recalculer la trésorerie avec filtre de dates
*/
public function getTresorerieData()
{
$session = session();
$user_id = $session->get('user');
$startDate = $this->request->getGet('start_date');
$endDate = $this->request->getGet('end_date');
$orderModel = new Orders();
$Avance = new Avance();
$sortieCaisse = new SortieCaisse();
$Recouvrement = new Recouvrement();
$autresEncaissementsModel = new AutresEncaissements();
$isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction', 'SuperAdmin']);
$storeIdFilter = $isAdmin ? null : $user_id['store_id'];
// Données avec filtre de dates
$paymentData = $orderModel->getPaymentModes($startDate, $endDate);
$totalAvance = $Avance->getTotalAvance($startDate, $endDate);
$paymentDataAvance = $Avance->getPaymentModesAvance($startDate, $endDate);
$totalRecouvrement = $Recouvrement->getTotalRecouvrements(null, $startDate, $endDate);
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse($startDate, $endDate);
$totauxAutresEncaissements = $autresEncaissementsModel->getTotalEncaissementsByMode($storeIdFilter, $startDate, $endDate);
// Autres encaissements
$total_autres_enc_espece = $totauxAutresEncaissements['total_espece'];
$total_autres_enc_mvola = $totauxAutresEncaissements['total_mvola'];
$total_autres_enc_virement = $totauxAutresEncaissements['total_virement'];
$total_autres_encaissements = $total_autres_enc_espece + $total_autres_enc_mvola + $total_autres_enc_virement;
// Sorties
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
// Recouvrements
$me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
$bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
$be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
$mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
// Orders
$total_orders = isset($paymentData->total) ? (float) $paymentData->total : 0;
$mv1_orders = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0;
$mv2_orders = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0;
$es1_orders = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0;
$es2_orders = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0;
$vb1_orders = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0;
$vb2_orders = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0;
// Avances
$total_avances = isset($paymentDataAvance->total) ? (float) $paymentDataAvance->total : 0;
$mv_avances = isset($paymentDataAvance->total_mvola) ? (float) $paymentDataAvance->total_mvola : 0;
$es_avances = isset($paymentDataAvance->total_espece) ? (float) $paymentDataAvance->total_espece : 0;
$vb_avances = isset($paymentDataAvance->total_virement_bancaire) ? (float) $paymentDataAvance->total_virement_bancaire : 0;
// Brut
$total_mvola_brut = $mv1_orders + $mv2_orders + $mv_avances + $total_autres_enc_mvola;
$total_espece_brut = $es1_orders + $es2_orders + $es_avances + $total_autres_enc_espece;
$total_vb_brut = $vb1_orders + $vb2_orders + $vb_avances + $total_autres_enc_virement;
$total_brut = $total_orders + $total_avances + $total_autres_encaissements;
// Final
$total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement;
$total_mvola_final = $total_mvola_brut - $me - $mb + $bm - $total_sortie_mvola;
$total_espece_final = $total_espece_brut + $me + $be - $total_sortie_espece;
$total_vb_final = $total_vb_brut - $be - $bm + $mb - $total_sortie_virement;
$total_final = $total_brut - $total_sortie_global;
return $this->response->setJSON([
'total_brut' => number_format($total_brut, 0, '.', ' '),
'total_sorties' => number_format($total_sortie_global, 0, '.', ' '),
'total_net' => number_format($total_final, 0, '.', ' '),
'espece' => number_format($total_espece_final, 0, '.', ' '),
'mvola' => number_format($total_mvola_final, 0, '.', ' '),
'banque' => number_format($total_vb_final, 0, '.', ' '),
'avance_mvola' => number_format($mv_avances, 0, '.', ' '),
'avance_espece' => number_format($es_avances, 0, '.', ' '),
'avance_banque' => number_format($vb_avances, 0, '.', ' '),
]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,11 @@ namespace App\Controllers;
use App\Controllers\AdminController;
use App\Models\Notification;
use App\Models\Orders;
use App\Models\OrderItems;
use App\Models\Products;
use App\Models\Remise;
use App\Models\Stores;
use App\Models\Historique;
class RemiseController extends AdminController
{
@ -149,6 +152,16 @@ class RemiseController extends AdminController
if ($demande_status == 'Refusé') {
if ($order_id) {
$ordersModel->update($order_id, ['paid_status' => 0]);
// Libérer les produits de la commande (retour en stock)
$orderItemsModel = new OrderItems();
$productModel = new Products();
$orderItems = $orderItemsModel->getOrdersItemData($order_id);
foreach ($orderItems as $item) {
if (!empty($item['product_id'])) {
$productModel->update($item['product_id'], ['product_sold' => 0]);
}
}
}
// Message de refus
@ -163,7 +176,11 @@ class RemiseController extends AdminController
$Notification->notifyGroupsByPermissionAllStores('notifRemise', $messageRefus . "<br><em>Pour information</em>", 'remise/');
$message = 'La demande de remise a été refusée. Le commercial et les responsables ont été notifiés.';
// Log refus remise
$historique = new Historique();
$historique->logAction('remise', 'REFUSE', $id_demande, "Refus de la demande de remise - Facture: {$bill_no}");
} elseif ($demande_status == 'Accepté' || $demande_status == 'Validé') {
// ✅ SI ACCEPTÉ PAR LE SUPERADMIN
if ($order_id) {
@ -184,6 +201,10 @@ class RemiseController extends AdminController
$Notification->notifyGroupsByPermissionAllStores('notifRemise', $messageAcceptation . "<br><em>Pour information</em>", 'remise/');
$message = 'La demande de remise a été acceptée. La caissière, le commercial et les responsables ont été notifiés.';
// Log validation remise
$historique = new Historique();
$historique->logAction('remise', 'VALIDATE', $id_demande, "Validation de la demande de remise - Facture: {$bill_no}");
}
return $this->response->setJSON([

View File

@ -6,6 +6,7 @@ use App\Models\Securite;
use App\Models\Products;
use App\Models\Orders;
use App\Models\Stores;
use App\Models\Historique;
class SecuriteController extends AdminController
{
@ -48,7 +49,7 @@ class SecuriteController extends AdminController
// Bouton daction
$buttons = in_array('validateCommande1', $this->permission)
? '<button type="button" class="btn btn-success" onclick="editFunc(' . $securite['id'] . ')" data-toggle="modal" data-target="#editModal"><i class="fa fa-check"></i></button>'
? '<button type="button" class="btn btn-success" onclick="editFunc(' . $securite['id'] . ')"><i class="fa fa-check"></i></button>'
: '';
// Statut
@ -102,7 +103,7 @@ class SecuriteController extends AdminController
public function update(int $id)
{
$this->verifyRole('updateCommande1');
$storeModel = new Securite();
$securiteModel = new Securite();
$post = $this->request->getPost();
$response = [];
@ -113,12 +114,40 @@ class SecuriteController extends AdminController
];
$session = session();
$users = $session->get('user');
$Notification = new NotificationController();
if ($storeModel->updateSecurite($data, $id)) {
$Notification = new NotificationController();
if ($securiteModel->updateSecurite($data, $id)) {
if ($post['status'] === "Validé") {
$Notification->notifyGroupsByPermission('notifRemise', 'Une commande a été validé', (int)$users['store_id'], 'orders');
// ✅ Récupérer les infos de la ligne securite
$securiteData = $securiteModel->getSecuriteData($id);
if ($securiteData) {
// ✅ Marquer le produit comme vendu (product_sold = 1)
$productModel = new Products();
$productModel->update($securiteData['product_id'], ['product_sold' => 1]);
// ✅ Mettre à jour la commande liée (paid_status = 3 = livré)
if (!empty($securiteData['bill_no'])) {
$orderModel = new Orders();
$order = $orderModel->getOrdersDataByBillNo($securiteData['bill_no']);
if ($order) {
$orderModel->update($order['id'], [
'paid_status' => 3,
'delivered_by' => $users['id'],
'delivered_at' => date('Y-m-d H:i:s')
]);
}
}
}
$Notification->notifyGroupsByPermission('notifRemise', 'Une commande a été validée et livrée', (int)$users['store_id'], 'orders');
}
$response = ['success' => true, 'messages' => 'Mise à jour réussie'];
// Log de l'action livraison
$historique = new Historique();
$billNo = $securiteData['bill_no'] ?? 'N/A';
$historique->logAction('securite', 'DELIVERY', $id, "Confirmation de livraison - Facture: {$billNo}");
$response = ['success' => true, 'messages' => 'Livraison confirmée avec succès'];
} else {
$response = ['success' => false, 'messages' => 'Erreur en base lors de la mise à jour'];
}

View File

@ -189,20 +189,26 @@ class Avance extends Model {
}
// ✅ CORRECTION : getTotalAvance pour la caissière
public function getTotalAvance() {
public function getTotalAvance($startDate = null, $endDate = null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
try {
$builder = $this->select('SUM(avance_amount) AS ta')
->where('is_order', 0)
->where('active', 1); // ✅ Ajout du filtre active
->where('active', 1);
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']); // ✅ Filtre par store pour caissière
$builder->where('store_id', $users['store_id']);
}
if ($startDate) {
$builder->where('DATE(avance_date) >=', $startDate);
}
if ($endDate) {
$builder->where('DATE(avance_date) <=', $endDate);
}
return $builder->get()->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
@ -212,7 +218,7 @@ class Avance extends Model {
// ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière
// ✅ MODIFICATION : Ne compter QUE les avances VALIDÉES
public function getPaymentModesAvance()
public function getPaymentModesAvance($startDate = null, $endDate = null)
{
$session = session();
$users = $session->get('user');
@ -226,14 +232,19 @@ public function getPaymentModesAvance()
SUM(CASE WHEN LOWER(type_payment) = "en espèce" THEN avance_amount ELSE 0 END) AS total_espece,
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
')
->where('validated', 1) // ✅ AJOUT : Uniquement les avances validées
->where('validated', 1)
->where('active', 1)
->where('is_order', 0);
// ✅ Filtre par store pour non-admin
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
if ($startDate) {
$builder->where('DATE(avance_date) >=', $startDate);
}
if ($endDate) {
$builder->where('DATE(avance_date) <=', $endDate);
}
$result = $builder->get()->getRowObject();
@ -442,9 +453,11 @@ public function getPaymentModesAvance()
$isCommercial = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissière']);
$builder = $this->where('is_order', 0)
->where('active', 1)
->where('amount_due', 0);
$builder = $this->where('amount_due', 0)
->groupStart()
->where('active', 1)
->orWhere('is_order', 1)
->groupEnd();
if ($isCommercial) {
$builder->where('user_id', $users['id']);

View File

@ -13,7 +13,7 @@ class Company extends Model
protected $table = 'company';
// List all the fields that are allowed to be updated or inserted
protected $allowedFields = [
'company_name', 'service_charge_value', 'vat_charge_value', 'address', 'phone', 'phone2', 'NIF', 'STAT', 'country', 'message', 'currency',
'company_name', 'service_charge_value', 'vat_charge_value', 'address', 'phone', 'phone2', 'NIF', 'STAT', 'country', 'message', 'currency', 'logo',
];
/**

View File

@ -16,6 +16,8 @@ class Historique extends Model
'sku',
'store_name',
'description',
'user_id',
'user_name',
'created_at'
];
protected $useTimestamps = false;
@ -111,6 +113,32 @@ class Historique extends Model
return $this->insert($data);
}
/**
* Enregistrer une action utilisateur dans l'historique
*/
public function logAction($tableName, $action, $rowId, $description)
{
$session = session();
$user = $session->get('user');
$data = [
'table_name' => $tableName,
'action' => $action,
'row_id' => $rowId,
'product_name' => null,
'sku' => null,
'store_name' => $user['store_name'] ?? null,
'description' => $description,
'user_id' => $user['id'] ?? null,
'user_name' => isset($user['firstname'], $user['lastname'])
? $user['firstname'] . ' ' . $user['lastname']
: ($user['username'] ?? 'Système'),
'created_at' => date('Y-m-d H:i:s')
];
return $this->insert($data);
}
/**
* Nettoyer l'historique ancien (plus de X jours)
*/

View File

@ -387,12 +387,12 @@ class Orders extends Model
->findAll();
}
public function getPaymentModes()
public function getPaymentModes($startDate = null, $endDate = null)
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$baseQuery = $this->db->table('orders')
->select('
@ -462,12 +462,19 @@ class Orders extends Model
ELSE 0
END) AS total_virement_bancaire2
')
->whereIn('orders.paid_status', [1, 3]);
->whereIn('orders.paid_status', [1, 3]);
if (!$isAdmin) {
$baseQuery->where('orders.store_id', $users['store_id']);
}
if ($startDate) {
$baseQuery->where('DATE(orders.date_time) >=', $startDate);
}
if ($endDate) {
$baseQuery->where('DATE(orders.date_time) <=', $endDate);
}
return $baseQuery->get()->getRowObject();
}

View File

@ -21,8 +21,8 @@ class Products extends Model
$isAdmin = in_array($user['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$builder = $this->where('is_piece', 0)
->where('product_sold', 0);
->whereIn('product_sold', [0, 2]);
if (!$isAdmin) {
if (empty($user['store_id']) || $user['store_id'] == 0) {
$builder->where('id', -1);
@ -58,8 +58,7 @@ class Products extends Model
return $builder->join('brands', 'brands.id = products.marque')
->orderBy('products.id', 'DESC')
->select('brands.name as brand_name,COUNT( products.id) as total_product, products.store_id as store_id,products.*')
->groupBy('store_id')
->select('brands.name as brand_name, products.store_id as store_id, products.*')
->findAll();
}
@ -75,7 +74,7 @@ class Products extends Model
public function getProductDataStore(int $store_id, bool $excludeAvance = true, int $currentProductId = null)
{
$builder = $this->where('is_piece', 0)
->where('product_sold', 0)
->whereIn('product_sold', [0, 2])
->where('availability', 1)
->where('store_id', $store_id);
@ -92,13 +91,14 @@ class Products extends Model
$builder->where("id NOT IN ($subQueryAvances)", null, false);
}
// ✅ LISTE : Exclure TOUS les produits ayant une commande (statuts 1, 2, 3)
// ✅ LISTE : Exclure uniquement les produits dont la commande est LIVRÉE (paid_status = 3)
// Les produits commandés mais non livrés restent visibles dans les produits disponibles
$subQueryOrders = $db->table('orders_item')
->select('orders_item.product_id')
->join('orders', 'orders.id = orders_item.order_id')
->whereIn('orders.paid_status', [1, 2, 3]) // ✅ Disparaît de la liste dès qu'il y a une commande
->where('orders.paid_status', 3)
->getCompiledSelect();
$builder->where("id NOT IN ($subQueryOrders)", null, false);
// Exception pour le produit actuel lors de modification de commande

View File

@ -12,7 +12,7 @@ class Securite extends Model
*/
protected $table = 'securite';
protected $primaryKey = 'id'; // Primary key column
protected $allowedFields = ['product_id', 'status', 'date', 'active'];
protected $allowedFields = ['product_id', 'bill_no', 'store_id', 'status', 'date', 'active'];

View File

@ -165,25 +165,24 @@ class SortieCaisse extends Model
/**
* MODIFICATION : DAF, Direction, SuperAdmin voient TOUS les totaux
*/
public function getTotalSortieCaisse() {
public function getTotalSortieCaisse($startDate = null, $endDate = null) {
$session = session();
$users = $session->get('user');
// ✅ DAF, Direction, SuperAdmin : Voir TOUS les stores
$isAdmin = in_array($users['group_name'], ['Direction', 'DAF', 'SuperAdmin']);
if ($isAdmin) {
try {
return $this->select('
$builder = $this->select('
SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece,
SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola,
SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr
')
// ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider")
->where('statut', 'Payé')
->get()
->getRowObject();
->where('statut', 'Payé');
if ($startDate) { $builder->where('DATE(created_at) >=', $startDate); }
if ($endDate) { $builder->where('DATE(created_at) <=', $endDate); }
return $builder->get()->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur getTotalSortieCaisse (Admin) : ' . $e->getMessage());
return (object)[
@ -194,19 +193,18 @@ class SortieCaisse extends Model
];
}
} else {
// ✅ CAISSIÈRE : Uniquement son store ET statut "Payé"
try {
return $this->select('
$builder = $this->select('
SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece,
SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola,
SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr
')
->where('store_id', $users['store_id'])
// ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider")
->where('statut', 'Payé')
->get()
->getRowObject();
->where('statut', 'Payé');
if ($startDate) { $builder->where('DATE(created_at) >=', $startDate); }
if ($endDate) { $builder->where('DATE(created_at) <=', $endDate); }
return $builder->get()->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur getTotalSortieCaisse (Store) : ' . $e->getMessage());
return (object)[

View File

@ -0,0 +1,178 @@
<meta charset="UTF-8">
<div class="content-wrapper">
<section class="content-header">
<h1>
Historique des <small>Actions</small>
</h1>
<ol class="breadcrumb">
<li><a href="<?= base_url('/') ?>"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Historique des Actions</li>
</ol>
</section>
<section class="content">
<div id="messages"></div>
<!-- Filtres -->
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title"><i class="fa fa-filter"></i> Filtres</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
</div>
</div>
<div class="box-body">
<div class="row">
<div class="col-md-2">
<label>Date de debut</label>
<input type="date" id="filterDateFrom" class="form-control" value="<?= date('Y-m-d') ?>">
</div>
<div class="col-md-2">
<label>Date de fin</label>
<input type="date" id="filterDateTo" class="form-control" value="<?= date('Y-m-d') ?>">
</div>
<div class="col-md-2">
<label>Action</label>
<select id="filterAction" class="form-control">
<option value="">Toutes</option>
<option value="CREATE">Creation</option>
<option value="UPDATE">Modification</option>
<option value="DELETE">Suppression</option>
<option value="PAYMENT">Paiement</option>
<option value="VALIDATE">Validation</option>
<option value="REFUSE">Refus</option>
<option value="DELIVERY">Livraison</option>
<option value="ASSIGN_STORE">Assignation</option>
<option value="ENTRER">Entree</option>
<option value="SORTIE">Sortie</option>
<option value="LOGIN">Connexion</option>
</select>
</div>
<div class="col-md-2">
<label>Magasin</label>
<select id="filterStore" class="form-control">
<option value="">Tous</option>
<?php if (isset($stores) && is_array($stores)): ?>
<?php foreach ($stores as $store): ?>
<option value="<?= $store['name'] ?>"><?= $store['name'] ?></option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="col-md-2">
<label>&nbsp;</label>
<div>
<button type="button" class="btn btn-primary btn-block" id="btnFilter">
<i class="fa fa-search"></i> Filtrer
</button>
</div>
</div>
<div class="col-md-2">
<label>&nbsp;</label>
<div>
<button type="button" class="btn btn-warning btn-block" id="btnReset">
<i class="fa fa-refresh"></i> Reinitialiser
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Tableau -->
<div class="box">
<div class="box-header">
<div class="pull-right">
<button type="button" class="btn btn-success" id="btnExport">
<i class="fa fa-download"></i> Exporter CSV
</button>
</div>
<h3 class="box-title"><i class="fa fa-history"></i> Toutes les actions</h3>
</div>
<div class="box-body">
<table id="actionLogTable" class="table table-bordered table-striped" style="width:100%">
<thead>
<tr>
<th>Date</th>
<th>Utilisateur</th>
<th>Action</th>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
</table>
</div>
</div>
</section>
</div>
<script>
$(document).ready(function() {
$("#mainActionLogNav").addClass('active');
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ elements",
sInfo: "Affichage de l'element _START_ a _END_ sur _TOTAL_ elements",
sInfoEmpty: "Affichage de l'element 0 a 0 sur 0 element",
sInfoFiltered: "(filtre de _MAX_ elements au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun element a afficher",
sEmptyTable: "Aucune donnee disponible",
oPaginate: {
sFirst: "Premier",
sPrevious: "Precedent",
sNext: "Suivant",
sLast: "Dernier"
}
}
});
var base_url = '<?= base_url() ?>';
function loadTable() {
if ($.fn.DataTable.isDataTable('#actionLogTable')) {
$('#actionLogTable').DataTable().destroy();
}
$('#actionLogTable').DataTable({
ajax: {
url: base_url + '/action-log/fetchData',
type: 'GET',
data: function(d) {
d.date_from = $('#filterDateFrom').val();
d.date_to = $('#filterDateTo').val();
d.action = $('#filterAction').val();
d.store_name = $('#filterStore').val();
}
},
order: [[0, 'desc']],
pageLength: 25
});
}
loadTable();
$('#btnFilter').on('click', function() {
loadTable();
});
$('#btnReset').on('click', function() {
$('#filterDateFrom').val('<?= date('Y-m-d') ?>');
$('#filterDateTo').val('<?= date('Y-m-d') ?>');
$('#filterAction').val('');
$('#filterStore').val('');
loadTable();
});
$('#btnExport').on('click', function() {
var params = '?date_from=' + $('#filterDateFrom').val()
+ '&date_to=' + $('#filterDateTo').val()
+ '&action=' + $('#filterAction').val()
+ '&store_name=' + $('#filterStore').val();
window.location.href = base_url + '/action-log/export' + params;
});
});
</script>

View File

@ -547,9 +547,33 @@ $(document).ready(function() {
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
});
var adminCompletedColumns = [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{
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: "Statut" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
];
$('#avance_order').on('click', function() {
$('#table-title').text('Avances Complètes');
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceBecameOrder', adminColumns);
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceBecameOrder', adminCompletedColumns);
});
$('#avance_expired').on('click', function() {
@ -627,21 +651,32 @@ $('#avance_order').on('click', function() {
}
rebuildTableHeaders([
'#',
'Produit',
'Avance',
'Reste à payer',
'#',
'Produit',
'Avance',
'Statut',
'Date',
'Action'
]);
var completedUserColumns = [
{ title: "#" },
{ title: "Produit" },
{ title: "Avance" },
{ title: "Statut" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
];
manageTable = $('#avanceTable').DataTable({
ajax: {
url: base_url + 'avances/fetchAvanceBecameOrder',
type: 'GET',
dataSrc: 'data'
},
columns: userColumns,
columns: completedUserColumns,
language: datatableLangFr
});

View File

@ -34,7 +34,7 @@
<div class="box-header">
<h3 class="box-title">Gérer les informations sur l'entreprise</h3>
</div>
<form role="form" action="<?php base_url('company/update') ?>" method="post">
<form role="form" action="<?php base_url('company/update') ?>" method="post" enctype="multipart/form-data">
<div class="box-body">
<!-- Validation errors -->
@ -48,6 +48,19 @@
</div>
<?php endif; ?>
<div class="form-group">
<label>Logo de l'entreprise</label>
<div style="margin-bottom: 10px;">
<?php if (!empty($company_data['logo'])): ?>
<img src="<?= base_url($company_data['logo']) ?>?v=<?= time() ?>" alt="Logo" style="max-height: 80px; border: 1px solid #ddd; padding: 5px; border-radius: 4px;">
<?php else: ?>
<img src="<?= base_url('assets/images/company_logo.jpg') ?>?v=<?= time() ?>" alt="Logo par defaut" style="max-height: 80px; border: 1px solid #ddd; padding: 5px; border-radius: 4px; opacity: 0.4;">
<?php endif; ?>
</div>
<input type="file" name="logo" accept="image/*" class="form-control" style="line-height: normal; padding: 6px 12px; height: auto;">
<p class="help-block">Formats acceptes : JPG, PNG, GIF. Laissez vide pour garder le logo actuel.</p>
</div>
<div class="form-group">
<label for="company_name">Nom de l'entreprise</label>
<input type="text" class="form-control" id="company_name" name="company_name" placeholder="Nom de l'entreprise" value="<?php echo $company_data['company_name'] ?>" autocomplete="off">

View File

@ -311,6 +311,13 @@
<h3 class="box-title">
<i class="fa fa-calculator"></i> Résumé de Trésorerie
</h3>
<div class="pull-right" style="display: flex; align-items: center; gap: 8px;">
<input type="date" id="tresoDateFrom" class="form-control input-sm" value="<?= date('Y-m-d') ?>" style="width: 140px; display: inline-block;">
<span>au</span>
<input type="date" id="tresoDateTo" class="form-control input-sm" value="<?= date('Y-m-d') ?>" style="width: 140px; display: inline-block;">
<button type="button" class="btn btn-primary btn-sm" id="btnFilterTreso"><i class="fa fa-search"></i> Filtrer</button>
<button type="button" class="btn btn-warning btn-sm" id="btnResetTreso"><i class="fa fa-refresh"></i></button>
</div>
</div>
<div class="box-body">
@ -320,7 +327,7 @@
<div class="col-sm-3 col-xs-12">
<div class="amount-block brut">
<span class="amount-label" style="color: #388e3c;">💰 Total Brut</span>
<div class="amount-header" style="color: #2e7d32;">
<div class="amount-header" style="color: #2e7d32;" id="treso-brut">
<?php echo number_format($total_brut, 0, '.', ' '); ?> Ar
</div>
<span class="amount-sublabel">Orders + Avances</span>
@ -336,7 +343,7 @@
<div class="col-sm-3 col-xs-12">
<div class="amount-block sorties">
<span class="amount-label" style="color: #c62828;">💸 Décaissements</span>
<div class="amount-header" style="color: #d32f2f;">
<div class="amount-header" style="color: #d32f2f;" id="treso-sorties">
<?php echo number_format($total_sorties, 0, '.', ' '); ?> Ar
</div>
<span class="amount-sublabel">Espèce + MVOLA + Virement</span>
@ -352,7 +359,7 @@
<div class="col-sm-4 col-xs-12">
<div class="amount-block net">
<span class="amount-label" style="color: #1565c0;"> Solde Net</span>
<div class="amount-header" style="color: #1976d2; font-size: 36px;">
<div class="amount-header" style="color: #1976d2; font-size: 36px;" id="treso-net">
<?php echo number_format($total_caisse, 0, '.', ' '); ?> Ar
</div>
<span class="amount-sublabel">Disponible en caisse</span>
@ -371,10 +378,10 @@
<div class="payment-icon espece">
<i class="fa fa-money"></i>
</div>
<div class="payment-amount">
<div class="payment-amount" id="treso-espece">
<?php echo number_format($total_espece_caisse, 0, '.', ' '); ?> Ar
</div>
<div class="payment-label">💵 Espèce Disponible</div>
<div class="payment-label">Espece Disponible</div>
</div>
</div>
@ -384,10 +391,10 @@
<div class="payment-icon mvola">
<i class="fa fa-mobile"></i>
</div>
<div class="payment-amount">
<div class="payment-amount" id="treso-mvola">
<?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?> Ar
</div>
<div class="payment-label">📱 MVOLA Disponible</div>
<div class="payment-label">MVOLA Disponible</div>
</div>
</div>
@ -397,10 +404,10 @@
<div class="payment-icon banque">
<i class="fa fa-bank"></i>
</div>
<div class="payment-amount">
<div class="payment-amount" id="treso-banque">
<?php echo number_format($total_vb_caisse, 0, '.', ' '); ?> Ar
</div>
<div class="payment-label">🏦 Banque Disponible</div>
<div class="payment-label">Banque Disponible</div>
</div>
</div>
</div>
@ -408,6 +415,48 @@
</div>
</div>
</div>
<script>
$(document).ready(function() {
var baseUrl = '<?= base_url() ?>';
function loadTresorerie() {
var startDate = $('#tresoDateFrom').val();
var endDate = $('#tresoDateTo').val();
$.ajax({
url: baseUrl + '/dashboard/getTresorerieData',
type: 'GET',
data: { start_date: startDate, end_date: endDate },
success: function(data) {
$('#treso-brut').text(data.total_brut + ' Ar');
$('#treso-sorties').text(data.total_sorties + ' Ar');
$('#treso-net').text(data.total_net + ' Ar');
$('#treso-espece').text(data.espece + ' Ar');
$('#treso-mvola').text(data.mvola + ' Ar');
$('#treso-banque').text(data.banque + ' Ar');
$('#avance-mvola').text(data.avance_mvola + ' Ar');
$('#avance-espece').text(data.avance_espece + ' Ar');
$('#avance-banque').text(data.avance_banque + ' Ar');
}
});
}
// Charger par défaut avec la date d'aujourd'hui
loadTresorerie();
$('#btnFilterTreso').on('click', function() {
loadTresorerie();
});
$('#btnResetTreso').on('click', function() {
$('#tresoDateFrom').val('<?= date('Y-m-d') ?>');
$('#tresoDateTo').val('<?= date('Y-m-d') ?>');
loadTresorerie();
});
});
</script>
<!-- Détail des avances par mode de paiement -->
<div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;">
<div class="col-lg-12">
@ -419,19 +468,19 @@
<div class="row">
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_mvola, 0, '.', ' '); ?> Ar</h5>
<h5 class="description-header" id="avance-mvola"><?php echo number_format($total_avances_mvola, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">MVOLA</span>
</div>
</div>
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_espece, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">ESPÈCE</span>
<h5 class="description-header" id="avance-espece"><?php echo number_format($total_avances_espece, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">ESPECE</span>
</div>
</div>
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_virement, 0, '.', ' '); ?> Ar</h5>
<h5 class="description-header" id="avance-banque"><?php echo number_format($total_avances_virement, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">BANQUE</span>
</div>
</div>

View File

@ -149,28 +149,27 @@
<tr>
<tr>
<td>Recouvrement</td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewRecouvrement" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="createRecouvrement" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateRecouvrement" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewRecouvrement" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="deleteRecouvrement" class="minimal"></td>
<td>-</td>
</tr>
<tr>
<td>Décaissement</td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewSortieCaisse" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="createSortieCaisse" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateSortieCaisse" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewSortieCaisse" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="deleteSortieCaisse" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="validateSortieCaisse" class="minimal"></td>
</tr>
<tr>
<td>Remise</td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewRemise" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="validateRemise" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="refusedRemise" class="minimal"></td>
<td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" value="validateRemise" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewRemise" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="refusedRemise" class="minimal"></td>
</tr>
<tr>
<td>Validation commande</td>
@ -181,11 +180,11 @@
</tr>
<tr>
<td>Securité</td>
<td> - </td>
<td> <input type="checkbox" name="permission[]" id="permission" value="viewSecurite" class="minimal"> </td>
<td><input type="checkbox" name="permission[]" id="permission" value="createSecurite" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateSecurite" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewSecurite" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="deleteSecurite" class="minimal"></td>
<td>-</td>
</tr>
<tr>
<td>Rapports</td>
@ -197,11 +196,11 @@
</tr>
<tr>
<td>Avances</td>
<td> - </td>
<td> <input type="checkbox" name="permission[]" id="permission" value="viewAvance" class="minimal"> </td>
<td><input type="checkbox" name="permission[]" id="permission" value="createAvance" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateAvance" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="viewAvance" class="minimal"></td>
<td><input type="checkbox" name="permission[]" id="permission" value="deleteAvance" class="minimal"></td>
<td>-</td>
</tr>
<tr>
<td>Entreprise</td>

View File

@ -420,12 +420,6 @@
</tr>
<tr>
<td>Recouvrement</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewRecouvrement"
<?php if ($serialize_permission) {
if (in_array('viewRecouvrement', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="createRecouvrement"
<?php if ($serialize_permission) {
if (in_array('createRecouvrement', $serialize_permission)) {
@ -438,6 +432,12 @@
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewRecouvrement"
<?php if ($serialize_permission) {
if (in_array('viewRecouvrement', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="deleteRecouvrement"
<?php if ($serialize_permission) {
if (in_array('deleteRecouvrement', $serialize_permission)) {
@ -455,12 +455,6 @@
<tr>
<td>Sortie caisse</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewSortieCaisse"
<?php if ($serialize_permission) {
if (in_array('viewSortieCaisse', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="createSortieCaisse"
<?php if ($serialize_permission) {
if (in_array('createSortieCaisse', $serialize_permission)) {
@ -473,6 +467,12 @@
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewSortieCaisse"
<?php if ($serialize_permission) {
if (in_array('viewSortieCaisse', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="deleteSortieCaisse"
<?php if ($serialize_permission) {
if (in_array('deleteSortieCaisse', $serialize_permission)) {
@ -528,18 +528,19 @@
</tr>
<tr>
<td>Remise</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewRemise"
<?php if ($serialize_permission) {
if (in_array('viewRemise', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="validateRemise"
<?php if ($serialize_permission) {
if (in_array('validateRemise', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewRemise"
<?php if ($serialize_permission) {
if (in_array('viewRemise', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="refusedRemise"
<?php if ($serialize_permission) {
if (in_array('refusedRemise', $serialize_permission)) {
@ -547,7 +548,6 @@
}
} ?>></td>
<td>-</td>
<td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="notifRemise"
<?php if ($serialize_permission) {
if (in_array('notifRemise', $serialize_permission)) {
@ -569,6 +569,7 @@
echo "checked";
}
} ?>></td>
<td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="deleteCommande1"
<?php if ($serialize_permission) {
if (in_array('deleteCommande1', $serialize_permission)) {
@ -577,7 +578,6 @@
} ?>></td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>Sécurité</td>

View File

@ -273,7 +273,30 @@
'<i class="glyphicon glyphicon-tag"></i>' +
'</button>';
// Add new row in the table
// Gérer la visibilité du bouton "+" selon le type de client
function toggleAddRowButton() {
var customerType = $("#customer_type").val();
var rowCount = $("#product_info_table tbody tr").length;
if (customerType === 'particulier' && rowCount >= 1) {
$("#add_row").hide();
} else {
$("#add_row").show();
}
}
// Écouter le changement de type de client
$("#customer_type").on('change', function () {
var customerType = $(this).val();
if (customerType === 'particulier') {
// Supprimer toutes les lignes sauf la première
$("#product_info_table tbody tr:gt(0)").remove();
subAmount();
}
toggleAddRowButton();
});
// Add new row in the table
$("#add_row").unbind('click').bind('click', function () {
var table = $("#product_info_table");
var count_table_tbody_tr = $("#product_info_table tbody tr").length;
@ -285,7 +308,6 @@
dataType: 'json',
success: function (response) {
// console.log(reponse.x);
var html = '<tr id="row_' + row_id + '">' +
'<td>' +
'<select class="form-control select_group product" data-row-id="' + row_id + '" id="product_' + row_id + '" name="product[]" style="width:100%;" onchange="getProductData(' + row_id + ')">' +
@ -313,6 +335,9 @@
$(".product").select2();
// Cacher le bouton "+" si particulier et déjà 2 lignes
toggleAddRowButton();
}
});
@ -430,6 +455,7 @@
function removeRow(tr_id) {
$("#product_info_table tbody tr#row_" + tr_id).remove();
subAmount();
toggleAddRowButton();
}
function formatAllNumbers() {
// Sélecteur pour tous les inputs que tu veux formater

View File

@ -365,7 +365,7 @@
<input type="hidden" name="vat_charge_rate" value="<?php echo $company_data['vat_charge_value'] ?>" autocomplete="off">
<a target="__blank" id="Imprimente" href="<?php echo base_url() . 'orders/printDiv/' . $order_data['order']['id'] ?>" class="btn btn-default">Imprimer</a>
<button type="submit" class="btn btn-primary">Enregistrer</button>
<button type="submit" class="btn btn-primary">Payer</button>
<a href="<?php echo base_url('orders/') ?>" class="btn btn-warning">Retour</a>
</div>
</form>

View File

@ -186,6 +186,33 @@
</div>
</div>
<!-- COMMANDES EN ATTENTE DE VALIDATION -->
<div class="row">
<div class="col-md-12">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa fa-clock-o"></i> Commandes en attente de livraison
</h3>
</div>
<div class="box-body">
<table id="pendingTable" class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Image</th>
<th>UGS</th>
<th>Désignation</th>
<th>Statut</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Filtres -->
<div class="row">
<div class="col-md-12">
@ -266,6 +293,38 @@
</section>
</div>
<!-- Modal de validation livraison -->
<div class="modal fade" id="editModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header" style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white;">
<button type="button" class="close" data-dismiss="modal" style="color: white;">&times;</button>
<h4 class="modal-title"><i class="fa fa-check-circle"></i> Valider la livraison</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4 text-center">
<img id="editImage" class="img-responsive img-thumbnail" src="" alt="Produit" style="max-height: 200px;">
</div>
<div class="col-md-8">
<p><strong>Produit :</strong> <span id="editNom"></span></p>
<p><strong>UGS :</strong> <span id="editUgs"></span></p>
<p><strong> Facture :</strong> <span id="editBillNo"></span></p>
<p><strong>Client :</strong> <span id="editClient"></span></p>
<p><strong>Adresse :</strong> <span id="editAddress"></span></p>
<p><strong>Téléphone :</strong> <span id="editPhone"></span></p>
<p><strong>CIN :</strong> <span id="editCin"></span></p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-success" id="btnValidate"><i class="fa fa-check"></i> Confirmer la livraison</button>
</div>
</div>
</div>
</div>
<!-- Modal Détails -->
<div class="modal fade" id="detailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
@ -361,7 +420,95 @@
<script>
$(function() {
$("#securiteNav").addClass('active');
// ✅ TABLEAU DES COMMANDES EN ATTENTE
var pendingTable = $('#pendingTable').DataTable({
ajax: {
url: '<?= base_url('validateSecurite/fetchSecuriteData') ?>',
type: 'GET',
dataSrc: 'data'
},
columns: [
{ data: 'image', orderable: false },
{ data: 'ugs' },
{ data: 'designation' },
{ data: 'statut' },
{ data: 'action', orderable: false }
],
order: [],
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sZeroRecords: "Aucune commande en attente",
sEmptyTable: "Aucune commande en attente de livraison",
sInfo: "Affichage de _START_ à _END_ sur _TOTAL_ éléments",
sInfoEmpty: "Aucun élément",
oPaginate: { sPrevious: "Précédent", sNext: "Suivant" }
}
});
// Rafraîchir les commandes en attente toutes les 10 secondes
setInterval(function() { pendingTable.ajax.reload(null, false); }, 10000);
// ✅ GESTION DE LA VALIDATION
var currentSecuriteId = null;
window.editFunc = function(id) {
currentSecuriteId = id;
$.ajax({
url: '<?= base_url('validateSecurite/fetchSecuriteDataById') ?>/' + id,
type: 'POST',
dataType: 'json',
success: function(response) {
$('#editImage').attr('src', response.image);
$('#editNom').text(response.nom);
$('#editUgs').text(response.ugs);
$('#editBillNo').text(response.bill_no);
$('#editClient').text(response.customer_name);
$('#editAddress').text(response.customer_address);
$('#editPhone').text(response.customer_phone);
$('#editCin').text(response.customer_cin);
$('#editModal').modal('show');
}
});
};
$('#btnValidate').on('click', function() {
if (!currentSecuriteId) return;
var btn = $(this);
btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Validation...');
$.ajax({
url: '<?= base_url('validateSecurite/update') ?>/' + currentSecuriteId,
type: 'POST',
data: { status: 'Validé' },
dataType: 'json',
success: function(response) {
$('#editModal').modal('hide');
btn.prop('disabled', false).html('<i class="fa fa-check"></i> Confirmer la livraison');
if (response.success) {
// Aussi marquer comme livré côté commande
pendingTable.ajax.reload(null, false);
if (typeof historyTable !== 'undefined') historyTable.ajax.reload(null, false);
$('#messages').html('<div class="alert alert-success alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">&times;</button>' +
'<i class="fa fa-check"></i> ' + response.messages + '</div>');
setTimeout(function() { $('#messages').fadeOut('slow', function() { $(this).html('').show(); }); }, 3000);
} else {
$('#messages').html('<div class="alert alert-danger alert-dismissible">' +
'<button type="button" class="close" data-dismiss="alert">&times;</button>' +
response.messages + '</div>');
}
},
error: function() {
btn.prop('disabled', false).html('<i class="fa fa-check"></i> Confirmer la livraison');
$('#editModal').modal('hide');
}
});
});
// Configuration DataTable en français
$.extend(true, $.fn.dataTable.defaults, {
language: {

View File

@ -3,7 +3,7 @@
<div>
<strong>Copyright &copy;<?php echo date('Y') ?>.</strong> All rights reserved.
</div>
<div class="pull-right hidden-xs"><h5 style="font-family: tahoma;">Designed and Managed by <strong>MYDEVUP</strong>
<div class="pull-right hidden-xs"><h5 style="font-family: tahoma;">Designed and Managed by <strong>COMPANY FOR MADAGASCAR</strong>
</footer>
<!-- Add the sidebar's background. This div must be placed
@ -17,26 +17,87 @@ function toggleSidebar() {
document.body.classList.toggle('sidebar-collapsed');
}
document.addEventListener('DOMContentLoaded', function() {
const treeviews = document.querySelectorAll('.sidebar-menu .treeview');
$(document).ready(function() {
// Supprimer TOUS les handlers AdminLTE sur le menu
$('.sidebar-menu').off();
$(document).off('click', '.sidebar-menu li a');
$(document).off('click', '.treeview > a');
treeviews.forEach(item => {
// Quand la souris entre dans un menu
item.addEventListener('mouseenter', function () {
if (document.body.classList.contains('sidebar-collapsed')) {
this.classList.add('menu-open');
}
});
// Aussi tenter de détruire le widget tree AdminLTE
try { $('.sidebar-menu').tree('destroy'); } catch(e) {}
// Quand la souris sort du menu
item.addEventListener('mouseleave', function () {
if (document.body.classList.contains('sidebar-collapsed')) {
this.classList.remove('menu-open');
// Re-supprimer après un court délai (AdminLTE peut rebind)
setTimeout(function() {
$('.sidebar-menu').off();
bindSidebarMenu();
}, 100);
function bindSidebarMenu() {
// Clic sur un treeview
$('.sidebar-menu .treeview > a').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
var $li = $(this).parent();
var menuId = $li.attr('id') || $('.sidebar-menu .treeview').index($li);
// Fermer les autres
$('.sidebar-menu .treeview').not($li).removeClass('menu-open')
.children('.treeview-menu').slideUp(200);
// Toggle
if ($li.hasClass('menu-open')) {
$li.removeClass('menu-open');
$li.children('.treeview-menu').slideUp(200);
localStorage.removeItem('openMenu');
} else {
$li.addClass('menu-open');
$li.children('.treeview-menu').slideDown(200);
localStorage.setItem('openMenu', String(menuId));
}
return false;
});
}
// Restaurer le menu ouvert au survol de la sidebar
var restored = false;
$('.main-sidebar').on('mouseenter', function() {
if (!restored && $('body').hasClass('sidebar-collapsed')) {
var openMenu = localStorage.getItem('openMenu');
if (openMenu !== null) {
var $target = $('#' + openMenu);
if (!$target.length) {
$target = $('.sidebar-menu .treeview').eq(parseInt(openMenu));
}
if ($target.length) {
$target.addClass('menu-open');
$target.children('.treeview-menu').slideDown(200);
}
}
restored = true;
}
});
// Quand la souris quitte : fermer visuellement mais garder en mémoire
var closeTimer = null;
$('.main-sidebar').on('mouseleave', function() {
if ($('body').hasClass('sidebar-collapsed')) {
closeTimer = setTimeout(function() {
$('.sidebar-menu .treeview').removeClass('menu-open');
$('.sidebar-menu .treeview-menu').slideUp(200);
restored = false;
}, 400);
}
});
$('.main-sidebar').on('mouseenter', function() {
if (closeTimer) {
clearTimeout(closeTimer);
closeTimer = null;
}
});
});
</script>

View File

@ -5,6 +5,13 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><?php echo $page_title; ?></title>
<?php if (!empty($company_logo)): ?>
<link rel="icon" type="image/x-icon" href="<?= base_url($company_logo) ?>?v=<?= time() ?>">
<link rel="shortcut icon" href="<?= base_url($company_logo) ?>?v=<?= time() ?>">
<?php else: ?>
<link rel="icon" type="image/x-icon" href="<?= base_url('assets/images/company_logo.jpg') ?>?v=<?= time() ?>">
<link rel="shortcut icon" href="<?= base_url('assets/images/company_logo.jpg') ?>?v=<?= time() ?>">
<?php endif; ?>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
@ -118,7 +125,7 @@
</head>
<body class="hold-transition skin-blue sidebar-mini">
<body class="hold-transition skin-blue sidebar-mini sidebar-collapsed">
<div class="wrapper">
<style>
/* ============================================
@ -127,6 +134,26 @@
/* ============================================
VARIABLES CSS - Design System Couleurs Unies
============================================ */
/* Fix select dropdowns */
select.form-control {
width: 100% !important;
min-width: 0;
text-overflow: ellipsis;
-webkit-appearance: menulist;
-moz-appearance: menulist;
appearance: menulist;
padding-right: 30px !important;
color: #333 !important;
background-color: #fff !important;
height: auto !important;
min-height: 40px;
}
select.form-control option {
color: #333;
background: #fff;
padding: 5px 10px;
}
:root {
--primary-color: #3498db;
--success-color: #2ecc71;
@ -146,13 +173,12 @@ body {
margin: 0;
padding: 0;
font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Source Sans Pro', sans-serif !important;
overflow-x: hidden;
min-height: 100vh;
background: #3498db !important;
}
.wrapper {
margin-top:30px;
margin-top:50px;
display: flex;
flex-direction: column;
min-height: 100vh;
@ -165,14 +191,14 @@ body {
.content-wrapper {
flex: 1;
overflow-y: auto;
padding-bottom: 60px;
overflow-x: auto;
margin-left: 230px;
padding-top: 50px;
transition: margin-left 0.3s ease;
width: calc(100% - 230px);
transition: margin-left 0.3s ease, width 0.3s ease;
background: #ecf0f1 !important;
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.1);
min-height: 100vh;
padding: 30px !important;
padding: 75px 30px 80px 30px !important;
}
/* ============================================
@ -188,6 +214,8 @@ body {
height: 50px;
transition: margin-left 0.3s ease;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.2);
display: flex;
align-items: stretch;
}
.content-header {
@ -240,21 +268,30 @@ body {
============================================ */
.main-sidebar {
position: fixed;
top: 0;
top: 50px;
left: 0;
height: 100vh;
height: calc(100vh - 50px);
overflow-y: auto;
z-index: 810;
width: 230px;
background: #2c3e50 !important;
transition: width 0.3s ease;
box-shadow: 2px 0 20px rgba(0, 0, 0, 0.3);
padding-top: 0 !important;
}
.main-sidebar .logo {
display: none;
}
/* Logo */
.logo {
display: block;
padding: 15px;
flex-shrink: 0;
width: 230px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 15px;
text-align: center;
background: #3498db !important;
color: white;
@ -262,8 +299,11 @@ body {
font-weight: 700;
overflow: hidden;
white-space: nowrap;
transition: padding 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
transition: width 0.3s ease, padding 0.3s ease;
}
.main-header .navbar {
flex: 1;
}
.logo-mini {
@ -696,7 +736,7 @@ body {
left: 230px;
margin-left: 2px;
right: 0;
z-index: 1000;
z-index: 800;
background: #ffffff !important;
padding: 12px 20px;
border-top: 2px solid #3498db;
@ -713,20 +753,30 @@ body {
============================================ */
.sidebar-toggle {
position: fixed;
top: 0;
left: 230px;
height: 50px;
line-height: 30px;
z-index: 2000;
background: #3c8dbc;
background: transparent;
color: #fff;
padding: 8px 12px;
border-radius: 3px;
padding: 0 15px;
cursor: pointer;
transition: left 0.3s ease;
font-size: 18px;
}
.sidebar-collapsed .sidebar-toggle {
left: 70px;
}
.sidebar-toggle::before {
content: "\f0c9";
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
}
/* ============================================
NOTIFICATIONS - Badge modernisé
============================================ */
@ -791,16 +841,69 @@ body {
.sidebar-collapsed .main-sidebar {
width: 70px;
overflow-y: auto;
overflow-x: visible;
overflow-x: hidden;
direction: rtl;
transition: width 0.35s ease;
}
/* Ouvrir la sidebar au survol */
.sidebar-collapsed .main-sidebar:hover {
width: 230px;
overflow-x: visible;
}
.sidebar-collapsed .main-sidebar:hover .sidebar-menu > li > a > span {
opacity: 1;
max-width: 180px;
white-space: nowrap;
transition: opacity 0.2s ease 0.15s, max-width 0.35s ease;
}
.sidebar-collapsed .main-sidebar:hover .sidebar-menu > li > a .pull-right-container {
opacity: 1;
max-width: 20px;
transition: opacity 0.2s ease 0.15s, max-width 0.35s ease;
}
.sidebar-collapsed .main-sidebar:hover .sidebar-menu > li > a {
text-align: left;
padding: 12px 15px;
width: auto;
justify-content: flex-start;
}
.sidebar-collapsed .main-sidebar:hover .sidebar-menu i {
margin-right: 10px;
display: inline-block;
font-size: 16px;
}
.sidebar-collapsed .main-sidebar:hover .logo {
width: 230px;
padding: 15px;
}
.sidebar-collapsed .main-sidebar:hover .logo-mini {
display: none !important;
opacity: 0;
max-width: 0;
}
.sidebar-collapsed .main-sidebar:hover .logo-lg {
display: inline !important;
opacity: 1 !important;
max-width: 200px !important;
overflow: visible !important;
transition: opacity 0.25s ease 0.15s, max-width 0.35s ease;
}
/* Sous-menus gérés plus bas */
.sidebar-collapsed .main-sidebar .sidebar-menu {
direction: ltr;
}
.sidebar-collapsed .content-wrapper {
margin-left: 70px;
width: calc(100% - 70px);
}
.sidebar-collapsed .main-sidebar:hover ~ .content-wrapper {
margin-left: 230px;
width: calc(100% - 230px);
transition: margin-left 0.35s ease, width 0.35s ease;
}
.sidebar-collapsed .main-header {
@ -813,11 +916,35 @@ body {
margin-left: 2px;
}
.sidebar-collapsed .main-sidebar:hover ~ .main-footer {
left: 230px;
transition: left 0.35s ease;
}
.sidebar-collapsed .sidebar-toggle {
left: 70px;
}
.sidebar-collapsed .logo {
.sidebar-collapsed .main-header .logo {
width: 70px;
padding: 0 5px;
}
.sidebar-collapsed .main-header .logo-mini {
display: inline-block;
}
.sidebar-collapsed .main-header .logo-lg {
display: none;
}
.logo-mini img {
max-height: 40px;
max-width: 50px;
border-radius: 4px;
}
.sidebar-collapsed .main-sidebar .logo {
padding: 15px 5px;
text-align: center;
display: flex;
@ -827,22 +954,37 @@ body {
box-sizing: border-box;
}
.sidebar-collapsed .logo-mini {
.sidebar-collapsed .main-sidebar .logo-mini {
display: inline-block;
text-align: center;
width: 100%;
opacity: 1;
max-width: 60px;
overflow: hidden;
transition: opacity 0.25s ease 0.1s, max-width 0.35s ease;
}
.sidebar-collapsed .logo-lg {
display: none;
.sidebar-collapsed .main-sidebar .logo-lg {
opacity: 0;
max-width: 0;
overflow: hidden;
white-space: nowrap;
transition: opacity 0.25s ease, max-width 0.35s ease;
}
.sidebar-collapsed .sidebar-menu > li > a > span {
display: none;
opacity: 0;
max-width: 0;
overflow: hidden;
white-space: nowrap;
transition: opacity 0.2s ease, max-width 0.35s ease;
}
.sidebar-collapsed .sidebar-menu > li > a .pull-right-container {
display: none;
opacity: 0;
max-width: 0;
overflow: hidden;
transition: opacity 0.2s ease, max-width 0.35s ease;
}
.sidebar-collapsed .sidebar-menu > li > a {
@ -854,7 +996,17 @@ body {
justify-content: center;
width: 70px;
box-sizing: border-box;
margin: 5px auto;
margin: 0 auto;
}
.sidebar-collapsed .sidebar-menu > li {
margin: 0;
padding: 0;
}
.sidebar-collapsed .sidebar-menu > li.treeview > .treeview-menu {
margin: 0;
padding: 0;
}
.sidebar-collapsed .sidebar-menu i {
@ -865,114 +1017,31 @@ body {
text-align: center;
}
/* Pas de tooltip en mode réduit */
.sidebar-collapsed .sidebar-menu > li > a::before {
content: attr(data-tooltip);
position: absolute;
left: 70px;
top: 50%;
transform: translateY(-50%);
background: #3498db;
color: #fff;
padding: 10px 15px;
border-radius: 8px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 9998;
font-size: 14px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
font-weight: 600;
pointer-events: none;
display: none;
}
.sidebar-collapsed .sidebar-menu > li:hover > a::before {
opacity: 1;
visibility: visible;
left: 80px;
}
.sidebar-collapsed .treeview-menu {
position: fixed;
left: 80px;
top: auto;
width: 200px;
max-width: calc(100vw - 100px);
z-index: 9999;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25);
border-radius: 4px;
border-left: 2px solid #3c8dbc;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;
display: block !important;
background: #fff;
backdrop-filter: blur(8px);
border: 1px solid rgba(210, 214, 222, 0.8);
pointer-events: none;
transform: translateX(-20px) scale(0.95);
transform-origin: left center;
margin-top: 0;
}
.sidebar-collapsed .treeview:hover .treeview-menu {
pointer-events: auto !important;
opacity: 1;
visibility: visible;
transform: translateX(0) scale(1);
}
.sidebar-collapsed .treeview-menu:hover {
/* Sidebar hover ouverte : sous-menus par clic (jQuery gère display) */
.sidebar-collapsed .main-sidebar:hover .treeview-menu {
position: static !important;
width: auto !important;
background: rgba(0,0,0,0.15) !important;
border: none !important;
box-shadow: none !important;
opacity: 1 !important;
visibility: visible !important;
transform: none !important;
pointer-events: auto !important;
transform: translateX(0) scale(1) !important;
}
.sidebar-collapsed .treeview-menu > li > a {
padding: 8px 12px;
color: #495057;
white-space: nowrap;
border-bottom: 1px solid rgba(210, 214, 222, 0.4);
transition: all 0.3s ease;
font-size: 12px;
font-weight: 500;
.sidebar-collapsed .main-sidebar:hover .treeview-menu > li > a {
padding: 8px 15px 8px 30px;
color: #b8c7ce;
display: block;
position: relative;
cursor: pointer;
text-decoration: none;
pointer-events: auto !important;
z-index: 10000;
}
.sidebar-collapsed .treeview-menu > li:last-child > a {
border-bottom: none;
border-radius: 0 0 4px 4px;
}
.sidebar-collapsed .treeview-menu > li:first-child > a {
border-radius: 4px 4px 0 0;
}
.sidebar-collapsed .treeview-menu > li > a:hover {
background: #3c8dbc;
.sidebar-collapsed .main-sidebar:hover .treeview-menu > li > a:hover {
color: #fff;
padding-left: 15px;
transform: translateX(2px);
}
.sidebar-collapsed .treeview-menu > li > a i {
margin-right: 6px;
width: 14px;
text-align: center;
font-size: 11px;
opacity: 0.7;
transition: all 0.3s ease;
}
.sidebar-collapsed .treeview-menu > li > a:hover i {
opacity: 1;
transform: scale(1.05);
background: rgba(255,255,255,0.1);
}
/* ============================================
@ -1167,12 +1236,7 @@ body {
left: 10px;
}
/* Treeview menu sur mobile */
.sidebar-collapsed .treeview-menu {
left: 70px;
width: 180px;
}
/* Charts containers */
.col-lg-6[style*="background-color: white"] {
padding: 15px !important;
@ -1563,9 +1627,7 @@ body {
.small-box:nth-child(4) { animation-delay: 0.4s; }
.small-box:nth-child(5) { animation-delay: 0.5s; }
.sidebar-collapsed .treeview:hover .treeview-menu {
animation: slideInFromSidebar 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* animation removed - using click-based submenus */
/* Désactiver les animations sur mobile pour de meilleures performances */
@media (max-width: 768px) {

View File

@ -1,16 +1,14 @@
<header class="main-header">
<!-- Logo -->
<a href="#" class="logo">
<span class="logo-mini"><b><img src="<?= base_url('assets/images/company_logo.jpg') ?>"></b></span>
<span class="logo-lg"><b>MotorBike</b></span>
<span class="logo-mini"><b><?php if (!empty($company_logo)): ?><img src="<?= base_url($company_logo) ?>?v=<?= time() ?>"><?php else: ?><img src="<?= base_url('assets/images/company_logo.jpg') ?>?v=<?= time() ?>" style="opacity:0.4;"><?php endif; ?></b></span>
<span class="logo-lg"><b><?= $company_name ?? 'MotorBike' ?></b></span>
</a>
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" style="position: relative; height: 50px; background: #3c8dbc;">
<!-- Sidebar toggle button -->
<a href="#" class="sidebar-toggle" onclick="toggleSidebar()">
<span class="sr-only">Toggle navigation</span>
</a>
<a href="#" class="sidebar-toggle" onclick="toggleSidebar()"></a>
<!-- Navbar Right Menu -->
<ul class="navbar-nav" style="position: absolute; right: 15px; top: 50%; transform: translateY(-50%); margin: 0; padding: 0; list-style: none; display: flex; align-items: center; gap: 20px;">

View File

@ -273,7 +273,6 @@
<li id="reportNav">
<a href="<?php echo base_url('reports/') ?>">
<i class="glyphicon glyphicon-stats"></i> <span>Rapports</span>
<span class="tooltip">Rapports</span>
</a>
</li>
<?php endif; ?>
@ -286,6 +285,18 @@
</li>
<?php endif; ?> -->
<?php
$session = session();
$currentUser = $session->get('user');
if (isset($currentUser['group_name']) && $currentUser['group_name'] === 'SuperAdmin'):
?>
<li id="mainActionLogNav">
<a href="<?php echo base_url('action-log') ?>">
<i class="fa fa-history"></i> <span>Historique des Actions</span>
</a>
</li>
<?php endif; ?>
<?php if (in_array('updateCompany', $user_permission)): ?>
<li id="companyNav"><a href="<?php echo base_url('company/') ?>"><i class="fa fa-building"></i> <span>Entreprise</span></a></li>
<?php endif; ?>

View File

@ -53,9 +53,7 @@
<th>Phone</th>
<th>Point de vente</th>
<th>Role</th>
<?php if (in_array('updateUser', $user_permission) || in_array('deleteUser', $user_permission)): ?>
<th>Action</th>
<?php endif; ?>
<th>Action</th>
</tr>
</thead>