Compare commits

...

No commits in common. '17803c1acb38e9fd8d87e1aa840f3a2a5158b227' and '0ce9cb608d1a236dc7238b2e096547fc7c624372' have entirely different histories.

  1. 2
      .vscode/sftp.json
  2. 98
      app/Config/Email.php
  3. 6
      app/Config/Kint.php
  4. 11
      app/Config/Routes.php
  5. 14
      app/Controllers/AlertsController.php
  6. 827
      app/Controllers/AvanceController.php
  7. 52
      app/Controllers/BaseController.php
  8. 428
      app/Controllers/ProductCOntroller.php
  9. 58
      app/Controllers/ReportController.php
  10. 25
      app/Controllers/TestDeadline.php
  11. 41
      app/Controllers/UserController.php
  12. 145
      app/Controllers/test.html
  13. 176
      app/Helpers/alerts_helper.php
  14. 21
      app/Models/AlertMail.php
  15. 326
      app/Models/Avance.php
  16. 89
      app/Views/attributes/addvalue.php
  17. 831
      app/Views/avances/avance.php
  18. 25
      app/Views/brands/index.php
  19. 25
      app/Views/category/index.php
  20. 94
      app/Views/commercial/addImage.php
  21. 25
      app/Views/commercial/index.php
  22. 16
      app/Views/commercial/single.php
  23. 26
      app/Views/dashboard.php
  24. 26
      app/Views/demande/index.php
  25. 8
      app/Views/groups/create.php
  26. 5
      app/Views/groups/edit.php
  27. 25
      app/Views/groups/index.php
  28. 32
      app/Views/mecanicien/index.php
  29. 27
      app/Views/orders/avance.php
  30. 4
      app/Views/orders/create.php
  31. 2
      app/Views/orders/createbyid.php
  32. 26
      app/Views/orders/index.php
  33. 25
      app/Views/performance/index.php
  34. 3
      app/Views/products/create.php
  35. 62
      app/Views/products/index.php
  36. 25
      app/Views/recouvrement/index.php
  37. 25
      app/Views/recouvrement/recouvrement.php
  38. 1
      app/Views/reports/performance.php
  39. 2
      app/Views/reports/stockDetail.php
  40. 1
      app/Views/reports/venteStore.php
  41. 24
      app/Views/securite/index.php
  42. 52
      app/Views/sortieCaisse/index.php
  43. 25
      app/Views/stores/index.php
  44. 49
      app/Views/templates/side_menubar.php
  45. 28
      app/Views/users/index.php
  46. 1
      awstats-icon
  47. 1
      awstats-icon
  48. 1
      awstatsicons
  49. 1
      awstatsicons
  50. 9
      composer.json
  51. 1
      icon/mime/conf.png
  52. BIN
      icon/mime/conf.png
  53. 1
      icon/mime/csv.png
  54. BIN
      icon/mime/csv.png
  55. 1
      icon/mime/document.png
  56. BIN
      icon/mime/document.png
  57. 1
      icon/mime/dtd.png
  58. BIN
      icon/mime/dtd.png
  59. 1
      icon/mime/flv.png
  60. BIN
      icon/mime/flv.png
  61. 1
      icon/mime/fon.png
  62. BIN
      icon/mime/fon.png
  63. 1
      icon/mime/package.png
  64. BIN
      icon/mime/package.png
  65. 1
      icon/mime/runtime.png
  66. BIN
      icon/mime/runtime.png
  67. 1
      icon/mime/swf.png
  68. BIN
      icon/mime/swf.png
  69. 1
      icon/mime/vbs.png
  70. BIN
      icon/mime/vbs.png
  71. 1
      icon/mime/xsl.png
  72. BIN
      icon/mime/xsl.png
  73. BIN
      public/assets/bower_components/jquery-ui/themes/humanity/images/animated-overlay.gif
  74. BIN
      public/assets/bower_components/jquery-ui/themes/humanity/images/ui-bg_glass_100_f5f0e5_1x400.png
  75. BIN
      public/assets/images/product_image/686cdaac4dad4.jpg
  76. BIN
      public/assets/images/product_image/686cdaac5f43e.jpg
  77. BIN
      public/assets/images/product_image/686cdaac72635.jpg
  78. BIN
      public/assets/images/product_image/6894a44cd6b5f.jpg
  79. BIN
      public/assets/images/product_image/689ca160619a9.jpg
  80. BIN
      public/assets/images/product_image/689eba65cf5a8.jpg
  81. BIN
      public/assets/images/product_image/689ebcb98f420.jpg
  82. BIN
      public/assets/images/product_image/689ebceb0d802.jpg
  83. BIN
      public/assets/images/product_image/689ebd3b59c5b.jpg
  84. 1
      writable/cache/check_deadline_last_run.txt
  85. 1
      writable/debugbar/debugbar_1751886377.352834.json
  86. 1
      writable/debugbar/debugbar_1751886387.359498.json
  87. 1
      writable/debugbar/debugbar_1751886393.226242.json
  88. 1
      writable/debugbar/debugbar_1751886396.580956.json
  89. 1
      writable/debugbar/debugbar_1751886397.779369.json
  90. 1
      writable/debugbar/debugbar_1751886406.431574.json
  91. 1
      writable/debugbar/debugbar_1751886416.347097.json
  92. 1
      writable/debugbar/debugbar_1751886426.829140.json
  93. 1
      writable/debugbar/debugbar_1751886438.478916.json
  94. 1
      writable/debugbar/debugbar_1751886439.130326.json
  95. 1
      writable/debugbar/debugbar_1751886443.397712.json
  96. 1
      writable/debugbar/debugbar_1751886445.279811.json
  97. 1
      writable/debugbar/debugbar_1751886453.171091.json
  98. 1
      writable/debugbar/debugbar_1751886454.621281.json
  99. 1
      writable/debugbar/debugbar_1751886456.946542.json
  100. 1
      writable/debugbar/debugbar_1751886458.223834.json

2
.vscode/sftp.json

@ -6,7 +6,7 @@
"username": "motorbike",
"remotePath": "/home/motorbike/public_html/",
"password": "IVrMDogT3XiBcrY",
"uploadOnSave": true,
"uploadOnSave": false,
"useTempFile": false,
"openSsh": false
}

98
app/Config/Email.php

@ -1,121 +1,53 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Email extends BaseConfig
{
public string $fromEmail = '';
public string $fromName = '';
public string $fromEmail = 'rey342505@gmail.com';
public string $fromName = 'motorbike';
public string $recipients = '';
/**
* The "user agent"
*/
public string $userAgent = 'CodeIgniter';
/**
* The mail sending protocol: mail, sendmail, smtp
*/
public string $protocol = 'mail';
public string $protocol = 'smtp';
/**
* The server path to Sendmail.
*/
public string $mailPath = '/usr/sbin/sendmail';
/**
* SMTP Server Hostname
*/
public string $SMTPHost = '';
/**
* SMTP Username
*/
public string $SMTPUser = '';
/**
* SMTP Password
*/
public string $SMTPPass = '';
/**
* SMTP Port
*/
public int $SMTPPort = 25;
/**
* SMTP Timeout (in seconds)
*/
public int $SMTPTimeout = 5;
/**
* Enable persistent SMTP connections
*/
public string $SMTPHost = 'smtp.gmail.com';
public string $SMTPUser = 'rey342505@gmail.com';
public string $SMTPPass = 'loirqovmfuxnasrm'; // Mot de passe d’application (App Password) Gmail
public int $SMTPPort = 587;
public int $SMTPTimeout = 30;
public bool $SMTPKeepAlive = false;
/**
* SMTP Encryption.
*
* @var string '', 'tls' or 'ssl'. 'tls' will issue a STARTTLS command
* to the server. 'ssl' means implicit SSL. Connection on port
* 465 should set this to ''.
*/
public string $SMTPCrypto = 'tls';
/**
* Enable word-wrap
*/
public bool $wordWrap = true;
/**
* Character count to wrap at
*/
public int $wrapChars = 76;
/**
* Type of mail, either 'text' or 'html'
*/
public string $mailType = 'text';
public string $mailType = 'html';
/**
* Character set (utf-8, iso-8859-1, etc.)
*/
public string $charset = 'UTF-8';
/**
* Whether to validate the email address
*/
public bool $validate = false;
public bool $validate = true;
/**
* Email Priority. 1 = highest. 5 = lowest. 3 = normal
*/
public int $priority = 3;
/**
* Newline character. (Use “\r\n” to comply with RFC 822)
*/
public string $CRLF = "\r\n";
/**
* Newline character. (Use “\r\n” to comply with RFC 822)
*/
public string $newline = "\r\n";
/**
* Enable BCC Batch Mode.
*/
public bool $BCCBatchMode = false;
/**
* Number of emails in each BCC batch
*/
public int $BCCBatchSize = 200;
/**
* Enable notify message from server
*/
public bool $DSN = false;
}

6
app/Config/Kint.php

@ -4,7 +4,7 @@ namespace Config;
use CodeIgniter\Config\BaseConfig;
use Kint\Parser\ConstructablePluginInterface;
use Kint\Renderer\AbstractRenderer;
//use Kint\Renderer\AbstractRenderer;
use Kint\Renderer\Rich\TabPluginInterface;
use Kint\Renderer\Rich\ValuePluginInterface;
@ -41,8 +41,10 @@ class Kint extends BaseConfig
|--------------------------------------------------------------------------
*/
public string $richTheme = 'aante-light.css';
public $richSort = null;
public bool $richFolder = false;
public int $richSort = AbstractRenderer::SORT_FULL;
//public int $richSort = AbstractRenderer::SORT_FULL;
/**
* @var array<string, class-string<ValuePluginInterface>>|null

11
app/Config/Routes.php

@ -30,6 +30,14 @@ use App\Controllers\PerformanceController;
*/
$routes->get('/login', [Auth::class, 'login'], ['filter' => 'loggedIn']);
$routes->post('/login', [Auth::class, 'loginPost'], ['filter' => 'loggedIn']);
$routes->get('test-email', 'TestEmail::index');
$routes->get('alerts/check', 'AlertsController::check');
$routes->get('check-deadline', 'TestDeadline::index');
/**
* route to all the rest of web app
@ -53,6 +61,8 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
* route to logout
*/
$routes->get('/logout', [Auth::class, 'logout']);
// Route pour tester les alertes manuellement (à supprimer en production)
$routes->get('test-deadline-alerts', 'AvanceController::checkDeadlineAlerts');
/**
* route for the users
@ -242,6 +252,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->post('delete', [MecanicienController::class, 'delete']);
$routes->post('update/(:num)', [MecanicienController::class, 'update']);
$routes->get('fetchMecanicienPerformances', [MecanicienController::class, 'fetchMecanicienPerformances']);
// $routes->put('update/(:num)', 'MecanicienController::update/$1');
});

14
app/Controllers/AlertsController.php

@ -0,0 +1,14 @@
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
class AlertsController extends BaseController
{
public function check()
{
helper('alerts');
checkDeadlineAlerts();
return "Vérification des alertes effectuée.";
}
}

827
app/Controllers/AvanceController.php

@ -6,6 +6,7 @@ use App\Models\Company;
use App\Models\Orders;
use App\Models\Products;
use App\Models\Avance;
use App\Models\User; // Ajout pour récupérer les emails DAF/Directrice
class AvanceController extends AdminController
{
@ -28,354 +29,634 @@ class AvanceController extends AdminController
return $this->render_template('avances/avance', $data);
}
public function fetchAvanceData()
private function isAdmin($user)
{
return in_array($user['group_name'], ['Conseil', 'Direction']);
}
private function isCommerciale($user)
{
return in_array($user['group_name'], ['COMMERCIALE']);
}
private function isCaissier($user)
{
return in_array($user['group_name'], ['Caissier']);
}
private function buildActionButtons($value, $isAdmin, $isOwner)
{
$buttons = '';
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['avance_id'] . ')" title="Modifier">'
. '<i class="fa fa-pencil"></i></button> ';
}
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')" title="Supprimer">'
. '<i class="fa fa-trash"></i></button> ';
}
if (in_array('viewAvance', $this->permission) && !$isAdmin) {
$buttons .= '<a href="#" data-order-id="' . $value['avance_id'] . '" class="btn btn-default btn-view" title="Voir">'
. '<i class="fa fa-eye"></i></a>';
}
return $buttons;
}
private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCaissier, $buttons)
{
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
if ($isAdmin) {
return [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
} elseif ($isCommerciale || $isCaissier) {
return [
$value['avance_id'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
}
return [];
}
private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
{
helper(['url', 'form']);
$Avance = new Avance();
$product = new Products();
$result = ['data' => []];
$data = $Avance->getAllAvanceData();
$data = $Avance->$methodName();
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissier']);
$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'];
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
// Boutons d’action
$buttons = '';
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">'
. '<i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>';
$isOwner = $users['id'] === $value['user_id'];
$buttons = $this->buildActionButtons($value, $isAdmin, $isOwner);
$row = $this->buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCaissier, $buttons);
if (!empty($row)) {
$result['data'][] = $row;
}
}
return $this->response->setJSON($result);
}
public function fetchAvanceData()
{
return $this->fetchAvanceDataGeneric('getAllAvanceData');
}
public function fetchAvanceBecameOrder()
{
return $this->fetchAvanceDataGeneric('getAllAvanceData1');
}
public function fetcheExpiredAvance()
{
return $this->fetchAvanceDataGeneric('getAllAvanceData2');
}
/**
* Méthode pour vérifier et envoyer des emails d'alerte 3 jours avant deadline
* À exécuter via CRON job quotidiennement
*/
public function checkDeadlineAlerts()
{
try {
$Avance = new Avance();
$Products = new Products();
// Récupérer toutes les avances actives non converties en commandes
$avances = $Avance->getAvancesNearDeadline(3); // 3 jours avant deadline
if (!empty($avances)) {
foreach ($avances as $avance) {
// Vérifier si l'email n'a pas déjà été envoyé pour cette avance
if (!$this->hasEmailBeenSent($avance['avance_id'])) {
$this->sendDeadlineAlert($avance, $Products);
$this->markEmailAsSent($avance['avance_id']);
}
}
}
if (in_array('viewAvance', $this->permission) && !$isAdmin) {
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>';
return $this->response->setJSON([
'success' => true,
'messages' => 'Vérification des alertes terminée',
'alerts_sent' => count($avances)
]);
} catch (\Exception $e) {
log_message('error', "Erreur vérification deadline: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la vérification des deadlines'
]);
}
}
/**
* Envoyer un email d'alerte au DAF et à la Directrice
*/
private function sendDeadlineAlert($avance, $Products)
{
try {
$email = \Config\Services::email();
// Configuration email (à adapter selon votre config)
$email->setFrom('noreply@yourcompany.com', 'Système de Gestion des Avances');
// Récupérer les emails du DAF et de la Directrice
$recipients = $this->getDAFAndDirectriceEmails($avance['store_id']);
$email->setTo($recipients);
$email->setSubject('⚠️ ALERTE: Avance arrive à échéance dans 3 jours');
// Récupérer le nom du produit
$productName = $Products->getProductNameById($avance['product_id']);
// Calcul des jours restants
$deadline = new \DateTime($avance['deadline']);
$today = new \DateTime();
$daysRemaining = $today->diff($deadline)->days;
// Corps de l'email
$message = $this->buildEmailMessage($avance, $productName, $daysRemaining);
$email->setMessage($message);
// Envoyer l'email
if ($email->send()) {
log_message('info', "Email d'alerte envoyé pour l'avance ID: " . $avance['avance_id']);
return true;
} else {
log_message('error', "Échec envoi email pour avance ID: " . $avance['avance_id'] . " - " . $email->printDebugger());
return false;
}
if ($isAdmin) {
$row = [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
// dd($row);die;
$result['data'][] = $row;
} catch (\Exception $e) {
log_message('error', "Erreur envoi email alerte: " . $e->getMessage());
return false;
}
}
/**
* Récupérer les emails du DAF et de la Directrice
*/
private function getDAFAndDirectriceEmails($store_id)
{
$User = new User();
$emails = [];
// Récupérer les utilisateurs avec les rôles DAF et Direction pour le store donné
$dafUsers = $User->getUsersByRole('DAF', $store_id);
$directionUsers = $User->getUsersByRole('Direction', $store_id);
// Extraire les emails
foreach ($dafUsers as $user) {
if (!empty($user['email'])) {
$emails[] = $user['email'];
}
if ($isCommerciale || $isCaissier) {
$row = [
$value['avance_id'],
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
$result['data'][] = $row;
}
foreach ($directionUsers as $user) {
if (!empty($user['email'])) {
$emails[] = $user['email'];
}
}
// Si aucun email trouvé, utiliser des emails par défaut (à configurer)
if (empty($emails)) {
$emails = [
'daf@yourcompany.com',
'direction@yourcompany.com'
];
}
return array_unique($emails); // Éviter les doublons
}
return $this->response->setJSON($result);
/**
* Construire le message de l'email
*/
private function buildEmailMessage($avance, $productName, $daysRemaining)
{
$typeAvance = strtoupper($avance['type_avance']);
$deadlineFormatted = date('d/m/Y', strtotime($avance['deadline']));
$avanceDateFormatted = date('d/m/Y à H:i', strtotime($avance['avance_date']));
$amountDueFormatted = number_format($avance['amount_due'], 0, ',', ' ') . ' FCFA';
$urgencyClass = $daysRemaining <= 1 ? 'style="color: red; font-weight: bold;"' : '';
return "
<html>
<head>
<style>
.container { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
.header { background-color: #f8f9fa; padding: 20px; text-align: center; border-radius: 5px; }
.alert { background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; margin: 20px 0; border-radius: 5px; }
.details { background-color: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 5px; }
.urgent { color: #dc3545; font-weight: bold; }
.footer { margin-top: 30px; padding: 15px; background-color: #e9ecef; border-radius: 5px; font-size: 12px; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h2>⚠️ ALERTE DEADLINE AVANCE</h2>
</div>
<div class='alert'>
<p><strong " . $urgencyClass . ">Une avance arrive à échéance dans {$daysRemaining} jour(s) !</strong></p>
</div>
<div class='details'>
<h3>Détails de l'avance :</h3>
<ul>
<li><strong>ID Avance :</strong> #{$avance['avance_id']}</li>
<li><strong>Type d'avance :</strong> {$typeAvance}</li>
<li><strong>Client :</strong> {$avance['customer_name']}</li>
<li><strong>Téléphone :</strong> {$avance['customer_phone']}</li>
<li><strong>Adresse :</strong> {$avance['customer_address']}</li>
<li><strong>CIN :</strong> {$avance['customer_cin']}</li>
<li><strong>Produit :</strong> {$productName}</li>
<li><strong>Montant restant dû :</strong> <span class='urgent'>{$amountDueFormatted}</span></li>
<li><strong>Date avance :</strong> {$avanceDateFormatted}</li>
<li><strong>Date limite :</strong> <span class='urgent'>{$deadlineFormatted}</span></li>
</ul>
</div>
<div class='alert'>
<p><strong>Action requise :</strong></p>
<p>Veuillez contacter le client pour régulariser le paiement avant l'échéance ou prendre les mesures appropriées.</p>
</div>
<div class='footer'>
<p>Cet email a été généré automatiquement par le système de gestion des avances.</p>
<p>Date d'envoi : " . date('d/m/Y à H:i') . "</p>
</div>
</div>
</body>
</html>
";
}
/**
* Vérifier si un email a déjà été envoyé pour cette avance
*/
private function hasEmailBeenSent($avance_id)
{
$db = \Config\Database::connect();
$query = $db->query("SELECT id FROM email_alerts WHERE avance_id = ? AND alert_type = 'deadline_3days'", [$avance_id]);
return $query->getNumRows() > 0;
}
/**
* Marquer l'email comme envoyé
*/
private function markEmailAsSent($avance_id)
{
$db = \Config\Database::connect();
$data = [
'avance_id' => $avance_id,
'alert_type' => 'deadline_3days',
'sent_date' => date('Y-m-d H:i:s'),
'status' => 'sent'
];
$db->table('email_alerts')->insert($data);
}
public function createAvance()
{
// $this->verifyRole('createAvance');
$data['page_title'] = $this->pageTitle;
$this->verifyRole('createAvance');
$Avance = new Avance();
$Products = new Products();
$Notification = New NotificationController();
if ($this->request->getMethod() !== 'post') {
return $this->response->setJSON([
'success' => false,
'messages' => 'Méthode non autorisée'
]);
}
if ($this->request->getMethod() === 'post') {
try {
$session = session();
$users = $session->get('user');
$users = $session->get('user');
$Avance = new Avance();
$Products = new Products();
$Notification = new NotificationController();
$validation = \Config\Services::validation();
$validation->setRules([
'customer_name_avance' => 'required|min_length[2]',
'customer_phone_avance' => 'required',
'customer_address_avance' => 'required',
'customer_cin_avance' => 'required',
'id_product' => 'required|numeric',
'avance_amount' => 'required|numeric|greater_than[0]',
'type_avance' => 'required|in_list[terre,mere]'
]);
if (!$validation->withRequest($this->request)->run()) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Données invalides: ' . implode(', ', $validation->getErrors())
]);
}
$avance_date = date('Y-m-d H:i:s');
// Calcul automatique de la deadline selon le type d'avance
$type_avance = $this->request->getPost('type_avance');
if ($type_avance === 'terre') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +15 days'));
} elseif ($type_avance === 'mere') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +2 months'));
} else {
$deadline = null; // fallback si jamais
}
$data = [
'type_avance' => $type_avance,
'customer_name' => $this->request->getPost('customer_name_avance'),
'customer_address' => $this->request->getPost('customer_address_avance'),
'customer_phone' => $this->request->getPost('customer_phone_avance'),
'customer_cin' => $this->request->getPost('customer_cin_avance'),
'avance_date' => date('Y-m-d'),
'user_id' => $users['id'],
'avance_date' => $avance_date,
'deadline' => $deadline,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'product_id' => $this->request->getPost('id_product'),
'gross_amount' => (float)$this->request->getPost('gross_amount'),
'product_id' => (int)$this->request->getPost('id_product'),
'gross_amount' => (float)$this->request->getPost('gross_amount'),
'avance_amount' => (float)$this->request->getPost('avance_amount'),
'amount_due' => (float)$this->request->getPost('amount_due'),
'is_order' => (float)0,
'active' => 1,
'is_order' => 0,
'active' => 1,
];
if($avance_id = $Avance->createAvance($data)){
$product = new Products();
$product->update((int)$this->request->getPost('id_product'), ['product_sold' => 1]);
$Notification->createNotification('Une avance a été créé', "Conseil",(int)$users['store_id'], 'avances');
return $this->response->setJSON([
'success' => true,
'messages' => 'Avance créé avec succès !'
]);
}
else{
if ($avance_id = $Avance->createAvance($data)) {
$Products->update($data['product_id'], ['product_sold' => 1]);
$Notification->createNotification(
'Une nouvelle avance a été créée',
"Conseil",
(int)$users['store_id'],
'avances'
);
return $this->response->setJSON([
'success' => true,
'messages' => 'Avance créée avec succès !',
'avance_id' => $avance_id
]);
} else {
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la création de l\'avance'
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur création avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur est survenue lors de la création d\une avance !'
'messages' => 'Une erreur interne est survenue'
]);
}
}
}
public function updateAvance(int $id)
{
$this->verifyRole('updateAvance');
$data['page_title'] = $this->pageTitle;
$Products = new Products();
$Avance = new Avance();
$session = session();
$users = $session->get('user');
if ($this->request->getMethod() === 'post') {
if ($this->request->getMethod() !== 'post') {
return $this->response->setJSON([
'success' => false,
'messages' => 'Méthode non autorisée'
]);
}
try {
$session = session();
$users = $session->get('user');
$Avance = new Avance();
$Products = new Products();
$Orders = new Orders();
$Company = new Company();
$Notification = new NotificationController();
$validation = \Config\Services::validation();
$validation->setRules([
'customer_name_avance_edit' => 'required|min_length[2]',
'customer_phone_avance_edit' => 'required',
'customer_address_avance_edit' => 'required',
'customer_cin_avance_edit' => 'required',
'id_product_edit' => 'required|numeric',
'avance_amount_edit' => 'required|numeric|greater_than[0]',
'type_avance_edit' => 'required|in_list[terre,mere]'
]);
if (!$validation->withRequest($this->request)->run()) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Données invalides: ' . implode(', ', $validation->getErrors())
]);
}
// Récupérer la date de création actuelle de l'avance pour recalculer deadline
$currentAvance = $Avance->find($id);
if (!$currentAvance) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Avance introuvable.'
]);
}
$avance_date = $currentAvance['avance_date'];
// Calcul automatique deadline selon le type d'avance
$type_avance = $this->request->getPost('type_avance_edit');
if ($type_avance === 'terre') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +15 days'));
} elseif ($type_avance === 'mere') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +2 months'));
} else {
$deadline = null;
}
$data = [
'customer_name' => $this->request->getPost('customer_name_avance_edit'),
'customer_address'=> $this->request->getPost('customer_address_avance_edit'),
'customer_phone' => $this->request->getPost('customer_phone_avance_edit'),
'customer_cin' => $this->request->getPost('customer_cin_avance_edit'),
'gross_amout' => $this->request->getPost('gros_amount_edit'),
'avance_amount' => (int)$this->request->getPost('avance_amount_edit'),
'amount_due' => (int)$this->request->getPost('amount_due_edit'),
'product_id' => $this->request->getPost('id_product_edit'),
'type_avance' => $type_avance,
'customer_name' => $this->request->getPost('customer_name_avance_edit'),
'customer_address' => $this->request->getPost('customer_address_avance_edit'),
'customer_phone' => $this->request->getPost('customer_phone_avance_edit'),
'customer_cin' => $this->request->getPost('customer_cin_avance_edit'),
'gross_amount' => (float)$this->request->getPost('gross_amount_edit'),
'avance_amount' => (float)$this->request->getPost('avance_amount_edit'),
'amount_due' => (float)$this->request->getPost('amount_due_edit'),
'product_id' => (int)$this->request->getPost('id_product_edit'),
'deadline' => $deadline,
];
$bill_no = 'BILPR-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
$Company = new Company();
$company = $Company->getCompanyData(1);
$company['vat_charge_value'] > 0;
$service_charge_rate = $company['service_charge_value'];
$vat_charge_rate = $company['vat_charge_value'];
$gross_amount = $this->request->getPost('gross_amount_edit');
$vat_charge = ($gross_amount / 100) * $vat_charge_rate;
$amount_due = (int)$this->request->getPost('amount_due_edit');
$product_id = (array)$this->request->getPost('id_product_edit');
$amount_due = $data['amount_due'];
if ($amount_due <= 0) {
$Orders = new Orders();
$data = [
'bill_no' => $bill_no,
'customer_name' => $this->request->getPost('customer_name_avance_edit'),
'customer_address'=> $this->request->getPost('customer_address_avance_edit'),
'customer_phone' => $this->request->getPost('customer_phone_avance_edit'),
'customer_cin' => $this->request->getPost('customer_cin_avance_edit'),
'gross_amout' => $gross_amount,
'net_amount' => $gross_amount,
'date_time' => date('Y-m-d H:i:s'),
$bill_no = 'BILPR-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
$company = $Company->getCompanyData(1);
$service_charge_rate = $company['service_charge_value'] ?? 0;
$vat_charge_rate = $company['vat_charge_value'] ?? 0;
$gross_amount = $data['gross_amount'];
$vat_charge = ($gross_amount / 100) * $vat_charge_rate;
$order_data = [
'bill_no' => $bill_no,
'customer_name' => $data['customer_name'],
'customer_address' => $data['customer_address'],
'customer_phone' => $data['customer_phone'],
'customer_cin' => $data['customer_cin'],
'gross_amount' => $gross_amount,
'net_amount' => $gross_amount,
'date_time' => date('Y-m-d H:i:s'),
'service_charge_rate' => $service_charge_rate,
'vat_charge_rate' => $vat_charge_rate,
'vat_charge' => $vat_charge,
'discount' => (int) 0,
'paid_status' => 1,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'amount_value' => $gross_amount,
'rate_value' => $gross_amount,
'vat_charge_rate' => $vat_charge_rate,
'vat_charge' => $vat_charge,
'discount' => 0,
'paid_status' => 1,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'amount_value' => $gross_amount,
'rate_value' => $gross_amount,
];
$data1 = ['is_order' => 1];
if($Orders->create($data,$product_id)){
$Avance->updateAvance($id,$data1);
$Notification = New NotificationController();
$Notification->createNotification('Une commande a été créé', "Conseil",(int)$users['store_id'], 'orders');
$product_id = [$data['product_id']];
if ($Orders->create($order_data, $product_id)) {
$Avance->updateAvance($id, ['is_order' => 1]);
$Notification->createNotification(
'Une avance a été convertie en commande',
"Conseil",
(int)$users['store_id'],
'orders'
);
return $this->response->setJSON([
'success' => true,
'messages' => 'success. Avance convertie en commande avec succès.'
'messages' => 'Avance convertie en commande avec succès.'
]);
}
else{
} else {
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la convertion de l\'avance'
'messages' => 'Erreur lors de la conversion de l\'avance en commande'
]);
}
}
else{
}
} else {
if ($Avance->updateAvance($id, $data)) {
return $this->response->setJSON([
'success' => true,
'messages' => 'success', 'Avance mise à jour avec succès.'
'messages' => 'Avance mise à jour avec succès.'
]);
} else {
return $this->response->setJSON([
'success' => true,
'messages' => 'Errors', 'Une erreur est survenue lors de la mise à jour.'
'success' => false,
'messages' => 'Erreur lors de la mise à jour de l\'avance.'
]);
}
}
}
} catch (\Exception $e) {
log_message('error', "Erreur mise à jour avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue'
]);
}
}
public function removeAvance()
{
$this->verifyRole('deleteAvance');
$avance_id = $this->request->getPost('avance_id');
$product_id = $this->request->getPost('product_id');
$response = [];
try {
$avance_id = $this->request->getPost('avance_id');
$product_id = $this->request->getPost('product_id');
$Avance = new Avance();
if ($Avance->removeAvance($avance_id)) {
$product = new Products();
$product->update($product_id, ['product_sold' => 0]);
$response['success'] = true;
$response['messages'] = "Avance supprimée avec succès. Ce produit peut désormais être réservé à nouveau.";
} else {
$response['success'] = false;
$response['messages'] = "une erreur est survenue lors de la suppression d'une avance";
}
return $this->response->setJSON($response);
}
public function fetchSingleAvance($avance_id)
{
$this->verifyRole('updateAvance');
try {
$avanceModel = new Avance();
$data = $avanceModel->fetchSingleAvance($avance_id);
return $this->response->setJSON($data);
}
catch (\Throwable $th) {
log_message('error', "Erreur lors de la récupération d'une avance: " . $th->getMessage());
if (!$avance_id || !$product_id) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Données manquantes pour la suppression'
]);
}
return $this->response
->setStatusCode(500)
->setJSON(['error' => 'Une erreur interne est survenue. Lors de la création d\'une avance']);
}
}
$Avance = new Avance();
$Products = new Products();
public function fetchAvanceBecameOrder()
{
helper(['url', 'form']);
$Avance = new Avance();
$product = new Products();
$result = ['data' => []];
$data = $Avance->getAllAvanceData1();
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissier']);
foreach ($data as $key => $value) {
$isOwner = $users['id'] === $value['user_id'];
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
// Boutons d’action
$buttons = '';
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">'
. '<i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>';
}
if (in_array('viewAvance', $this->permission) && !$isAdmin) {
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>';
}
if ($isAdmin) {
$row = [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
// dd($row);die;
$result['data'][] = $row;
}
if ($isCommerciale || $isCaissier) {
$row = [
$value['avance_id'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
$result['data'][] = $row;
if ($Avance->removeAvance($avance_id)) {
$Products->update($product_id, ['product_sold' => 0]);
return $this->response->setJSON([
'success' => true,
'messages' => "Avance supprimée avec succès. Le produit peut être réservé à nouveau."
]);
} else {
return $this->response->setJSON([
'success' => false,
'messages' => "Erreur lors de la suppression de l'avance"
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur suppression avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue'
]);
}
return $this->response->setJSON($result);
}
public function fetcheExpiredAvance()
public function fetchSingleAvance($avance_id)
{
helper(['url', 'form']);
$Avance = new Avance();
$product = new Products();
$result = ['data' => []];
$data = $Avance->getAllAvanceData2();
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissier']);
foreach ($data as $key => $value) {
$isOwner = $users['id'] === $value['user_id'];
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
// Boutons d’action
$buttons = '';
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">'
. '<i class="fa fa-pencil"></i></button>';
}
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>';
}
if (in_array('viewAvance', $this->permission) && !$isAdmin) {
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>';
}
if ($isAdmin) {
$row = [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
// dd($row);die;
$result['data'][] = $row;
$this->verifyRole('updateAvance');
try {
if (!$avance_id || !is_numeric($avance_id)) {
return $this->response->setStatusCode(400)->setJSON([
'error' => 'ID d\'avance invalide'
]);
}
if ($isCommerciale || $isCaissier) {
$row = [
$value['avance_id'],
$product->getProductNameById($value['product_id']),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
$result['data'][] = $row;
$avanceModel = new Avance();
$data = $avanceModel->fetchSingleAvance($avance_id);
if (!$data) {
return $this->response->setStatusCode(404)->setJSON([
'error' => 'Avance non trouvée'
]);
}
}
return $this->response->setJSON($result);
return $this->response->setJSON($data);
} catch (\Exception $e) {
log_message('error', "Erreur récupération avance: " . $e->getMessage());
return $this->response->setStatusCode(500)->setJSON([
'error' => 'Erreur interne lors de la récupération de l\'avance'
]);
}
}
}
}

52
app/Controllers/BaseController.php

@ -1,58 +1,22 @@
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* Class BaseController
*
* BaseController provides a convenient place for loading components
* and performing functions that are needed by all your controllers.
* Extend this class in any new controllers:
* class Home extends BaseController
*
* For security be sure to declare any new methods as protected or private.
*/
abstract class BaseController extends Controller
class BaseController extends Controller
{
/**
* Instance of the main Request object.
*
* @var CLIRequest|IncomingRequest
*/
protected $request;
/**
* An array of helpers to be loaded automatically upon
* class instantiation. These helpers will be available
* to all other controllers that extend BaseController.
*
* @var list<string>
*/
protected $helpers = [];
/**
* Be sure to declare properties for any property fetch you initialized.
* The creation of dynamic property is deprecated in PHP 8.2.
*/
// protected $session;
/**
* @return void
*/
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
public function initController(\CodeIgniter\HTTP\RequestInterface $request,
\CodeIgniter\HTTP\ResponseInterface $response,
\Psr\Log\LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
// Preload any models, libraries, etc, here.
helper('alerts');
// E.g.: $this->session = \Config\Services::session();
if (function_exists('checkDeadlineAlerts')) {
checkDeadlineAlerts();
}
}
}

428
app/Controllers/ProductCOntroller.php

@ -74,81 +74,79 @@ class ProductCOntroller extends AdminController
public function fetchProductData()
{
// Initialize the response array
$result = ['data' => []];
$Products = new Products();
$Stores = new Stores();
function convertString($name)
{
return "$name";
}
// Fetch product data from the model
$data = $Products->getProductData(); // Ensure this method exists in your ProductModel
$data = $Products->getProductData();
foreach ($data as $key => $value) {
// Fetch store data
$store_data = $Stores->getStoresData($value['store_id']); // Ensure this method exists in your StoreModel
$store_data['name'] = $value['store_id'] == 0 ? "TOUS" : $Stores->getStoresData($value['store_id'])["name"];
// Construct buttons
// Gestion du nom du magasin
if ($value['store_id'] == 0) {
$store_name = "TOUS";
} else {
$store_info = $Stores->getStoresData($value['store_id']);
$store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu";
}
// Disponibilité
$availability = ($value['qty'] > 0) ? '<span class="label label-success">En stock</span>' : '<span class="label label-danger">Rupture</span>';
// Construction des boutons
$buttons = '';
if (in_array('updateProduct', $this->permission ?? [])) {
$buttons .= '<a href="' . base_url('products/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>';
}
if (in_array('deleteProduct', $this->permission ?? [])) {
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
}
if (in_array('updateProduct', $this->permission ?? [])) {
$buttons .= ' <a href="ventes/' . $value['id'] . '" class="btn btn-default"><i class="fa fa-image"></i></a>';
}
if (in_array('updateProduct', $this->permission ?? [])) {
$buttons .= ' <button class="btn btn-default" onclick="generateQrPdf(' . $value["id"] . ')"><i class="fa fa-qrcode"></i></button>';
}
if (in_array('viewProduct', $this->permission ?? [])) {
$buttons .= " <a href='/ventes/show/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-eye'></i></a>";
}
if (in_array('assignStore', $this->permission ?? [])) {
$buttons .=
'<button type="button" class="btn btn-info assignbtn" title="Assigner sur un magasin" data-magasin="' . $store_data['name'] . '" data-products-id="' . $value["id"] . '" data-toggle="modal" data-target="#assignStoreModal">
'<button type="button" class="btn btn-info assignbtn" title="Assigner sur un magasin" data-magasin="' . $store_name . '" data-products-id="' . $value["id"] . '" data-toggle="modal" data-target="#assignStoreModal">
<i class="fa fa-forward"></i>
</button>';
}
// Image HTML
$img = '<img src="' . base_url('assets/images/product_image/' . $value['image']) . '" alt="' . $value['name'] . '" class="img-circle" width="50" height="50" />';
// Availability Status
$availability = ($value['availability'] == 1) ? '<span class="label label-success">Disponible</span>' : '<span class="label label-warning">Indisponible</span>';
// Quantity Status
$qty_status = '';
if ($value['qty'] <= 10 && $value['qty'] > 0) {
$qty_status = '<span class="label label-warning">Low!</span>';
} elseif ($value['product_sold'] == false) {
$qty_status = '<span class="label label-danger">Rupture de stock!</span>';
}
// Populate the result data
$result['data'][] = [
$img,
$value['sku'],
$imagePath = 'assets/images/product_image/' . $value['image'];
$imageHtml = $value['image'] ?
'<img src="' . base_url($imagePath) . '" width="50" height="50" class="img-thumbnail">' :
'<div class="no-image">Aucune image</div>';
// Préparer les données pour DataTables (7 colonnes)
$result['data'][$key] = [
$value['image'],
convertString($value['sku']),
$value['name'],
number_format($value['prix_vente'], 0, ',', ' '),
$store_data['name'] ?? 'Unknown Store',
$availability,
$value['price'],
$store_name,
$availability, // <-- ici la disponibilité ajoutée
$buttons
];
}
// Return JSON response
return $this->response->setJSON($result);
}
public function create()
{
@ -356,233 +354,147 @@ class ProductCOntroller extends AdminController
return $this->response->setJSON($response);
}
public function createByExcel()
{
$this->verifyRole("createProduct");
// 1) Récupération et validation du fichier
$file = $this->request->getFile('excel_product');
if (!$file || !$file->isValid() || $file->hasMoved()) {
return $this->response->setJSON([
'success' => false,
'messages' => "Aucun fichier valide reçu"
]);
}
$ext = strtolower($file->getClientExtension());
if (! in_array($ext, ['xls', 'xlsx'])) {
return $this->response->setJSON([
'success' => false,
'messages' => "Seuls les fichiers xls/xlsx sont autorisés"
]);
}
try {
// 2) Chargement du fichier Excel
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file->getTempName());
$sheet = $spreadsheet->getActiveSheet();
// 3) Lecture des données brutes et mapping des en-têtes
$allRows = $sheet->toArray(null, true, true, true);
if (count($allRows) < 2) {
return $this->response->setJSON([
'success' => false,
'messages' => "Le fichier ne contient aucune donnée"
]);
}
$headerRow = array_shift($allRows);
$map = [];
foreach ($headerRow as $col => $heading) {
$h = mb_strtolower(trim($heading));
$h = str_replace(['’', '‘', '“', '”'], '\'', $h);
$h = iconv('UTF-8', 'ASCII//TRANSLIT', $h);
$h = preg_replace('/[^a-z0-9_]/', '_', $h);
$h = preg_replace('/_+/', '_', $h);
$h = trim($h, '_');
switch ($h) {
case 'designation':
case 'nom':
$map[$col] = 'name'; break;
case 'n_serie':
$map[$col] = 'sku'; break;
case 'prix_ar':
$map[$col] = 'prix_vente'; break;
case 'prix_d_achat':
case 'prix_dachat':
case 'prixd_achat':
$map[$col] = 'price'; break;
case 'marque':
$map[$col] = 'marque'; break;
case 'description':
$map[$col] = 'description'; break;
case 'code_moteur':
case 'n_moteur':
$map[$col] = 'numero_de_moteur'; break;
case 'chassis':
case 'chasis':
$map[$col] = 'chasis'; break;
case 'date_arrivage':
case 'date_d_arivage':
$map[$col] = 'date_arrivage'; break;
case 'puissance':
$map[$col] = 'puissance'; break;
case 'availability':
case 'disponibilite':
$map[$col] = 'availability'; break;
case 'piece':
case 'piece_manquant':
$map[$col] = 'is_piece'; break;
case 'cle':
$map[$col] = 'cler'; break;
case 'categories':
case 'categorie_id':
$map[$col] = 'categorie_id'; break;
case 'etat':
case 'etats':
$map[$col] = 'etats'; break;
case 'magasin':
$map[$col] = 'store_id'; break;
case 'info_manquekit':
case 'infomanquekit':
$map[$col] = 'infoManquekit'; break;
case 'info':
case 'info_piece':
$map[$col] = 'info'; break;
case 'info_manque':
$map[$col] = 'infoManque'; break;
case 'image':
case 'image_s':
$map[$col] = 'image'; break;
default:
// Non mappé
break;
}
}
// 4) Extraction des images intégrées, si présent
$imagesMap = [];
foreach ($sheet->getDrawingCollection() as $drawing) {
if ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
$coord = $drawing->getCoordinates();
$extImg = pathinfo($drawing->getPath(), PATHINFO_EXTENSION);
$name = uniqid('img_') . ".$extImg";
$dir = FCPATH . 'assets/images/product_image/';
if (! is_dir($dir)) {
mkdir($dir, 0777, true);
}
file_put_contents($dir . $name, file_get_contents($drawing->getPath()));
$imagesMap[$coord] = $name;
}
}
// 5) Chargement des modèles
$ProductsModel = new \App\Models\Products();
$ProductsModel->skipValidation(true);
$BrandsModel = new \App\Models\Brands();
$CatModel = new \App\Models\Category();
$countInserted = 0;
// 6) Boucle sur chaque ligne de données
foreach ($allRows as $rowIndex => $row) {
$data = [];
// Lecture des cellules formatées pour chaque champ mappé
foreach ($map as $col => $field) {
$cellValue = $sheet
->getCell($col . ($rowIndex + 2))
->getFormattedValue();
$data[$field] = trim((string)$cellValue);
public function createByExcel()
{
$this->verifyRole("createProduct");
try {
$file = $this->request->getFile('excel_product');
if (!$file || !$file->isValid()) {
return $this->response->setJSON([
'success' => false,
'messages' => "Fichier invalide ou non reçu"
]);
}
if (empty($data['name'])) {
continue; // champ désignation vide
$ext = strtolower($file->getClientExtension());
if (!in_array($ext, ['xls', 'xlsx'])) {
return $this->response->setJSON([
'success' => false,
'messages' => "Seuls les fichiers Excel (.xls, .xlsx) sont acceptés"
]);
}
// Conversion du prix AR : capture tous les groupes de chiffres
if (! empty($data['prix_vente'])) {
preg_match_all('/\d+/', $data['prix_vente'], $matches);
$digits = implode('', $matches[0]); // ex. ["2","000","000"] => "2000000"
$data['prix_vente'] = intval($digits);
} else {
$data['prix_vente'] = 0;
$spreadsheet = IOFactory::load($file->getTempName());
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
if (count($rows) <= 1) {
return $this->response->setJSON([
'success' => false,
'messages' => "Le fichier ne contient pas de données"
]);
}
// Valeurs par défaut
$data['qty'] = 1;
$data['product_sold'] = 0;
$data['availability'] = isset($data['availability'])
? (strtolower($data['availability']) === 'oui' ? 1 : 0)
: 0;
$data['is_piece'] = isset($data['is_piece'])
? (strtolower($data['is_piece']) === 'oui' ? 1 : 0)
: 0;
$data['cler'] = isset($data['cler'])
? (strtolower($data['cler']) === 'oui' ? 1 : 0)
: 1;
$data['etats'] = isset($data['etats'])
? (strtolower($data['etats']) === 'kit' ? 1 : 0)
: 1;
// Association d’image si présente
foreach ($map as $col => $field) {
if ($field === 'image') {
$coordImg = $col . ($rowIndex + 2);
if (isset($imagesMap[$coordImg])) {
$data['image'] = $imagesMap[$coordImg];
// Récupérer les en-têtes
$headers = array_shift($rows);
$headers = array_map('strtolower', $headers);
// Mapping des colonnes Excel vers les champs de la base
$columnMapping = [
'n° série' => 'sku',
'marque' => 'marque',
'désignation' => 'name',
'fournisseur' => 'info', // À adapter selon votre besoin
'date d\'arrivage' => 'date_arivage',
'n° moteur' => 'numero_de_moteur',
'châssis' => 'chasis',
'puissance' => 'puissance',
'clé' => 'cler',
'prix d\'achat' => 'prix_vente',
'prix ar' => 'price',
'catégories' => 'categorie_id',
'magasin' => 'store_id',
'disponibilité' => 'availability',
'état' => 'etats',
'pièce manquant' => 'infoManque'
];
$ProductsModel = new Products();
$BrandsModel = new Brands();
$StoresModel = new Stores();
$CategoryModel = new Category();
$countInserted = 0;
foreach ($rows as $row) {
if (empty(array_filter($row))) continue; // Ignore les lignes vides
$data = [
'is_piece' => 0,
'product_sold' => 0,
'qty' => 1
];
// Mapper chaque colonne
foreach ($headers as $index => $header) {
$header = trim($header);
if (isset($columnMapping[$header]) && isset($row[$index])) {
$field = $columnMapping[$header];
$value = trim($row[$index]);
// Traitements spécifiques pour certains champs
switch ($field) {
case 'marque':
// Chercher ou créer la marque
$brand = $BrandsModel->where('name', $value)->first();
if (!$brand) {
$brandId = $BrandsModel->insert(['name' => $value, 'active' => 1]);
} else {
$brandId = $brand['id'];
}
$data[$field] = $brandId;
break;
case 'store_id':
// Gestion du magasin
if ($value == 'TOUS') {
$data[$field] = 0;
} else {
$store = $StoresModel->where('name', $value)->first();
$data[$field] = $store ? $store['id'] : 0;
}
break;
case 'date_arivage':
// Convertir la date Excel en format MySQL
if (is_numeric($value)) {
$data[$field] = date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($value));
} else {
$data[$field] = date('Y-m-d', strtotime($value));
}
break;
case 'price':
// Nettoyer "1 900 000 Ar" → 1900000.00
$cleanedValue = str_replace(['Ar', ' ', ','], '', $value);
$data[$field] = (float)$cleanedValue;
break;
default:
$data[$field] = $value;
}
}
break;
}
}
// Gestion des clés étrangères
if (! empty($data['marque'])) {
$data['marque'] = $BrandsModel->getOrCreateIdByName($data['marque']);
}
if (! empty($data['categorie_id'])) {
$labels = array_map('trim', explode(',', $data['categorie_id']));
$catIds = [];
foreach ($labels as $label) {
if ($label !== '') {
$catIds[] = $CatModel->getOrCreateIdByName($label);
// Insertion
if (!empty($data['name'])) {
if ($ProductsModel->insert($data)) {
$countInserted++;
}
}
$data['categorie_id'] = $catIds;
}
if (! empty($data['store_id'])) {
// store_id depuis la session
$Store = new Stores();
$store = $Store->getIdStoreByName($data['store_id']);
$data['store_id'] = $store;
}
// Insertion
$id = $ProductsModel->insert($data);
if ($id !== false) {
$countInserted++;
}
return $this->response->setJSON([
'success' => true,
'messages' => "$countInserted produits importés avec succès"
]);
} catch (\Exception $e) {
return $this->response->setJSON([
'success' => false,
'messages' => "Erreur lors de l'import: " . $e->getMessage()
]);
}
// 7) Notification et réponse
$Notification = new \App\Controllers\NotificationController();
$user = session()->get('user');
$Notification->createNotification(
"$countInserted produits ajoutés",
"COMMERCIALE",
(int)$user['store_id'],
"avances"
);
return $this->response->setJSON([
'success' => true,
'messages' => "Produits importés avec succès ($countInserted)"
]);
} catch (\Exception $e) {
log_message('error', $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => "Erreur pendant l’import : " . $e->getMessage()
]);
}
}
}

58
app/Controllers/ReportController.php

@ -22,39 +22,35 @@ class ReportController extends AdminController
{
$this->verifyRole('viewReports');
$data['page_title'] = $this->pageTitle;
// Get the current year or the selected year from the form
$today_year = date('Y');
if ($this->request->getPost('select_year')) {
$today_year = $this->request->getPost('select_year');
}
// // Fetch order data and years
// Fetch order data and years
$Reports = new Reports();
$Orders = new Orders();
$Store = new Stores();
$parking_data = $Reports->getOrderData($today_year);
$data['report_years'] = $Reports->getOrderYear();
// // Process the parking data and calculate total amounts
// Process the parking data and calculate total amounts
$final_parking_data = [];
foreach ($parking_data as $month => $orders) {
$total_amount_earned = 0; // Initialize the total amount for the month
// If there are orders for this month, sum the gross_amount
if (!empty($orders)) {
foreach ($orders as $order) {
// Cast gross_amount to float and add to the total
$total_amount_earned += (float) $order['net_amount'];
}
}
// Assign the total amount for the month to the final array
$final_parking_data[$month] = $total_amount_earned;
}
// //data for the camembert
// Data for the camembert (pie chart)
$paymentModes = $Orders->getPaymentModes();
$total_mvola1 = $paymentModes->total_mvola1;
$total_mvola2 = $paymentModes->total_mvola2;
@ -68,57 +64,59 @@ class ReportController extends AdminController
$totalOrders = $Orders->getTotalOrders();
$totalAmountPerPaymentModes = ["MVOLA" => $total_mvola, "Espece" => $total_espece, "Virement Bancaire" => $total_banque];
$totalOrdersCount = (int) $totalOrders->total_orders;
// // dd($paymentModes);
$labels = [];
$totals = [];
if ($totalOrdersCount > 0) {
foreach ($totalAmountPerPaymentModes as $mode => $total) {
$labels[] = $mode;
$totals[] = $total;
}
}
$data['labels'] = json_encode($labels);
$data['totals'] = json_encode($totals);
// // prepare data for product chart
// Prepare data for product chart
$OrderItem = new OrderItems();
$productTable = $OrderItem->getAllSoldProductToday();
$product_sold = (int) $productTable->total_product_sold;
$unsold_product = (int) $productTable->total_unsold_product;
// Définir les labels et les valeurs pour le Pie Chart
$labels1 = ["Produits vendus", "Produits non vendus"];
$totals2 = [$product_sold, $unsold_product];
// Encoder les données en JSON pour le Pie Chart
$data['labels_product'] = json_encode($labels1);
$data['totals_product'] = json_encode($totals2);
// // Prepare data for the view
// Prepare data for the view
$data['selected_year'] = $today_year;
$data['company_currency'] = $this->companycurrency();
$data['results'] = $final_parking_data;
// //data for the camember in dashboard
// Data for the camembert in dashboard
$totalStoreOrder = $Orders->getTotalOrderPerStore();
$totalOrders = $Orders->getTotalOrders();
$totalOrdersCount = (int) $totalOrders->total_orders;
// Initialisation des variables pour éviter l'erreur "Undefined variable"
$labelStore = [];
$totalPerStore = [];
foreach ($totalStoreOrder as $totalOrdersInStore) {
$storeList = $Store->getStoreById($totalOrdersInStore->store_id);
$labelStore[] = $storeList->name ?? 'Inconnu';
$totalPerStore[] = ((int) $totalOrdersInStore->total / $totalOrdersCount) * 100;
}
$data['labelStore'] = json_encode($labelStore);
$data['totalPerStore'] = json_encode($totalPerStore);
// Load the view
// return view('reports/index', $this->data);
return $this->render_template('reports/index', $data);
}
private function companycurrency()
{
return 'AR'; // Replace with your actual logic for company currency

25
app/Controllers/TestDeadline.php

@ -0,0 +1,25 @@
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class TestDeadline extends Controller
{
public function index()
{
// Charger le helper qui contient ta fonction
helper('alerts'); // si ton fichier s'appelle alerts_helper.php
// 🔹 Supprimer le cache 24h pour forcer l'exécution
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt';
if (file_exists($cacheFile)) {
unlink($cacheFile);
}
// Lancer la vérification
checkDeadlineAlerts();
echo "✅ Test de l'envoi d'alertes terminé.";
}
}

41
app/Controllers/UserController.php

@ -350,34 +350,25 @@ class UserController extends AdminController
return redirect()->to('/users');
}
public function delete($id)
{
$this->verifyRole('deleteUser');
if ($id) {
// Check if the form has been submitted with confirmation
if ($this->request->getPost('confirm')) {
$usersModel = new Users(); // Ensure Users model is loaded
// supression utilisateur
$delete = $usersModel->delete($id);
$data['page_title'] = $this->pageTitle;
if ($delete) {
session()->setFlashdata('success', 'Supprimé avec succès');
return redirect()->to('/users');
} else {
session()->setFlashdata('error', 'Une erreur est survenue !!');
return redirect()->to("/users/delete/{$id}");
}
} else {
// If no confirmation yet, load the delete confirmation view
$data = [
'id' => $id,
'page_title' => $this->pageTitle
];
// die(var_dump($data));
return $this->render_template('users/delete', $data); // Use CodeIgniter 4's view function
}
public function delete($id)
{
$this->verifyRole('deleteUser');
if (!$id) {
return $this->response->setJSON(['success' => false, 'message' => 'ID manquant']);
}
$usersModel = new Users();
$delete = $usersModel->delete($id);
if ($delete) {
return $this->response->setJSON(['success' => true, 'message' => 'Supprimé avec succès']);
} else {
return $this->response->setJSON(['success' => false, 'message' => 'Échec de la suppression']);
}
}

145
app/Controllers/test.html

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
<style>
body { font-size: 14px; font-family: Arial, sans-serif; }
.invoice-container {
max-width: 350px; /* Réduire la largeur du cadre */
margin: 20px auto;
padding: 20px;
border: 2px solid #007bff; /* Bordure plus visible */
border-radius: 10px;
background: #f0f8ff; /* Couleur de fond plus douce */
}
.invoice-header {
background: #007bff;
color: white;
text-align: center;
font-size: 18px;
font-weight: bold;
padding: 10px;
border-radius: 10px 10px 0 0;
}
.invoice-footer {
background: #343a40;
color: white;
text-align: center;
font-size: 14px;
padding: 10px;
border-radius: 0 0 10px 10px;
margin-top: 12px;
}
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #e9ecef; }
p, strong { color: #333; }
</style>
</head>
<body onload="window.print();">
<div class="invoice-container">
<div class="invoice-header">
' . esc($company_info['company_name']) . '
</div>
<p><strong>Facture ID:</strong> ' . esc($order_data['bill_no']) . '</p>
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
<div style="display: flex;align-items: center;justify-content: space-around;margin-bottom: 3%;">
<div>
<p>Signature du client</p>
</div>
<div>
<p>Signature du commercial</p>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Produit</th>
<th>Qté</th>
<th>Prix</th>
<th>Total</th>
</tr>
</thead>
<tbody>';
foreach ($orders_items as $item) {
$product_data = $Products->getProductData($item['product_id']);
$html .= '<tr>
<td>' . esc($product_data['name']) . '</td>
<td>' . esc($item['qty']) . '</td>
<td>' . number_format((float)$item['rate'], 2, '.', ' ') . '</td>
<td>' . number_format((float)$item['amount'], 2, '.', ' ') . '</td>
</tr>';
}
$html .= ' </tbody>
</table>
<table class="table">
<tr>
<th>Total:</th>
<td>' . number_format((float)$order_data['gross_amount'], 2, '.', ' ') . '</td>
</tr>';
if (!empty($order_data['service_charge']) && (float)$order_data['service_charge'] > 0) {
$html .= '<tr>
<th>Frais de service:</th>
<td>' . number_format((float)$order_data['service_charge'], 2, '.', ' ') . '</td>
</tr>';
}
if (!empty($order_data['vat_charge']) && (float)$order_data['vat_charge'] > 0) {
$html .= '<tr>
<th>TVA:</th>
<td>' . number_format((float)$order_data['vat_charge'], 2, '.', ' ') . '</td>
</tr>';
}
$html .= '<tr>
<th>Réduction:</th>
<td>' . number_format((float)$order_data['discount'], 2, '.', ' ') . '</td>
</tr>
<tr>
<th>Total à payer:</th>
<td><strong>' . number_format((float)$order_data['net_amount'], 2, '.', ' ') . '</strong></td>
</tr>
<tr>
<th>Statut:</th>
<td>' . $paid_status . '</td>
</tr>';
// Vérification et ajout des informations de paiement
if (!empty($order_data['order_payment_mode'])) {
$html .= '<tr>
<th>Mode de paiement:</th>
<td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td>
</tr>';
}
if (!empty($order_data['tranche_1'])) {
$html .= '<tr>
<th>Tranche 1:</th>
<td><strong>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . '</strong></td>
</tr>';
}
if (!empty($order_data['tranche_2'])) {
$html .= '<tr>
<th>Tranche 2:</th>
<td><strong>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . '</strong></td>
</tr>';
}
$html .= '</table>
<div class="invoice-footer">
Merci pour votre achat !<br>
<strong>' . esc($company_info['company_name']) . '</strong>
</div>
</div>
</body>
</html>

176
app/Helpers/alerts_helper.php

@ -0,0 +1,176 @@
<?php
use App\Models\Users;
use App\Models\Avance;
use App\Models\AlertMail;
function checkDeadlineAlerts()
{
log_message('info', "=== DÉBUT checkDeadlineAlerts ===");
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt';
// On enlève la vérification de 24h pour s'assurer que le script tourne quotidiennement
file_put_contents($cacheFile, time());
$avanceModel = new Avance();
$alertMailModel = new AlertMail();
$usersModel = new Users();
$today = date('Y-m-d');
log_message('info', "Date du jour: {$today}");
// Modification pour vérifier les avances dans 0-3 jours
$avances = $avanceModel
->where('DATE(deadline) >=', $today) // Inclut le jour même
->where('DATE(deadline) <=', date('Y-m-d', strtotime('+3 days')))
->where('active', 1)
->findAll();
log_message('info', "Nombre d'avances trouvées (0-3 jours): " . count($avances));
$users = $usersModel->select('users.email, users.firstname, users.lastname')
->join('user_group', 'user_group.user_id = users.id')
->join('groups', 'groups.id = user_group.group_id')
->where('groups.group_name', 'DAF')
->findAll();
log_message('info', "Utilisateurs DAF trouvés: " . json_encode($users));
$emails = array_column($users, 'email');
log_message('info', "Emails extraits: " . json_encode($emails));
if (empty($emails)) {
log_message('error', "Aucun email DAF trouvé");
$db = \Config\Database::connect();
$allGroups = $db->query("SELECT DISTINCT group_name FROM groups")->getResult();
log_message('info', "Groupes disponibles: " . json_encode($allGroups));
return;
}
foreach ($avances as $avance) {
$deadline = date('Y-m-d', strtotime($avance['deadline']));
$daysLeft = (int) ceil((strtotime($deadline) - strtotime($today)) / 86400);
log_message('info', "Avance ID: {$avance['avance_id']}, Deadline: {$deadline}, Jours restants: {$daysLeft}");
// Modification des types d'alerte pour 0, 1, 2, 3 jours
$alertType = match($daysLeft) {
3 => 'deadline_3_days',
2 => 'deadline_2_days',
1 => 'deadline_1_day',
0 => 'deadline_today',
default => null,
};
if ($alertType === null) {
log_message('info', "Pas d'alerte nécessaire pour {$daysLeft} jours restants");
continue;
}
log_message('info', "Type d'alerte: {$alertType}");
// Vérification si l'alerte a déjà été envoyée
$alreadySent = $alertMailModel
->where('avance_id', $avance['avance_id'])
->where('alert_type', $alertType)
->first();
if ($alreadySent) {
log_message('info', "Alerte déjà envoyée pour avance_id={$avance['avance_id']} type={$alertType}");
continue;
}
// Message modifié pour inclure le cas du jour même
$urgencyText = $daysLeft === 0 ? "ÉCHÉANCE AUJOURD'HUI" : "{$daysLeft} jour(s) restant(s)";
$message = "
<h3>⚠️ URGENT : Avance approchant de la deadline</h3>
<p><strong>ID Avance :</strong> {$avance['avance_id']}</p>
<p><strong>Client :</strong> {$avance['customer_name']}</p>
<p><strong>Montant avance :</strong> " . number_format($avance['avance_amount'], 0, ',', ' ') . " Ar</p>
<p><strong>Montant dû :</strong> " . number_format($avance['amount_due'], 0, ',', ' ') . " Ar</p>
<p><strong>Deadline :</strong> {$deadline}</p>
<p><strong>Statut :</strong> <span style='color: red; font-weight: bold;'>{$urgencyText}</span></p>
<p><strong>Téléphone client :</strong> {$avance['customer_phone']}</p>
<p><strong>Adresse client :</strong> {$avance['customer_address']}</p>
<hr>
<p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p>
";
$emailsSent = 0;
foreach ($emails as $to) {
log_message('info', "Tentative d'envoi email à: {$to}");
$subject = $daysLeft === 0
? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI"
: "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)";
if (sendEmailInBackground($to, $subject, $message)) {
$emailsSent++;
log_message('info', "Email envoyé avec succès à: {$to}");
} else {
log_message('error', "Échec envoi email à: {$to}");
}
}
if ($emailsSent > 0) {
log_message('info', "Insertion alerte pour avance_id={$avance['avance_id']} avec type {$alertType}");
$alertMailModel->insert([
'avance_id' => $avance['avance_id'],
'alert_type' => $alertType,
'sent_date' => date('Y-m-d H:i:s'),
'status' => 'sent',
'created_at' => date('Y-m-d H:i:s'),
]);
} else {
log_message('error', "Aucun email envoyé pour avance_id={$avance['avance_id']} avec type {$alertType}");
}
}
log_message('info', "=== FIN checkDeadlineAlerts ===");
}
function sendEmailInBackground($to, $subject, $message)
{
try {
log_message('info', "Préparation envoi email à: {$to}");
$email = \Config\Services::email();
$config = [
'protocol' => 'smtp',
'SMTPHost' => 'smtp.gmail.com',
'SMTPUser' => 'rey342505@gmail.com',
'SMTPPass' => 'loirqovmfuxnasrm',
'SMTPPort' => 587,
'SMTPCrypto' => 'tls',
'mailType' => 'html',
'charset' => 'utf-8',
'newline' => "\r\n"
];
$email->initialize($config);
$email->setFrom('rey342505@gmail.com', 'Système Motorbike - Alertes Avances');
$email->setTo($to);
$email->setSubject($subject);
$email->setMessage($message);
log_message('info', "Configuration email terminée, tentative d'envoi...");
if (!$email->send()) {
$debugInfo = $email->printDebugger(['headers']);
log_message('error', "Erreur email à {$to}: " . print_r($debugInfo, true));
return false;
}
log_message('info', "Email envoyé avec succès à: {$to}");
return true;
} catch (\Exception $e) {
log_message('error', "Exception email à {$to}: " . $e->getMessage());
return false;
}
}

21
app/Models/AlertMail.php

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class AlertMail extends Model
{
protected $table = 'email_alerts';
protected $primaryKey = 'id';
protected $allowedFields = [
'avance_id',
'alert_type',
'sent_date',
'status',
'created_at',
];
// Pas de fonction checkDeadlineAlerts ici !
}

326
app/Models/Avance.php

@ -2,104 +2,120 @@
namespace App\Models;
use CodeIgniter\Model;
class Avance extends Model{
/**
* table name
* @var string
*/
class Avance extends Model {
protected $table = 'avances';
protected $primaryKey = 'avance_id';
protected $allowedFields = [
'avance_amount', 'avance_date','user_id',
'customer_name',
'customer_address',
'customer_phone',
'customer_cin','gross_amount','amount_due','product_id','is_order','active','store_id'];
'customer_name', 'customer_address', 'customer_phone', 'customer_cin',
'gross_amount','amount_due','product_id','is_order','active','store_id',
'type_avance', 'deadline' // Ajout du champ type et deadline
];
public function createAvance( array $data) {
try {
return $this->insert($data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de l\'ajout de l\'avance : ' . $e->getMessage());
return false;
}
}
public function createAvance(array $data) {
try {
// Si la date de création n'est pas définie, on prend aujourd'hui
if (empty($data['avance_date'])) {
$data['avance_date'] = date('Y-m-d');
}
public function updateAvance(int $id, array $data) {
if ($id <= 0) {
log_message('error', 'ID invalide pour la mise à jour du recouvrement : ' . $id);
return false;
// Calcul de la deadline en fonction du type
if (!empty($data['type'])) {
if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
} elseif (strtolower($data['type']) === 'avance sur mer') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +2 months'));
}
}
try {
return $this->update($id, $data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la mise à jour de l\'avance : ' . $e->getMessage());
return false;
return $this->insert($data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de l\'ajout de l\'avance : ' . $e->getMessage());
return false;
}
}
public function updateAvance(int $id, array $data) {
if ($id <= 0) {
log_message('error', 'ID invalide pour la mise à jour du recouvrement : ' . $id);
return false;
}
try {
// Recalcul de la deadline si le type change
if (!empty($data['type']) && !empty($data['avance_date'])) {
if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
} elseif (strtolower($data['type']) === 'avance sur mer') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +2 months'));
}
}
return $this->update($id, $data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la mise à jour de l\'avance : ' . $e->getMessage());
return false;
}
public function getAllAvanceData(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
}
// 📌 Le reste de tes fonctions restent inchangées
public function getAllAvanceData(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',1)
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
else{
if($id){
try {
return $this->where('user_id',$id)
try {
return $this
->where('is_order',0)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function fetchSingleAvance(int $avance_id){
return $this->where('avance_id',$avance_id)
->first();
return $this->where('avance_id',$avance_id)->first();
}
public function removeAvance(int $avance_id){
@ -112,159 +128,165 @@ class Avance extends Model{
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
try {
return $this->select('
SUM(avance_amount) AS ta,
')
->where('is_order', 0)
->get()
->getRowObject();
return $this->select('SUM(avance_amount) AS ta')
->where('is_order', 0)
->get()
->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
return false;
}
}
else{
} else {
try {
return $this->select('
SUM(avance_amount) AS ta,
')
->where('is_order', 0)
->where('store_id',$users['store_id'])
->get()
->getRowObject();
return $this->select('SUM(avance_amount) AS ta')
->where('is_order', 0)
->where('store_id',$users['store_id'])
->get()
->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
return false;
}
}
}
public function getAllAvanceData1(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',1)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',1)
->where('active',1)
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
else{
if($id){
try {
return $this->where('user_id',$id)
try {
return $this
->where('is_order',1)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',1)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function getAllAvanceData2(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',0)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',0)
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
else{
if($id){
try {
return $this->where('user_id',$id)
try {
return $this
->where('is_order',0)
->where('active',0)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',0)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',0)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC') ->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',0)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function checkExpiredAvance()
{
public function checkExpiredAvance() {
$now = date('Y-m-d');
$avances = $this->where('active', '1')
->where('DATE_ADD(avance_date, INTERVAL 15 DAY) <', $now)
->where('deadline <', $now)
->findAll();
if (!empty($avances)) {
$productModel = new Products();
foreach ($avances as $avance) {
// Mettre l'avance à expirée
$this->update($avance['id'], ['active' => '0']);
// Remettre le produit disponible
$this->update($avance['avance_id'], ['active' => '0']);
$productModel->update($avance['product_id'], ['product_sold' => 0]);
}
}
}
}
/**
* Récupérer les avances qui arrivent à échéance dans X jours
*/
public function getAvancesNearDeadline($days = 3)
{
$alertDate = date('Y-m-d', strtotime("+{$days} days"));
return $this->select('avances.*, users.store_id')
->join('users', 'users.id = avances.user_id')
->where('avances.is_order', 0)
->where('avances.active', 1)
->where('avances.amount_due >', 0)
->where('DATE(avances.deadline)', $alertDate)
->findAll();
}
}

89
app/Views/attributes/addvalue.php

@ -173,27 +173,46 @@
$(document).ready(function() {
$("#attributeNav").addClass('active');
// initialize the datatable
// initialisation de la DataTable en français
manageTable = $('#manageTable').DataTable({
'ajax': base_url + 'attributes/fetchAttributeValueData/' + <?php echo $attribute_data['id']; ?>,
'order': []
'order': [],
"language": {
"sProcessing": "Traitement en cours...",
"sSearch": "Rechercher&nbsp;:",
"sLengthMenu": "Afficher _MENU_ &eacute;l&eacute;ments",
"sInfo": "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
"sInfoEmpty": "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
"sInfoFiltered": "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
"sInfoPostFix": "",
"sLoadingRecords": "Chargement en cours...",
"sZeroRecords": "Aucun &eacute;l&eacute;ment &agrave; afficher",
"sEmptyTable": "Aucune donn&eacute;e disponible dans le tableau",
"oPaginate": {
"sFirst": "Premier",
"sPrevious": "Pr&eacute;c&eacute;dent",
"sNext": "Suivant",
"sLast": "Dernier"
},
"oAria": {
"sSortAscending": ": activer pour trier la colonne par ordre croissant",
"sSortDescending": ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
// submit the create from
// soumission du formulaire de création
$("#createForm").unbind('submit').on('submit', function() {
var form = $(this);
// remove the text-danger
$(".text-danger").remove();
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(), // /converting the form data into array and sending it to server
data: form.serialize(),
dataType: 'json',
success: function(response) {
@ -201,20 +220,15 @@
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>');
// hide the modal
$("#addModal").modal('hide');
// reset the form
$("#createForm")[0].reset();
$("#createForm .form-group").removeClass('has-error').removeClass('has-success');
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
@ -229,8 +243,8 @@
});
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>');
}
}
@ -242,31 +256,24 @@
});
// edit function
// id => attribute value id
// fonction de modification
function editFunc(id) {
$.ajax({
url: base_url + 'attributes/fetchAttributeValueById/' + id,
type: 'post',
dataType: 'json',
success: function(response) {
console.log(response);
$("#edit_attribute_value_name").val(response.value);
// submit the edit from
$("#updateForm").unbind('submit').bind('submit', function() {
var form = $(this);
// remove the text-danger
$(".text-danger").remove();
$.ajax({
url: form.attr('action') + '/' + id,
type: form.attr('method'),
data: form.serialize(), // /converting the form data into array and sending it to server
data: form.serialize(),
dataType: 'json',
success: function(response) {
@ -274,18 +281,14 @@
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>');
// hide the modal
$("#editModal").modal('hide');
// reset the form
$("#updateForm .form-group").removeClass('has-error').removeClass('has-success');
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
@ -300,8 +303,8 @@
});
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>');
}
}
@ -315,22 +318,18 @@
});
}
// remove functions
// fonction de suppression
function removeFunc(id) {
if (id) {
$("#removeForm").on('submit', function() {
var form = $(this);
// remove the text-danger
$(".text-danger").remove();
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: {
attribute_value_id: id
},
data: { attribute_value_id: id },
dataType: 'json',
success: function(response) {
@ -338,18 +337,16 @@
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>');
// hide the modal
$("#removeModal").modal('hide');
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'<button type="button" class="close" data-dismiss="alert" aria-label="Fermer"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>');
}
}
@ -359,4 +356,4 @@
});
}
}
</script>
</script>

831
app/Views/avances/avance.php

@ -96,11 +96,29 @@
<div class="modal-content">
<form id="create_avance_form">
<div class="modal-header">
<h4 class="modal-title">Ajouter une avance</h4>
<h4 class="modal-title"> Ajouter une avance </h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="row">
<!-- type d'avance -->
<!-- <div class="form-group col-md-6">
<label for="id_product" class="form-label">Type d'avance</label>
<select name="id_product" id="id_product" class="form-control " required>
<option value="" disabled selected>Sélectionnez un type d'avance </option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
</select>
</div> -->
<div class="form-group col-md-6">
<label for="type_avance" class="form-label">Type d'avance</label>
<select name="type_avance" id="type_avance" class="form-control" required>
<option value="" disabled selected>Sélectionnez un type d'avance</option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
</select>
</div>
<!-- Nom client -->
<div class="form-group col-md-6">
<label>Nom du client</label>
@ -129,15 +147,15 @@
<div class="row">
<!-- Produit -->
<div class="form-group col-md-6">
<label for="id_product" class="form-label">Produit</label>
<select name="id_product" id="id_product" class="form-control " onchange="getProductDataCreate()" required>
<option value="">Sélectionnez un produit</option>
<?php foreach($products as $p): ?>
<option value="<?= $p['id'] ?>" <?= $p['product_sold'] ? 'disabled' : '' ?>>
<?= esc($p['name']) ?> <?= $p['product_sold'] ? '(Rupture)' : '' ?>
</option>
<?php endforeach; ?>
</select>
<label for="id_product" class="form-label">Produit</label>
<select name="id_product" id="id_product" class="form-control" onchange="getProductDataCreate()" required>
<option value="">Sélectionnez un produit</option>
<?php foreach($products as $p): ?>
<option value="<?= $p['id'] ?>" <?= $p['product_sold'] ? 'disabled' : '' ?>>
<?= esc($p['name']) ?> <?= $p['product_sold'] ? '(Rupture)' : '' ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Prix brut -->
<div class="form-group col-md-6">
@ -182,6 +200,16 @@
</div>
<div class="modal-body">
<div class="row">
<!-- Type d'avance -->
<div class="form-group col-md-6">
<label for="type_avance_edit" class="form-label">Type d'avance</label>
<select name="type_avance_edit" id="type_avance_edit" class="form-control" required>
<option value="" disabled>Sélectionnez un type d'avance</option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
</select>
</div>
<!-- Nom client -->
<div class="form-group col-md-6">
<label>Nom du client</label>
@ -249,362 +277,493 @@
</div>
<?php endif;?>
<?php if (in_array('deleteAvance', $user_permission)): ?>
<!-- remove brand modal -->
<div class="modal fade" tabindex="-1" role="dialog" id="removeModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Supprimer cette avance</h4>
<!-- remove brand modal -->
<div class="modal fade" tabindex="-1" role="dialog" id="removeModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Supprimer cette avance</h4>
</div>
<form role="form" action="<?php echo base_url('avances/deleteAvance') ?>" method="post" id="removeForm">
<input type="hidden" name="avance_id" value="">
<input type="hidden" name="product_id" value="">
<div class="modal-body">
<p>Voulez-vous vraiment supprimer ?</p>
</div>
<form role="form" action="<?php echo base_url('avances/deleteAvance') ?>" method="post" id="removeForm">
<div class="modal-body">
<p>Voulez-vous vraiment supprimer ?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
<button type="submit" class="btn btn-primary">Oui</button>
</div>
</form>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
<button type="submit" class="btn btn-primary">Oui</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<?php endif; ?>
<script>
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;
$(document).ready(function() {
$('#avance_menu').addClass("active");
$('.select2').select2();
// 📌 Configuration langue FR
var datatableLangFr = {
lengthMenu: "Afficher _MENU_ enregistrements par page",
zeroRecords: "Aucun résultat trouvé",
info: "Affichage de _START_ à _END_ sur _TOTAL_ enregistrements",
infoEmpty: "Aucun enregistrement disponible",
infoFiltered: "(filtré depuis _MAX_ enregistrements au total)",
search: "Rechercher :",
paginate: {
first: "Premier",
last: "Dernier",
next: "Suivant",
previous: "Précédent"
}
};
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<?php endif; ?>
// 📌 Fonction pour initialiser la DataTable
function initAvanceTable(url, columns) {
if ($.fn.DataTable.isDataTable('#avanceTable')) {
$('#avanceTable').DataTable().destroy();
}
return $('#avanceTable').DataTable({
ajax: url,
columns: columns,
language: datatableLangFr
});
}
<script>
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;
$(document).ready(function() {
$('#avance_menu').addClass("active");
$('.select2').select2();
<?php if ($isAdmin):?>
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceData',
columns: [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{ title: "Prix" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
// 🔄 FONCTION DE MISE À JOUR DYNAMIQUE DE LA DATATABLE
function refreshDataTable() {
if (typeof manageTable !== 'undefined' && manageTable) {
manageTable.ajax.reload(null, false); // Recharger seulement les données de la table
}
}
<?php if ($isAdmin): ?>
var adminColumns = [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{ title: "Prix" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ 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 () {
$('#avanceTable').DataTable().destroy();
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceBecameOrder',
columns: [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{ title: "Prix" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
]
});
});
<?php endif; ?>
];
$('#avance_expired').on('click', function () {
$('#avanceTable').DataTable().destroy();
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchExpiredAvance',
columns: [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{ title: "Prix" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
]
});
});
var manageTable = initAvanceTable('fetchAvanceData', adminColumns);
$('#avance_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceBecameOrder', adminColumns);
});
$('#avance_no_order').on('click', function () {
$('#avanceTable').DataTable().destroy();
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceData',
columns: [
{ title: "Client" },
{ title: "Téléphone" },
{ title: "Adresse" },
{ title: "Produit" },
{ title: "Prix" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
]
});
});
<?php endif; ?>
<?php if ($isCaissier || $isCommerciale):?>
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceData',
columns: [
{ title: "#" },
{ title: "Produit" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
$('#avance_expired').on('click', function () {
manageTable = initAvanceTable('fetchExpiredAvance', adminColumns);
});
$('#avance_no_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceData', adminColumns);
});
<?php endif; ?>
<?php if ($isCaissier || $isCommerciale): ?>
var userColumns = [
{ title: "#" },
{ title: "Produit" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ 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 () {
$('#avanceTable').DataTable().destroy();
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceBecameOrder',
columns: [
{ title: "#" },
{ title: "Produit" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
]
});
});
<?php endif; ?>
];
$('#avance_no_order').on('click', function () {
$('#avanceTable').DataTable().destroy();
manageTable = $('#avanceTable').DataTable({
ajax: 'fetchAvanceData',
columns: [
{ title: "#" },
{ title: "Produit" },
{ title: "Avance" },
{ title: "Reste à payer" },
{ title: "Date" }
<?php if (in_array('updateAvance', $user_permission) || in_array('deleteAvance', $user_permission)): ?>
,{ title: "Action", orderable: false, searchable: false }
<?php endif; ?>
]
});
});
<?php endif; ?>
var manageTable = initAvanceTable('fetchAvanceData', userColumns);
$('#avance_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceBecameOrder', userColumns);
});
$('#avance_no_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceData', userColumns);
});
<?php endif; ?>
// Création AJAX
// ✅ CRÉATION avec actualisation automatique
$('#create_avance_form').on('submit', function(e) {
e.preventDefault();
const $form = $(this);
$.post('/avances/createAvance', $form.serialize(), function(res) {
if (res.success === true) {
manageTable.ajax.reload(null, false);
$("#messages").html(
`<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>`
);
// Cacher le modal de création
$("#createModal").modal('hide');
$form[0].reset();
} else {
$("#createModal").modal('hide');
$("#messages").html(
`<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ${res.messages}
</div>`
);
e.preventDefault();
const $form = $(this);
var brut = parseFloat($('#gross_amount').val()) || 0;
var avance = parseFloat($('#avance_amount').val()) || 0;
var minAvance = brut * 0.25;
if (avance < minAvance) {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong>
L'avance doit être au minimum de 25% du prix du produit (${minAvance.toFixed(2)} Ar).
</div>
`);
return;
}
}, 'json');
// Désactiver le bouton de soumission
$form.find('button[type="submit"]').prop('disabled', true).text('Enregistrement...');
$.post('/avances/createAvance', $form.serialize(), function(res) {
if (res.success === true) {
$("#messages").html(`
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>
`);
$("#createModal").modal('hide');
$form[0].reset(); // Reset le formulaire
// 🔄 MISE À JOUR DYNAMIQUE après ajout
refreshDataTable();
// Auto-masquer le message après 3 secondes
setTimeout(function() {
$("#messages .alert").fadeOut();
}, 3000);
} else {
$("#createModal").modal('hide');
$("#messages").html(`
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ${res.messages}
</div>
`);
// Réactiver le bouton en cas d'erreur
$form.find('button[type="submit"]').prop('disabled', false).text('Enregistrer');
}
}, 'json');
});
});
// 🔥 SUPPRESSION avec actualisation automatique
window.removeFunc = function(id, product_id) {
$('#removeModal').modal('show');
$('#removeForm input[name="avance_id"]').val(id);
$('#removeForm input[name="product_id"]').val(product_id);
};
$('#removeForm').on('submit', function(e) {
e.preventDefault();
var form = $(this);
var submitButton = form.find('button[type="submit"]');
// Désactiver le bouton
submitButton.prop('disabled', true).text('Suppression...');
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
dataType: 'json',
success: function(response) {
if (response.success === true) {
$('#removeModal').modal('hide');
$("#messages").html(`
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${response.messages}
</div>
`);
// 🔄 MISE À JOUR DYNAMIQUE après suppression
refreshDataTable();
// Auto-masquer le message après 3 secondes
setTimeout(function() {
$("#messages .alert").fadeOut();
}, 3000);
} else {
$('#removeModal').modal('hide');
$("#messages").html(`
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ${response.messages}
</div>
`);
}
},
error: function(xhr, status, error) {
console.log('Erreur AJAX:', error);
$('#removeModal').modal('hide');
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Une erreur est survenue lors de la suppression.
</div>
`);
},
complete: function() {
submitButton.prop('disabled', false).text('Oui');
}
});
return false;
});
// Récupère prix pour création
function getProductDataCreate(){
// Réinitialiser le modal à la fermeture
$('#removeModal').on('hidden.bs.modal', function() {
$('#removeForm')[0].reset();
$('#removeForm button[type="submit"]').prop('disabled', false).text('Oui');
});
}); // Fin document.ready
// --- Fonctions utilitaires ---
function getProductDataCreate() {
var id = $('#id_product').val();
if(!id){ brutCreate=0; $('#gross_amount,#amount_due').val(''); return; }
else{
$.post(base_url+'orders/getProductValueById',{product_id:id}, function(r){
brutCreate = parseFloat(r.prix_vente)||0;
$('#gross_amount').val(brutCreate);
$('#amount_due').val(brutCreate);
updateDueCreate();
},'json');
if (!id) {
brutCreate = 0;
$('#gross_amount,#amount_due,#avance_amount').val('');
return;
}
}
$.post(base_url + 'orders/getProductValueById', { product_id: id }, function(r) {
brutCreate = parseFloat(r.prix_vente) || 0;
$('#gross_amount').val(brutCreate.toFixed(2));
var avance25 = brutCreate * 0.25;
$('#avance_amount').val(avance25.toFixed(2));
$('#amount_due').val((brutCreate - avance25).toFixed(2));
updateDueCreate();
}, 'json');
}
function getProductDataUpdate(){
function updateDueCreate() {
var av = parseFloat($('#avance_amount').val()) || 0;
var brutAmount = parseFloat($('#gross_amount').val()) || 0;
$('#amount_due').val(Math.max(brutAmount - av, 0));
}
function getProductDataUpdate() {
var id = $('#id_product_edit').val();
if(!id){ brutCreate=0; $('#gross_amount_edit,#amount_due_edit').val(''); return; }
else{
$.post(base_url+'orders/getProductValueById',{product_id:id}, function(r){
brutCreate = parseFloat(r.prix_vente)||0;
$('#gross_amount_edit').val(brutCreate);
$('#amount_due_edit').val(brutCreate);
updateDueCreate();
},'json');
console.log('getProductDataUpdate appelée avec ID:', id); // Pour déboguer
if (!id) {
brutEdit = 0;
$('#gross_amount_edit,#amount_due_edit,#avance_amount_edit').val('');
return;
}
}
function updateDueCreate(){
var av = parseFloat($('#avance_amount').val())||0;
var brutAmount = parseFloat($('#gross_amount').val())||0;
$('#amount_due').val(Math.max(brutAmount-av,0));
}
// Prépare et affiche modal édition
function editFunc(id) {
// Récupération des données de l'avance
$.getJSON(base_url + 'avances/fetchSingleAvance/' + id, function(r) {
// Préremplissage du modal
$('#customer_name_avance_edit').val(r.customer_name);
$('#customer_phone_avance_edit').val(r.customer_phone);
$('#customer_adress_avance_edit').val(r.customer_adress);
$('#customer_cin_avance_edit').val(r.customer_cin);
$('#gross_amount_edit').val(r.gross_amount);
$('#avance_amount_edit').val(r.avance_amount);
$('#amount_due_edit').val(r.amount_due);
const product_id = r.product_id;
const productSelect = $("#id_product_edit");
const optionExists = productSelect.find('option').filter(function() {
return $(this).val().trim().toLowerCase() === product_id.trim().toLowerCase();
}).length > 0;
if (optionExists) {
productSelect.val(product_id);
} else {
productSelect.val("");
}
// Calcul du montant brut initial
brutEdit = parseFloat(r.avance_amount) + parseFloat(r.amount_due);
// Affiche le modal
$('#updateModal').modal('show');
$('#update_avance_form').off('submit');
$('#update_avance_form').on('submit', function(e) {
e.preventDefault();
const actionUrl = base_url + 'avances/updateAvance/' + id;
// Envoi AJAX
$.post(actionUrl, $(this).serialize(), function(res) {
if (res.success) {
$('#updateModal').modal('hide');
manageTable.ajax.reload(null, false);
$("#messages").html(
`<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>`
);
} else {
$("#messages").html(
`<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>`
);
alert(res.messages || 'Erreur lors de la modification.');
$.ajax({
url: base_url + 'orders/getProductValueById',
type: 'POST',
data: { product_id: id },
dataType: 'json',
success: function(r) {
console.log('Données produit reçues:', r); // Pour déboguer
brutEdit = parseFloat(r.prix_vente) || 0;
$('#gross_amount_edit').val(brutEdit.toFixed(2));
var avance25 = brutEdit * 0.25;
$('#avance_amount_edit').val(avance25.toFixed(2));
$('#amount_due_edit').val((brutEdit - avance25).toFixed(2));
updateDueEdit();
},
error: function(xhr, status, error) {
console.log('Erreur lors de la récupération des données produit:', error);
}
}, 'json');
});
});
}
function updateDueEdit(){
var av = parseFloat($('#avance_amount_edit').val())||0;
$('#amount_due_edit').val(Math.max(brutEdit-av,0).toFixed(2));
}
function removeFunc(id, product_id) {
// Affiche correctement le modal
$('#removeModal').modal('show');
if (id) {
// On évite d'attacher plusieurs fois le submit handler
$('#removeForm').off('submit').on('submit', function(e) {
e.preventDefault();
var form = $(this);
function updateDueEdit() {
var av = parseFloat($('#avance_amount_edit').val()) || 0;
$('#amount_due_edit').val(Math.max(brutEdit - av, 0).toFixed(2));
}
// Supprime les anciens messages d'erreur
$(".text-danger").remove();
// ✅ MODIFICATION avec mise à jour dynamique - CORRIGÉ
function editFunc(id) {
// Réinitialiser d'abord le formulaire
$('#update_avance_form')[0].reset();
$.getJSON(base_url + 'avances/fetchSingleAvance/' + id, function(r) {
console.log('Données récupérées:', r); // Pour déboguer
// Pré-remplir le formulaire de modification avec les BONS IDs
$('#avance_id_edit').val(r.id || id); // Le champ caché pour l'ID
$('#customer_name_avance_edit').val(r.customer_name || '');
$('#customer_phone_avance_edit').val(r.customer_phone || '');
$('#customer_address_avance_edit').val(r.customer_address || r.customer_adress || '');
$('#customer_cin_avance_edit').val(r.customer_cin || '');
$('#type_avance_edit').val(r.type_avance || '');
$('#gross_amount_edit').val(r.gross_amount || '');
$('#avance_amount_edit').val(r.avance_amount || '');
$('#amount_due_edit').val(r.amount_due || '');
// 🔥 CORRECTION PRINCIPALE - Sélection du produit avec toutes les variantes possibles
var productId = r.product_id || r.id_product || r.productId || r.idProduct;
console.log('Product ID trouvé:', productId);
console.log('Options disponibles:', $('#id_product_edit option').length);
brutEdit = parseFloat(r.gross_amount || 0); // Utiliser gross_amount directement
// Réinitialiser le bouton
$('#update_avance_form button[type="submit"]').prop('disabled', false).text('Modifier');
// Ouvrir le modal
$('#updateModal').modal('show');
// 🔥 SÉLECTION DU PRODUIT après ouverture complète du modal
$('#updateModal').on('shown.bs.modal', function() {
if (productId) {
console.log('Tentative de sélection du produit ID:', productId);
// Méthode 1: Sélection standard
$('#id_product_edit').val(productId);
console.log('Après sélection standard:', $('#id_product_edit').val());
// Méthode 2: Si la sélection standard ne marche pas
if ($('#id_product_edit').val() != productId) {
$('#id_product_edit option').each(function() {
if ($(this).val() == productId) {
$(this).prop('selected', true);
console.log('Produit sélectionné via boucle:', $(this).val(), $(this).text());
}
});
}
// Déclencher l'événement change pour mettre à jour le prix
$('#id_product_edit').trigger('change');
console.log('Valeur finale du select:', $('#id_product_edit').val());
}
// Détacher l'événement pour éviter les appels multiples
$('#updateModal').off('shown.bs.modal');
});
// 🔥 Détacher tous les anciens événements et attacher le nouveau
$('#update_avance_form').off('submit').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $submitBtn = $form.find('button[type="submit"]');
var avance = parseFloat($('#avance_amount_edit').val()) || 0;
var minAvance = brutEdit * 0.25;
// Validation 25%
if (avance < minAvance) {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong>
L'avance doit être au minimum de 25% du prix du produit (${minAvance.toFixed(2)} Ar).
</div>
`);
return;
}
// Désactiver le bouton pendant l'opération
$submitBtn.prop('disabled', true).text('Modification...');
$.ajax({
url: base_url + 'avances/updateAvance/' + id,
type: 'POST',
data: $form.serialize(),
dataType: 'json',
success: function(res) {
if (res.success === true) {
// Fermer le modal
$('#updateModal').modal('hide');
// Message de succès
$("#messages").html(`
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>
`);
// 🔄 MISE À JOUR IMMÉDIATE de la DataTable
refreshDataTable();
// Auto-masquer le message
setTimeout(function() {
$("#messages .alert").fadeOut();
}, 3000);
} else {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ${res.messages}
</div>
`);
}
},
error: function(xhr, status, error) {
console.log('Erreur lors de la modification:', error);
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Erreur lors de la modification.
</div>
`);
},
complete: function() {
// Réactiver le bouton dans tous les cas
$submitBtn.prop('disabled', false).text('Modifier');
}
});
});
}).fail(function() {
console.log('Erreur lors du chargement des données');
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Erreur lors du chargement des données.
</div>
`);
});
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: {
avance_id: id,
product_id: product_id
},
dataType: 'json',
success: function(response) {
manageTable.ajax.reload(null, false);
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>');
// Cache le modal
$("#removeModal").modal('hide');
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>');
}
}
});
return false;
// Réinitialiser le modal à la fermeture
$('#updateModal').on('hidden.bs.modal', function() {
$('#update_avance_form')[0].reset();
$('#update_avance_form button[type="submit"]').prop('disabled', false).text('Modifier');
});
}
}
</script>
</script>

25
app/Views/brands/index.php

@ -189,6 +189,31 @@
$("#brandNav").addClass('active');
// initialize the datatable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': '<?= base_url('brands/fetchBrandData') ?>',
'order': []

25
app/Views/category/index.php

@ -186,6 +186,31 @@
$("#categoryNav").addClass('active');
// initialize the datatable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': 'fetchCategoryData',
'order': []

94
app/Views/commercial/addImage.php

@ -14,24 +14,28 @@
<div class="row g-0">
<!-- Left: Product Image -->
<div class="col-md-6 text-center">
<img src="<?= base_url('assets/images/product_image/' . $products['image']) ?>"
alt="<?= $products['name'] ?>"
<img src="<?= base_url('assets/images/product_image/' . ($products['image'] ?? 'default.png')) ?>"
alt="<?= esc($products['name'] ?? '') ?>"
class="img-fluid rounded"
style="width: 100%; height: 100%; object-fit: cover;">
</div>
<!-- Right: Product Details -->
<div class="col-md-6 p-4">
<h2 class="text-dark"><?= $products['name'] ?></h2>
<h4 class="text-success fw-bold"><?= number_format($products['price'], 0, ',', ' ') ?> MGA</h4>
<p class="text-muted">En stock: <strong><?= $products['qty'] ?></strong></p>
<p class="text-secondary"><?= $products['description'] ?></p>
<h2 class="text-dark"><?= esc($products['name'] ?? '') ?></h2>
<h4 class="text-success fw-bold">
<?= number_format((float) ($products['price'] ?? 0), 0, ',', ' ') ?> MGA
</h4>
<p class="text-muted">
En stock: <strong><?= esc($products['qty'] ?? 0) ?></strong>
</p>
<p class="text-secondary"><?= esc($products['description'] ?? '') ?></p>
<!-- Buttons -->
<div class="d-flex mt-4">
<form action="<?= base_url('ventes/moreimage/') . $products['id'] ?>" enctype="multipart/form-data" method="post">
<form action="<?= base_url('ventes/moreimage/') . ($products['id'] ?? 0) ?>" enctype="multipart/form-data" method="post">
<div class="form-group">
<label for="image"> <b>Ajouter plus d'images</b></label>
<label for="image"><b>Ajouter plus d'images</b></label>
<input type="file" required name="images[]" multiple accept="image/*" id="image" class="form-control">
</div>
<button type="submit" class="btn btn-primary me-2">
@ -47,50 +51,56 @@
</div>
<h3>Galleries</h3>
<div class="row">
<?php foreach ($galleries as $key => $value) { ?>
<div class="col-md-3 col-xs-12 col-sm-6 mb-3" style="margin-bottom: 1%;">
<div class="card shadow-lg border-0 rounded">
<div class="card-header bg-success text-white text-center p-0.5">
<!-- Clickable Image -->
<a href="#" data-toggle="modal" data-target="#imageModal<?= $key ?>">
<img src="<?= base_url('assets/images/product_image/' . $value['images']) ?>"
alt="<?= $value['images'] ?>"
class="img-fluid img-thumbnail rounded"
style="width: 100%; height: 250px; object-fit: cover;">
</a>
</div>
<?php if (!empty($galleries)) : ?>
<?php foreach ($galleries as $key => $value) : ?>
<div class="col-md-3 col-xs-12 col-sm-6 mb-3" style="margin-bottom: 1%;">
<div class="card shadow-lg border-0 rounded">
<div class="card-header bg-success text-white text-center p-0.5">
<!-- Clickable Image -->
<a href="#" data-toggle="modal" data-target="#imageModal<?= $key ?>">
<img src="<?= base_url('assets/images/product_image/' . ($value['images'] ?? 'default.png')) ?>"
alt="<?= esc($value['images'] ?? '') ?>"
class="img-fluid img-thumbnail rounded"
style="width: 100%; height: 250px; object-fit: cover;">
</a>
</div>
<div class="card-footer" style="padding: 1%;">
<form action="<?= base_url('ventes/moreimage/supp/') . $value['id'] ?>" method="post">
<button class="btn btn-danger">Supprimer</button>
</form>
<div class="card-footer" style="padding: 1%;">
<form action="<?= base_url('ventes/moreimage/supp/') . ($value['id'] ?? 0) ?>" method="post">
<button class="btn btn-danger">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
<!-- Bootstrap Modal for Each Image -->
<div class="modal fade" id="imageModal<?= $key ?>" tabindex="-1" aria-labelledby="imageModalLabel<?= $key ?>" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageModalLabel<?= $key ?>">Visualisation d'image</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close"> <i class="fas fa-times"></i></button>
</div>
<div class="modal-body text-center" style="width: 100%;">
<img src="<?= base_url('assets/images/product_image/' . $value['images']) ?>"
alt="<?= $value['images'] ?>"
class="img-fluid rounded"
style="width: 100%; height: 100%; object-fit: cover;">>
<!-- Bootstrap Modal for Each Image -->
<div class="modal fade" id="imageModal<?= $key ?>" tabindex="-1" aria-labelledby="imageModalLabel<?= $key ?>" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageModalLabel<?= $key ?>">Visualisation d'image</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body text-center" style="width: 100%;">
<img src="<?= base_url('assets/images/product_image/' . ($value['images'] ?? 'default.png')) ?>"
alt="<?= esc($value['images'] ?? '') ?>"
class="img-fluid rounded"
style="width: 100%; height: 100%; object-fit: cover;">
</div>
</div>
</div>
</div>
</div>
<?php } ?>
<?php endforeach; ?>
<?php else : ?>
<p class="text-muted p-3">Aucune image disponible dans la galerie.</p>
<?php endif; ?>
</div>
</div>
</section>
</div>
<script>
$("#espaceMainMenu").addClass('active');
</script>
</script>

25
app/Views/commercial/index.php

@ -40,6 +40,31 @@
</div>
<script>
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$("#espaceMainMenu").addClass('active');
const id = <?php echo json_encode($id); ?>;

16
app/Views/commercial/single.php

@ -32,7 +32,21 @@
<p class="text-secondary">Moteur N° <?= $products['numero_de_moteur'] ?></p>
<p class="text-secondary">Boutique <b><?= $stores ?></b></p>
<p class="text-secondary"><?= $products['description'] ?></p>
<p class="text-secondary"> <b>Kit</b> <br><?= $products['etats'] == 1 ? $products['infoManquekit'] : 'Non Kit' ?></p>
<p class="text-secondary">
<b>Etat</b> <br>
<?php
switch($products['etats']) {
case 1:
echo 'Kit';
break;
case 2:
echo 'Non kit';
break;
default:
echo 'Non défini';
}
?>
</p>
<!-- Buttons -->
<div class="d-flex mt-4">

26
app/Views/dashboard.php

@ -1,4 +1,3 @@
<!-- Content Wrapper. Contains page content -->
<style>
.card {
border-radius: 12px;
@ -147,7 +146,7 @@
<div class="small-box" style="background-color: #A9A9A9;">
<div class="inner">
<h2><?php echo number_format($total, 0, '.', ' '); ?>Ar</h2>
<p>Totale CAISSE</p>
<p>Totale FLUX</p>
</div>
<div class="icon">
<i class="fa fa-credit-card"></i>
@ -381,6 +380,29 @@
$(document).ready(function () {
// Initialize the datatable
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#commperformance').DataTable({
'ajax': 'reports/detail/fetchPerformances',
'order': [],

26
app/Views/demande/index.php

@ -67,6 +67,32 @@
<script>
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$("#remise_menu").addClass('active');
// Check if the URL contains the _ parameter

8
app/Views/groups/create.php

@ -51,10 +51,10 @@
<label for="group_name">Désignation</label>
<input type="text" class="form-control" id="group_name" name="group_name" placeholder="Enter group name">
</div>
<div class="form-group">
<label for="permission">Permission</label>
<!-- <div class="form-group">
<label for="permission">Permission</label> -->
<table class="table table-responsive">
<!-- <table class="table table-responsive">
<thead>
<tr>
<th></th>
@ -228,7 +228,7 @@
<td>-</td>
</tr>
</tbody>
</table>
</table> -->
</div>

5
app/Views/groups/edit.php

@ -12,6 +12,11 @@
</ol>
</section>
<!-- modification tandroany al -->
<?= $this->extend('layouts/adminlte') ?>
<?= $this->section('content') ?>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->

25
app/Views/groups/index.php

@ -92,6 +92,31 @@
<script type="text/javascript">
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$('#groupTable').DataTable();
$("#mainUserNav").addClass('active');

32
app/Views/mecanicien/index.php

@ -276,12 +276,36 @@
$("#mecanicNav").addClass('active');
// initialize the datatable
const id = <?php echo json_encode($id); ?>;
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': `<?= base_url('mecanicien/fetchMecanicienData') ?>`,
'order': []
});
'ajax': `<?= base_url('mecanicien/fetchMecanicien') ?>`,
'order': []
});
// submit the create from
$("#createForm").unbind('submit').on('submit', function () {

27
app/Views/orders/avance.php

@ -76,7 +76,7 @@
<div class="modal-content">
<form id="create_avance_form" >
<div class="modal-header">
<h4 class="modal-title">Ajouter une avance</h4>
<h4 class="modal-title">Ajouter une avance </h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
@ -175,6 +175,31 @@
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$('avance_menu').addClass("active");
$('.select2').select2();
manageTable = $('#avanceTable').DataTable({

4
app/Views/orders/create.php

@ -64,10 +64,10 @@
<div class="form-group">
<label for="gross_amount" class="col-sm-12 control-label">Date: <?php echo date('Y-m-d') ?></label>
<label for="gross_amount" class="col-sm-12 control-label"> Date : <?php echo date('Y-m-d') ?></label>
</div>
<div class="form-group">
<label for="gross_amount" class="col-sm-12 control-label">Heure: <?php echo date('h:i a') ?></label>
<label for="gross_amount" class="col-sm-12 control-label"> Heure : <?php echo date('h:i a') ?></label>
</div>
<div class="col-md-4 col-xs-12 pull pull-left">

2
app/Views/orders/createbyid.php

@ -55,7 +55,7 @@
<?php echo date('Y-m-d') ?></label>
</div>
<div class="form-group">
<label for="gross_amount" class="col-sm-12 control-label">Date:
<label for="gross_amount" class="col-sm-12 control-label">Heure:
<?php echo date('h:i a') ?></label>
</div>

26
app/Views/orders/index.php

@ -213,6 +213,32 @@
$("#manageOrdersNav").addClass('active');
// initialize the datatable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': base_url + 'orders/fetchOrdersData',
'order': [],

25
app/Views/performance/index.php

@ -92,6 +92,31 @@
let manageTable;
$(document).ready(function () {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$("#performance_menu").addClass('active');
function initDataTable(date = '', type = 'day') {

3
app/Views/products/create.php

@ -198,8 +198,9 @@
<div class="form-group">
<label for="store">Etats</label>
<select class="form-control" id="etat" name="etats" required>
<option value="2" selected>Non Kit</option>
<option value="" >selectionnez une Etat</option>
<option value="1">Kit</option>
<option value="2">Non kit</option>
</select>
</div>

62
app/Views/products/index.php

@ -282,15 +282,61 @@
$("#manageProductNav").addClass('active');
// initialize the datatable
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': base_url + 'products/fetchProductData',
'order': [],
'columnDefs': [{
targets: 3,
className: 'text-right'
} // Column index 3 corresponds to "Prix"
]
});
'ajax': base_url + 'products/fetchProductData',
'order': [],
'columns': [
{
data: 0, // Colonne Image
render: function(data) {
return data; // Affiche le HTML brut (déjà formaté en PHP)
},
orderable: false // Désactive le tri sur cette colonne
}, // SKU
{ data: 1 }, // Nom
{ data: 2 }, // Quantité
{
data: 3, // Prix
render: function(data, type, row) {
if (type === 'display') {
// Format: "1 900 000 Ar"
return new Intl.NumberFormat('fr-FR').format(data) + ' Ar';
}
return data; // Valeur non formatée pour le tri/filtre
}
},
{ data: 4 }, // Magasin
{ data: 5 }, // Disponibilité
{ data: 6 } // Actions
],
'columnDefs': [{
targets: 3,
className: 'text-right'
}]
});
});

25
app/Views/recouvrement/index.php

@ -278,6 +278,31 @@
if (window.location.search.startsWith("?_=")) {
window.location.href = window.location.origin + window.location.pathname;
}
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',

25
app/Views/recouvrement/recouvrement.php

@ -183,6 +183,31 @@
// list of recouvrement
// initialize the datatable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#recouvrement_table').DataTable({
'ajax': base_url + 'recouvrement/loadData',
'recouvrement': []

1
app/Views/reports/performance.php

@ -199,6 +199,7 @@
$("#reportNav").addClass('active');
// Initialize the datatable
manageTable = $('#commperformance').DataTable({
'ajax': 'fetchPerformances',
'order': [],

2
app/Views/reports/stockDetail.php

@ -187,6 +187,8 @@
$("#reportNav").addClass('active');
// initialize the datatable
manageTable = $('#venteTable').DataTable({
'ajax': 'fetctData/' + 0,
'order': [],

1
app/Views/reports/venteStore.php

@ -183,6 +183,7 @@
$("#reportNav").addClass('active');
// Initialize the datatable
manageTable = $('#commperformance').DataTable({
'ajax': 'fetchPerformances',
'order': [],

24
app/Views/securite/index.php

@ -75,7 +75,29 @@
$(function() {
$("#securiteNav").addClass('active');
// ===== Initialisation DataTable =====
// ===== Initialisation DataTable =====$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$('#manageTable').DataTable({
ajax: {
url: '<?= base_url('validateSecurite/fetchSecuriteData') ?>',

52
app/Views/sortieCaisse/index.php

@ -342,7 +342,6 @@
<!-- Modal for validating a recouvrement -->
<?php if (in_array('validateSortieCaisse', $user_permission)): ?>
<!-- update brand modal -->
<div class="modal fade" tabindex="-1" role="dialog" id="validateModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
@ -353,7 +352,6 @@
<form role="form" action="<?php echo base_url('sortieCaisse/validateSortieCaisse') ?>" method="post" id="validate_form">
<div class="modal-body">
<div class="row form-group">
<div class="col-lg-6">
<label for="validation" class="control-label">statut :</label>
@ -361,38 +359,60 @@
<div class="col-lg-6">
<div class="form-group">
<label for="statut">Raison de validation</label>
<input type="text" id="admin_raison">
<label for="admin_raison">Raison de validation</label>
<input type="text" class="form-control" id="admin_raison" name="admin_raison">
</div>
<div class="form-group">
<label for="statut">Statut du décaissement</label>
<sname="statut" id="statut" class="form-control">
<select name="statut" id="statut" class="form-control">
<option value="En attente" selected>En attente</option>
<option value="Valider">✔ Valider</option>
<option value="En attente" selected> En attente</option>
<option value="Refuser">✖ Refuser</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="form-group">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<script>
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$("#sortie_caisse_menu").addClass('active');
manageTable = $('#manageTable').DataTable({
ajax: '<?= base_url('sortieCaisse/fetchSortieCaisseData') ?>',

25
app/Views/stores/index.php

@ -188,11 +188,36 @@
$("#storeNav").addClass('active');
// initialize the datatable
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': '<?= base_url('stores/fetchStoresData') ?>',
'order': []
});
// submit the create from
$("#createForm").unbind('submit').on('submit', function() {
var form = $(this);

49
app/Views/templates/side_menubar.php

@ -1,3 +1,4 @@
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
@ -73,17 +74,39 @@
</li>
<?php endif; ?> -->
<!--debut espace commerciale -->
<?php if (in_array('viewCom', $user_permission) || in_array('updateCom', $user_permission)): ?>
<li id="espaceMainMenu">
<a href="<?php echo base_url('/ventes') ?>">
<i class="fa fa-shopping-cart"></i> <span> Espace commercial</span>
</a>
</li>
<?php endif; ?>
<li class="treeview" id="espaceMainMenu">
<a href="#">
<i class="fa fa-shopping-cart"></i>
<span>Espace commercial</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li><a href="<?php echo base_url('/ventes') ?>"><i class="fa fa-circle"></i> liste des produits disponibles</a></li>
<?php if (in_array('createOrder', $user_permission) || in_array('updateOrder', $user_permission) || in_array('viewOrder', $user_permission) || in_array('deleteOrder', $user_permission)): ?>
<li><a href="<?php echo base_url('orders') ?>"><i class="fa fa-circle"></i> Commandes</a></li>
<?php endif; ?>
<?php if (in_array('viewAvance', $user_permission)): ?>
<li><a href="<?php echo base_url('avances/') ?>"><i class="fa fa-circle"></i> Avances</a></li>
<?php endif; ?>
</ul>
</li>
<?php endif; ?>
<!-- rapport statistique -->
<!-- fin espace commerciale -->
<?php if (in_array('validateSecurite', $user_permission) || in_array('viewSecurite', $user_permission)): ?>
<li id="securiteNav">
<a href="<?php echo base_url('/validateSecurite') ?>">
<i class="fa fa-lock"></i> <span> Espace Securite</span>
</a>
</li>
@ -106,7 +129,7 @@
<?php if (in_array('createMecanicien', $user_permission) || in_array('updateMecanicien', $user_permission) || in_array('viewMecanicien', $user_permission) || in_array('deleteMecanicien', $user_permission)): ?>
<li id="mecanicNav">
<a href="<?php echo base_url('mecanicien/') ?>">
<i class="fa fa-cog"></i> <span>Mécanicien</span>
<i class="fa fa-cog"></i> <span>Réparations</span>
</a>
</li>
<?php endif; ?>
@ -191,7 +214,7 @@
<?php endif; ?>
<?php if (in_array('createOrder', $user_permission) || in_array('updateOrder', $user_permission) || in_array('viewOrder', $user_permission) || in_array('deleteOrder', $user_permission)): ?>
<!-- <?php if (in_array('createOrder', $user_permission) || in_array('updateOrder', $user_permission) || in_array('viewOrder', $user_permission) || in_array('deleteOrder', $user_permission)): ?>
<li class="treeview" id="mainOrdersNav">
<a href="#">
<i class="fa fa-dollar"></i>
@ -201,15 +224,15 @@
</span>
</a>
<ul class="treeview-menu">
<!-- <?php if (in_array('createOrder', $user_permission)): ?>
<?php if (in_array('createOrder', $user_permission)): ?>
<li id="addOrderNav"><a href="<?php echo base_url('orders/create') ?>"><i class="fa fa-circle"></i> Nouveau Commande</a></li>
<?php endif; ?> -->
<?php endif; ?>
<?php if (in_array('updateOrder', $user_permission) || in_array('viewOrder', $user_permission) || in_array('deleteOrder', $user_permission)): ?>
<li id="manageOrdersNav"><a href="<?php echo base_url('orders') ?>"><i class="fa fa-circle"></i> Gestion de Commande</a></li>
<?php endif; ?>
</ul>
</li>
<?php endif; ?>
<?php endif; ?> -->
<?php if (in_array('viewReports', $user_permission)): ?>
<li id="reportNav">
@ -219,13 +242,13 @@
</li>
<?php endif; ?>
<?php if (in_array('viewAvance', $user_permission)): ?>
<!-- <?php if (in_array('viewAvance', $user_permission)): ?>
<li id="avance_menu">
<a href="<?php echo base_url('avances/') ?>">
<i class="fas fa-hand-holding-dollar"></i> <span>Avances</span>
</a>
</li>
<?php endif; ?>
<?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>

28
app/Views/users/index.php

@ -51,8 +51,8 @@
<th>Prenom</th>
<th>Email</th>
<th>Phone</th>
<th>Role</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; ?>
@ -262,6 +262,32 @@
}
// Initialisation de DataTable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
manageTable = $('#manageTable').DataTable({
'ajax': {
url: '<?= base_url('users/fetchUserData') ?>',

1
awstats-icon

@ -1 +0,0 @@
icon

1
awstats-icon

@ -0,0 +1 @@
icon

1
awstatsicons

@ -1 +0,0 @@
icon

1
awstatsicons

@ -0,0 +1 @@
icon

9
composer.json

@ -10,10 +10,11 @@
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"kint-php/kint": "^4.2",
"laminas/laminas-escaper": "^2.9",
"psr/log": "^1.1",
"firebase/php-jwt": "^6.11"
"firebase/php-jwt": "^6.11",
"kint-php/kint": "5.0",
"phpoffice/phpspreadsheet": "^5.0"
},
"require-dev": {
"codeigniter/coding-standard": "^1.5",
@ -52,9 +53,7 @@
]
},
"scripts": {
"post-update-cmd": [
"CodeIgniter\\ComposerScripts::postUpdate"
],
"test": "phpunit"
},
"support": {

1
icon/mime/conf.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/conf.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/csv.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/csv.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/document.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/document.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/dtd.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/dtd.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/flv.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/flv.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/fon.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/fon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/package.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/package.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/runtime.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/runtime.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/swf.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/swf.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/vbs.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/vbs.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

1
icon/mime/xsl.png

@ -1 +0,0 @@
notavailable.png

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/xsl.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
public/assets/bower_components/jquery-ui/themes/humanity/images/animated-overlay.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 0 B

BIN
public/assets/bower_components/jquery-ui/themes/humanity/images/ui-bg_glass_100_f5f0e5_1x400.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 0 B

BIN
public/assets/images/product_image/686cdaac4dad4.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
public/assets/images/product_image/686cdaac5f43e.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
public/assets/images/product_image/686cdaac72635.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 KiB

BIN
public/assets/images/product_image/6894a44cd6b5f.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/assets/images/product_image/689ca160619a9.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

BIN
public/assets/images/product_image/689eba65cf5a8.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/assets/images/product_image/689ebcb98f420.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/assets/images/product_image/689ebceb0d802.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/assets/images/product_image/689ebd3b59c5b.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

1
writable/cache/check_deadline_last_run.txt

@ -0,0 +1 @@
1755601005

1
writable/debugbar/debugbar_1751886377.352834.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886387.359498.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886393.226242.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886396.580956.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886397.779369.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886406.431574.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886416.347097.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886426.829140.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886438.478916.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886439.130326.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886443.397712.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886445.279811.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886453.171091.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886454.621281.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886456.946542.json

File diff suppressed because one or more lines are too long

1
writable/debugbar/debugbar_1751886458.223834.json

File diff suppressed because one or more lines are too long

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save