Compare commits

...

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

346 changed files with 25480 additions and 1436 deletions

2
.vscode/sftp.json vendored
View File

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

View File

@ -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 = '';
public string $SMTPHost = 'smtp.gmail.com';
/**
* SMTP Username
*/
public string $SMTPUser = '';
public string $SMTPUser = 'rey342505@gmail.com';
/**
* SMTP Password
*/
public string $SMTPPass = '';
public string $SMTPPass = 'loirqovmfuxnasrm'; // Mot de passe dapplication (App Password) Gmail
/**
* SMTP Port
*/
public int $SMTPPort = 25;
public int $SMTPPort = 587;
/**
* SMTP Timeout (in seconds)
*/
public int $SMTPTimeout = 5;
public int $SMTPTimeout = 30;
/**
* Enable persistent SMTP connections
*/
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;
}

View File

@ -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

View File

@ -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');
});

View File

@ -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.";
}
}

View File

@ -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,57 +29,96 @@ 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']);
foreach ($data as $key => $value) {
$isOwner = $users['id'] === $value['user_id'];
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
$users = $session->get('user');
$isAdmin = $this->isAdmin($users);
$isCommerciale = $this->isCommerciale($users);
$isCaissier = $this->isCaissier($users);
// Boutons daction
$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'],
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
foreach ($data as $key => $value) {
$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;
}
}
@ -86,296 +126,537 @@ class AvanceController extends AdminController
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']);
}
}
}
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;
}
} 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'];
}
}
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
}
/**
* 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 :</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') {
$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'),
];
$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');
if ($this->request->getMethod() !== 'post') {
return $this->response->setJSON([
'success' => false,
'messages' => 'Méthode non autorisée'
]);
}
try {
$session = session();
$users = $session->get('user');
if ($amount_due <= 0) {
$Orders = new Orders();
$Avance = new Avance();
$Products = new Products();
$Orders = new Orders();
$Company = new Company();
$Notification = new NotificationController();
$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'),
'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,
];
$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');
return $this->response->setJSON([
'success' => true,
'messages' => 'success. Avance convertie en commande avec succès.'
]);
}
else{
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la convertion de l\'avance'
]);
}
$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())
]);
}
else{
if ($Avance->updateAvance($id, $data)) {
// 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 = [
'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,
];
$amount_due = $data['amount_due'];
if ($amount_due <= 0) {
$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' => 0,
'paid_status' => 1,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'amount_value' => $gross_amount,
'rate_value' => $gross_amount,
];
$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 mise à jour avec succès.'
'messages' => 'Avance convertie en commande 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 conversion de l\'avance en commande'
]);
}
}
} else {
if ($Avance->updateAvance($id, $data)) {
return $this->response->setJSON([
'success' => true,
'messages' => 'Avance mise à jour avec succès.'
]);
} else {
return $this->response->setJSON([
'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";
if (!$avance_id || !$product_id) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Données manquantes pour la suppression'
]);
}
$Avance = new Avance();
$Products = new Products();
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($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());
return $this->response
->setStatusCode(500)
->setJSON(['error' => 'Une erreur interne est survenue. Lors de la création d\'une avance']);
}
}
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']));
$this->verifyRole('updateAvance');
// Boutons daction
$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>';
try {
if (!$avance_id || !is_numeric($avance_id)) {
return $this->response->setStatusCode(400)->setJSON([
'error' => 'ID d\'avance invalide'
]);
}
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;
$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($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'
]);
}
return $this->response->setJSON($result);
}
public function fetcheExpiredAvance()
{
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 daction
$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;
}
}
return $this->response->setJSON($result);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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) {
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"
]);
}
$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"
]);
}
$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"
]);
}
// 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;
}
}
}
// Insertion
if (!empty($data['name'])) {
if ($ProductsModel->insert($data)) {
$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' => "Le fichier ne contient aucune donnée"
'messages' => "Erreur lors de l'import: " . $e->getMessage()
]);
}
$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);
}
if (empty($data['name'])) {
continue; // champ désignation vide
}
// 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;
}
// 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 dimage si présente
foreach ($map as $col => $field) {
if ($field === 'image') {
$coordImg = $col . ($rowIndex + 2);
if (isset($imagesMap[$coordImg])) {
$data['image'] = $imagesMap[$coordImg];
}
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);
}
}
$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++;
}
}
// 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 limport : " . $e->getMessage()
]);
}
}
}

View File

@ -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

View File

@ -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é.";
}
}

View File

@ -350,34 +350,25 @@ class UserController extends AdminController
return redirect()->to('/users');
}
// supression utilisateur
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
$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
}
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 Normal file
View File

@ -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>

View File

@ -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 :</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 Normal file
View File

@ -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 !
}

View File

@ -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;
}
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;
// 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'));
}
}
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;
}
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)
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;
}
}
// 📌 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();
}
}

View File

@ -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>

View File

@ -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><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<?php endif; ?>
<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();
<?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)): ?>
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"
}
};
// 📌 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
});
}
// 🔄 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; ?>
$('#avance_expired').on('click', function () {
manageTable = initAvanceTable('fetchExpiredAvance', adminColumns);
});
<?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_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);
e.preventDefault();
const $form = $(this);
var brut = parseFloat($('#gross_amount').val()) || 0;
var avance = parseFloat($('#avance_amount').val()) || 0;
var minAvance = brut * 0.25;
$("#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>`
);
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);
};
// Récupère prix pour création
function getProductDataCreate(){
$('#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é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.');
}
}, '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);
// Supprime les anciens messages d'erreur
$(".text-danger").remove();
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: {
avance_id: id,
product_id: product_id
},
$.ajax({
url: base_url + 'orders/getProductValueById',
type: 'POST',
data: { product_id: 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>');
}
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);
}
});
return false;
});
}
}
function updateDueEdit() {
var av = parseFloat($('#avance_amount_edit').val()) || 0;
$('#amount_due_edit').val(Math.max(brutEdit - av, 0).toFixed(2));
}
</script>
// ✅ 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>
`);
});
// 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>

View File

@ -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': []

View File

@ -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': []

View File

@ -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>
<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>
</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>
<?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="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;">>
<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>
<?php } ?>
<!-- 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>
<?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>

View File

@ -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); ?>;

View File

@ -32,7 +32,21 @@
<p class="text-secondary">Moteur <?= $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">

View File

@ -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': [],

View File

@ -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

View File

@ -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>

View File

@ -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) -->

View File

@ -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');

View File

@ -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 () {

View File

@ -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({

View File

@ -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">

View File

@ -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>

View File

@ -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': [],

View File

@ -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') {

View File

@ -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>

View File

@ -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'
}]
});
});

View File

@ -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') ?>',

View File

@ -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': []

View File

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

View File

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

View File

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

View File

@ -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') ?>',

View File

@ -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 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>
</div>
<div class="modal-footer">
<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') ?>',

View File

@ -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);

View File

@ -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>

View File

@ -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') ?>',

View File

@ -1 +0,0 @@
icon

1
awstats-icon Normal file
View File

@ -0,0 +1 @@
icon

View File

@ -1 +0,0 @@
icon

1
awstatsicons Normal file
View File

@ -0,0 +1 @@
icon

View File

@ -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": {

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/conf.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/csv.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/document.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/dtd.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/flv.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/fon.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/package.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/runtime.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/swf.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/vbs.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

View File

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

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

BIN
icon/mime/xsl.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 B

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@ -0,0 +1 @@
1755601005

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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