Sarobidy22 1 month ago
parent
commit
67b33ad2e6
  1. 33
      app/Config/Routes.php
  2. 3
      app/Controllers/AdminController.php
  3. 1530
      app/Controllers/AvanceController.php
  4. 110
      app/Controllers/Dashboard.php
  5. 21
      app/Controllers/NotificationController.php
  6. 639
      app/Controllers/OrderController.php
  7. 12
      app/Controllers/ProductCOntroller.php
  8. 194
      app/Helpers/alerts_helper.php
  9. 356
      app/Models/Avance.php
  10. 124
      app/Models/Products.php
  11. 873
      app/Views/avances/avance.php
  12. 269
      app/Views/dashboard.php
  13. 233
      app/Views/login.php
  14. 236
      app/Views/orders/createbyid.php
  15. 474
      app/Views/orders/edit.php
  16. 2
      app/Views/products/create.php
  17. 5
      app/Views/products/index.php
  18. 1
      app/Views/templates/header.php
  19. 40
      app/Views/templates/header_menu.php

33
app/Config/Routes.php

@ -57,6 +57,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->post('/ventes/moreimage/(:num)', [Auth::class, 'uploadImagePub']); $routes->post('/ventes/moreimage/(:num)', [Auth::class, 'uploadImagePub']);
$routes->post('/ventes/moreimage/supp/(:num)', [Auth::class, 'delete']); $routes->post('/ventes/moreimage/supp/(:num)', [Auth::class, 'delete']);
/** /**
* route to logout * route to logout
*/ */
@ -176,6 +177,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
// $routes->get('generateqrcode/(:num)', [QrCodeController::class, 'generate']); // $routes->get('generateqrcode/(:num)', [QrCodeController::class, 'generate']);
$routes->post('assign_store', [ProductCOntroller::class, 'assign_store']); $routes->post('assign_store', [ProductCOntroller::class, 'assign_store']);
$routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']); $routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']);
}); });
/** /**
@ -261,6 +263,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->group('/notifications', function ($routes) { $routes->group('/notifications', function ($routes) {
$routes->get('/', [NotificationController::class, 'getNotification']); $routes->get('/', [NotificationController::class, 'getNotification']);
$routes->post('markAsRead/(:num)', [NotificationController::class, 'markAsRead']); $routes->post('markAsRead/(:num)', [NotificationController::class, 'markAsRead']);
$routes->post('markAllAsRead', [NotificationController::class, 'markAllAsRead']);
}); });
// routes for sortie caisse // routes for sortie caisse
$routes->group('/sortieCaisse', function ($routes) { $routes->group('/sortieCaisse', function ($routes) {
@ -291,15 +294,33 @@ $routes->group('/sortieCaisse', function ($routes) {
// avance // avance
$routes->group('/avances', function ($routes) { $routes->group('/avances', function ($routes) {
$routes->get('/', [AvanceController::class, 'index']); $routes->get('/', [AvanceController::class, 'index']);
// ✅ Routes pour récupérer les données (GET)
$routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']); $routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']);
$routes->get('fetchCompletedAvances', [AvanceController::class, 'fetchCompletedAvances']); $routes->get('fetchAvanceBecameOrder', [AvanceController::class, 'fetchAvanceBecameOrder']);
$routes->get('fetchIncompleteAvances', [AvanceController::class, 'fetchIncompleteAvances']); $routes->get('fetchExpiredAvance', [AvanceController::class, 'fetchExpiredAvance']);
$routes->get('fetchAvanceBecameOrder', 'AvanceController::fetchAvanceBecameOrder');
$routes->get('fetchExpiredAvance', [AvanceController::class, 'fetcheExpiredAvance']); // Routes pour une avance spécifique
$routes->get('fetchSingleAvance/(:num)', [AvanceController::class, 'fetchSingleAvance']); $routes->get('fetchSingleAvance/(:num)', [AvanceController::class, 'fetchSingleAvance/$1']);
$routes->get('getInvoicePreview/(:num)', [AvanceController::class, 'getInvoicePreview/$1']);
$routes->get('getFullInvoiceForPrint/(:num)', [AvanceController::class, 'getFullInvoiceForPrint/$1']);
$routes->get('printInvoice/(:num)', [AvanceController::class, 'printInvoice/$1']);
// ✅ Routes POST pour modifications
$routes->post('createAvance', [AvanceController::class, 'createAvance']); $routes->post('createAvance', [AvanceController::class, 'createAvance']);
$routes->post('updateAvance', [AvanceController::class, 'updateAvance']);
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']); $routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
$routes->post('updateAvance/(:num)', [AvanceController::class, 'updateAvance']); $routes->post('notifyPrintInvoice', [AvanceController::class, 'notifyPrintInvoice']);
// ✅ AJOUTER CETTE ROUTE MANQUANTE
$routes->post('processExpiredAvances', [AvanceController::class, 'processExpiredAvances']);
// ✅ Route CRON (optionnel)
$routes->get('checkDeadlineAlerts', [AvanceController::class, 'checkDeadlineAlerts']);
$routes->post('payAvance', 'AvanceController::payAvance');
$routes->get('forceConvertToOrder/(:num)', 'AvanceController::forceConvertToOrder/$1');
$routes->post('checkAndConvertCompleted', 'AvanceController::checkAndConvertCompleted');
}); });
// historique // historique
$routes->group('historique', ['filter' => 'auth'], static function ($routes) { $routes->group('historique', ['filter' => 'auth'], static function ($routes) {

3
app/Controllers/AdminController.php

@ -12,7 +12,6 @@ use CodeIgniter\Logger\LoggerInterface;
abstract class AdminController extends BaseController abstract class AdminController extends BaseController
{ {
protected $permission = []; protected $permission = [];
public function __construct() public function __construct()
{ {
if (empty(session()->get('user'))) { if (empty(session()->get('user'))) {
@ -25,10 +24,10 @@ abstract class AdminController extends BaseController
$group_data = $Groups->getUserGroupByUserId($userId); $group_data = $Groups->getUserGroupByUserId($userId);
$this->permission = unserialize($group_data['permission']); $this->permission = unserialize($group_data['permission']);
} }
} }
/** /**
* finction to verify role of users * finction to verify role of users
* @return mixed * @return mixed

1530
app/Controllers/AvanceController.php

File diff suppressed because it is too large

110
app/Controllers/Dashboard.php

@ -21,6 +21,10 @@ class Dashboard extends AdminController
public function index() public function index()
{ {
// === 🔥 Récupérer l'utilisateur en premier ===
$session = session();
$user_id = $session->get('user');
$productModel = new Products(); $productModel = new Products();
$orderModel = new Orders(); $orderModel = new Orders();
$userModel = new Users(); $userModel = new Users();
@ -65,49 +69,82 @@ class Dashboard extends AdminController
$es_avances = isset($paymentDataAvance->total_espece) ? (float) $paymentDataAvance->total_espece : 0; $es_avances = isset($paymentDataAvance->total_espece) ? (float) $paymentDataAvance->total_espece : 0;
$vb_avances = isset($paymentDataAvance->total_virement_bancaire) ? (float) $paymentDataAvance->total_virement_bancaire : 0; $vb_avances = isset($paymentDataAvance->total_virement_bancaire) ? (float) $paymentDataAvance->total_virement_bancaire : 0;
// === COMBINAISON ORDERS + AVANCES === // === COMBINAISON ORDERS + AVANCES (BRUT = CE QUE LA CAISSIÈRE A ENCAISSÉ) ===
$total_mvola = $mv1_orders + $mv2_orders + $mv_avances; $total_mvola_brut = $mv1_orders + $mv2_orders + $mv_avances;
$total_espece = $es1_orders + $es2_orders + $es_avances; $total_espece_brut = $es1_orders + $es2_orders + $es_avances;
$total_vb = $vb1_orders + $vb2_orders + $vb_avances; $total_vb_brut = $vb1_orders + $vb2_orders + $vb_avances;
$total = $total_orders + $total_avances; $total_brut = $total_orders + $total_avances;
// === AJUSTEMENTS AVEC RECOUVREMENTS ET SORTIES (PAR MODE DE PAIEMENT) === // === AJUSTEMENTS AVEC RECOUVREMENTS ET SORTIES ===
$total_mvola_final = $total_mvola - $total_mvola_final = $total_mvola_brut - $me - $mb + $bm - $total_sortie_mvola;
$me - $total_espece_final = $total_espece_brut + $me + $be - $total_sortie_espece;
$mb + $total_virement_bancaire_final = $total_vb_brut - $be - $bm + $mb - $total_sortie_virement;
$bm -
$total_sortie_mvola;
$total_espece_final = $total_espece +
$me +
$be -
$total_sortie_espece;
$total_virement_bancaire_final = $total_vb -
$be -
$bm +
$mb -
$total_sortie_virement;
// === CALCUL DU TOTAL GÉNÉRAL === // === CALCUL DU TOTAL GÉNÉRAL ===
// ✅ CORRECTION: Ne PAS additionner les recouvrements au total
// Les recouvrements sont des transferts internes, pas des entrées d'argent
$total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement; $total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement;
$total_final = $total - $total_sortie_global; $total_final = $total_brut - $total_sortie_global;
// check avance expired
$avance = new Avance();
$avance->checkExpiredAvance();
// ✅ MODIFICATION : La caissière voit EXACTEMENT les mêmes montants que Direction/Conseil
$data = [ $data = [
// === POUR DIRECTION/CONSEIL (AVEC TOUS LES AJUSTEMENTS) ===
'total' => $total_final, 'total' => $total_final,
'total_mvola' => $total_mvola_final, 'total_mvola' => $total_mvola_final,
'total_espece' => $total_espece_final, 'total_espece' => $total_espece_final,
'total_virement_bancaire' => $total_virement_bancaire_final, 'total_virement_bancaire' => $total_virement_bancaire_final,
'user_permission' => $this->permission,
// === POUR CAISSIÈRE (MÊME CALCUL QUE DIRECTION) ===
'total_caisse' => $total_final, // ← Identique à 'total'
'total_mvola_caisse' => $total_mvola_final, // ← Identique à 'total_mvola'
'total_espece_caisse' => $total_espece_final, // ← Identique à 'total_espece'
'total_vb_caisse' => $total_virement_bancaire_final, // ← Identique à 'total_virement_bancaire'
// === DÉTAIL POUR LA CAISSIÈRE ===
'total_orders_only' => $total_orders, // Ventes complètes uniquement
'total_avances' => $total_avances, // Avances uniquement
// ✅ Détails des sorties
'total_sorties' => $total_sortie_global,
'total_sortie_espece' => $total_sortie_espece,
'total_sortie_mvola' => $total_sortie_mvola,
'total_sortie_virement' => $total_sortie_virement,
// ✅ Détails des recouvrements
'recouvrement_me' => $me, // Mvola → Espèce
'recouvrement_be' => $be, // Banque → Espèce
'recouvrement_bm' => $bm, // Banque → Mvola
'recouvrement_mb' => $mb, // Mvola → Banque
'total_recouvrements' => $me + $be + $bm + $mb,
// Détail avances par mode de paiement
'total_avances_mvola' => $mv_avances,
'total_avances_espece' => $es_avances,
'total_avances_virement' => $vb_avances,
// Détail orders par mode de paiement
'total_mvola_orders' => $mv1_orders + $mv2_orders,
'total_espece_orders' => $es1_orders + $es2_orders,
'total_vb_orders' => $vb1_orders + $vb2_orders,
// ✅ Montants bruts (avant recouvrements et sorties)
'total_brut' => $total_brut,
'total_mvola_brut' => $total_mvola_brut,
'total_espece_brut' => $total_espece_brut,
'total_vb_brut' => $total_vb_brut,
]; ];
$data['total_products'] = $productModel->countTotalProducts(); // === ✅ Compter les produits selon le store de l'utilisateur ===
$data['total_products'] = $productModel->countProductsByUserStore();
// === ✅ Récupérer le nom du store pour l'affichage ===
$isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction']);
if (!$isAdmin && !empty($user_id['store_id']) && $user_id['store_id'] != 0) {
$store = $storeModel->getStoresData($user_id['store_id']);
$data['store_name'] = $store['name'] ?? 'Votre magasin';
} else {
$data['store_name'] = 'Tous les magasins';
}
$data['total_paid_orders'] = $orderModel->countTotalPaidOrders(); $data['total_paid_orders'] = $orderModel->countTotalPaidOrders();
$data['total_users'] = $userModel->countTotalUsers(); $data['total_users'] = $userModel->countTotalUsers();
$data['total_stores'] = $storeModel->countTotalStores(); $data['total_stores'] = $storeModel->countTotalStores();
@ -175,16 +212,14 @@ class Dashboard extends AdminController
$data['count_id'] = $countId; $data['count_id'] = $countId;
// Check if the user is an Conseil
$session = session();
$user_id = $session->get('user');
$data['is_admin'] = false; $data['is_admin'] = false;
$data['isCommercial'] = false; $data['isCommercial'] = false;
$data['isChef'] = false; $data['isChef'] = false;
$data['isCaissier'] = false; $data['isCaissier'] = false;
$data['isMecanicien'] = false; $data['isMecanicien'] = false;
$data['isSecurite'] = false;
if ($user_id['group_name'] == "Direction" || $user_id['group_name'] == "Conseil") { if ($user_id['group_name'] == "Direction" || $user_id['group_name'] == "DAF") {
$data['is_admin'] = true; $data['is_admin'] = true;
} }
@ -203,6 +238,9 @@ class Dashboard extends AdminController
if ($user_id['group_name'] == "MECANICIEN") { if ($user_id['group_name'] == "MECANICIEN") {
$data['isMecanicien'] = true; $data['isMecanicien'] = true;
} }
if ($user_id['group_name'] == "Sécurité" || $user_id['group_name'] == "SECURITE") {
$data['isSecurite'] = true;
}
$data['page_title'] = 'Dashboard'; $data['page_title'] = 'Dashboard';
$data['marques_total'] = json_encode($orderModel->getTotalProductvente()); $data['marques_total'] = json_encode($orderModel->getTotalProductvente());

21
app/Controllers/NotificationController.php

@ -43,4 +43,25 @@ class NotificationController extends AdminController
$Notification->insertNotification($data); $Notification->insertNotification($data);
} }
// Marquer toutes les notifications comme lues pour l'utilisateur connecté
public function markAllAsRead()
{
$Notification = new Notification();
$session = session();
$users = $session->get('user');
// Mettre à jour toutes les notifications non lues pour ce store et ce groupe
$builder = $Notification->builder();
$builder->where('store_id', $users['store_id'])
->groupStart()
->where('forgroup', $users['group_name'])
->orWhere('forgroup', strtolower('TOUS'))
->groupEnd()
->where('is_read', 0)
->set(['is_read' => 1])
->update();
return $this->response->setJSON(['status' => 'success']);
}
} }

639
app/Controllers/OrderController.php

@ -46,6 +46,9 @@ class OrderController extends AdminController
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
// ========================================
// POUR CAISSIÈRE
// ========================================
if ($users['group_name'] == "Caissière") { if ($users['group_name'] == "Caissière") {
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); $date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
@ -57,7 +60,6 @@ class OrderController extends AdminController
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>'; $buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
} }
// Bouton voir // Bouton voir
if (in_array('viewOrder', $this->permission)) { if (in_array('viewOrder', $this->permission)) {
$buttons .= ' $buttons .= '
@ -71,8 +73,10 @@ class OrderController extends AdminController
</a>'; </a>';
} }
// Bouton modifier // ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente)
if (in_array('updateOrder', $this->permission) && $users["store_id"] == $value['store_id']) { if (in_array('updateOrder', $this->permission)
&& $users["store_id"] == $value['store_id']
&& in_array($value['paid_status'], [0, 2])) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>'; $buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>';
} }
@ -87,7 +91,7 @@ class OrderController extends AdminController
$paid_status = '<span class="label label-danger">Refusé</span>'; $paid_status = '<span class="label label-danger">Refusé</span>';
} }
// Calcul délai (SANS notification ici) // Calcul délai
$date1 = new DateTime($date_time); $date1 = new DateTime($date_time);
$date2 = new DateTime(); $date2 = new DateTime();
$interval = $date1->diff($date2); $interval = $date1->diff($date2);
@ -104,9 +108,6 @@ class OrderController extends AdminController
} }
} }
// $Orders_items = new OrderItems();
// $sum_order_item = $Orders_items->getSumOrdersItemData($value['id']);
$result['data'][$key] = [ $result['data'][$key] = [
$value['product_names'], $value['product_names'],
$value['user_name'], $value['user_name'],
@ -120,29 +121,30 @@ class OrderController extends AdminController
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }
// ========================================
// POUR DIRECTION OU DAF
// ========================================
elseif($users['group_name'] == "Direction" || $users['group_name'] == "DAF"){ elseif($users['group_name'] == "Direction" || $users['group_name'] == "DAF"){
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); $date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
$buttons = ''; $buttons = '';
// Bouton imprimer (sauf pour SECURITE) // Bouton imprimer
if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") { if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>'; $buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
} }
// ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente)
if (in_array('updateOrder', $this->permission)) { if (in_array('updateOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>'; $buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>';
} }
if (in_array('deleteOrder', $this->permission)) { // ✅ Bouton supprimer pour statuts 0 et 2
if (in_array('deleteOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) {
$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>'; $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>';
} }
// Statut de paiement // Statut de paiement
if ($value['paid_status'] == 1) { if ($value['paid_status'] == 1) {
$paid_status = '<span class="label label-success">Validé</span>'; $paid_status = '<span class="label label-success">Validé</span>';
@ -154,7 +156,7 @@ class OrderController extends AdminController
$paid_status = '<span class="label label-danger">Refusé</span>'; $paid_status = '<span class="label label-danger">Refusé</span>';
} }
// Calcul délai (SANS notification) // Calcul délai
$date1 = new DateTime($date_time); $date1 = new DateTime($date_time);
$date2 = new DateTime(); $date2 = new DateTime();
$interval = $date1->diff($date2); $interval = $date1->diff($date2);
@ -171,9 +173,6 @@ class OrderController extends AdminController
} }
} }
// $Orders_items= new OrderItems();
// $sum_order_item = $Orders_items->getSumOrdersItemData($value['id']);
$result['data'][$key] = [ $result['data'][$key] = [
$value['bill_no'], $value['bill_no'],
$value['customer_name'], $value['customer_name'],
@ -187,23 +186,29 @@ class OrderController extends AdminController
} }
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }
// ========================================
// POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, etc.)
// ========================================
else { else {
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); $date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
$buttons = ''; $buttons = '';
// Bouton imprimer (sauf pour SECURITE) // Bouton imprimer
if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") { if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>'; $buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
} }
if (in_array('updateOrder', $this->permission) && $users["id"] == $value['user_id']) { // ✅ Bouton modifier pour statuts 0 et 2, ET si c'est l'utilisateur créateur
if (in_array('updateOrder', $this->permission)
&& $users["id"] == $value['user_id']
&& in_array($value['paid_status'], [0, 2])) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>'; $buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>';
} }
// Bouton voir
if (in_array('viewOrder', $this->permission)) { if (in_array('viewOrder', $this->permission)) {
$buttons .= ' $buttons .= '
<a <a
@ -216,11 +221,13 @@ class OrderController extends AdminController
</a>'; </a>';
} }
if (in_array('deleteOrder', $this->permission) && $users["id"] == $value['user_id']) { // ✅ Bouton supprimer pour statuts 0 et 2, ET si c'est l'utilisateur créateur
if (in_array('deleteOrder', $this->permission)
&& $users["id"] == $value['user_id']
&& in_array($value['paid_status'], [0, 2])) {
$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>'; $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>';
} }
// Statut de paiement // Statut de paiement
if ($value['paid_status'] == 1) { if ($value['paid_status'] == 1) {
$paid_status = '<span class="label label-success">Validé</span>'; $paid_status = '<span class="label label-success">Validé</span>';
@ -232,7 +239,7 @@ class OrderController extends AdminController
$paid_status = '<span class="label label-danger">Refusé</span>'; $paid_status = '<span class="label label-danger">Refusé</span>';
} }
// Calcul délai (SANS notification) // Calcul délai
$date1 = new DateTime($date_time); $date1 = new DateTime($date_time);
$date2 = new DateTime(); $date2 = new DateTime();
$interval = $date1->diff($date2); $interval = $date1->diff($date2);
@ -249,9 +256,6 @@ class OrderController extends AdminController
} }
} }
// $Orders_items= new OrderItems();
// $sum_order_item = $Orders_items->getSumOrdersItemData($value['id']);
$result['data'][$key] = [ $result['data'][$key] = [
$value['product_names'], $value['product_names'],
$value['user_name'], $value['user_name'],
@ -362,7 +366,7 @@ class OrderController extends AdminController
return redirect()->back() return redirect()->back()
->withInput() ->withInput()
->with('errors', [ ->with('errors', [
"⚠️ Commande bloquée : Le rabais de {$discountFormatted} Ar pour « {$productData['name']} » est inférieur au prix minimal autorisé de {$prixMinimalFormatted} Ar." "⚠️ Commande bloquée : Le rabais de {$discountFormatted} Ar pour « {$productData['name']} » est trop élevé."
]); ]);
} }
} }
@ -477,10 +481,8 @@ class OrderController extends AdminController
// Redirection selon le rôle // Redirection selon le rôle
if ($users["group_name"] != "COMMERCIALE") { if ($users["group_name"] != "COMMERCIALE") {
$this->checkProductisNull($posts, $users['store_id']); $this->checkProductisNull($posts, $users['store_id']);
return redirect()->to('orders/update/' . $order_id);
} else {
return redirect()->to('orders/');
} }
return redirect()->to('orders/');
} else { } else {
session()->setFlashdata('errors', 'Error occurred!!'); session()->setFlashdata('errors', 'Error occurred!!');
@ -626,6 +628,22 @@ public function markAsDelivered()
$data['page_title'] = $this->pageTitle; $data['page_title'] = $this->pageTitle;
$validation = \Config\Services::validation(); $validation = \Config\Services::validation();
// ✅ NOUVELLE VÉRIFICATION : Bloquer UNIQUEMENT si statut = Validé (1) ou Validé et Livré (3)
$Orders = new Orders();
$current_order = $Orders->getOrdersData($id);
if (!$current_order) {
session()->setFlashData('errors', 'Commande introuvable.');
return redirect()->to('orders/');
}
// ✅ Bloquer UNIQUEMENT les statuts 1 (Validé) et 3 (Validé et Livré)
// Le statut 0 (Refusé) et 2 (En Attente) restent modifiables
if (in_array($current_order['paid_status'], [1, 3])) {
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
return redirect()->to('orders/');
}
// Règles de validation // Règles de validation
$validation->setRules([ $validation->setRules([
'product' => 'required' 'product' => 'required'
@ -635,7 +653,6 @@ public function markAsDelivered()
'product' => $this->request->getPost('product') 'product' => $this->request->getPost('product')
]; ];
$Orders = new Orders();
$Company = new Company(); $Company = new Company();
$Products = new Products(); $Products = new Products();
$OrderItems = new OrderItems(); $OrderItems = new OrderItems();
@ -646,12 +663,18 @@ public function markAsDelivered()
if ($this->request->getMethod() === 'post' && $validation->run($validationData)) { if ($this->request->getMethod() === 'post' && $validation->run($validationData)) {
$current_order = $Orders->getOrdersData($id); // ✅ DOUBLE VÉRIFICATION avant l'update
$current_order_check = $Orders->getOrdersData($id);
if (in_array($current_order_check['paid_status'], [1, 3])) {
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
return redirect()->to('orders/');
}
$old_paid_status = $current_order['paid_status']; $old_paid_status = $current_order['paid_status'];
// ✅ Statut payé pour COMMERCIALE reste toujours "En attente" // ✅ Statut payé pour COMMERCIALE reste toujours "En attente"
if ($role === 'COMMERCIALE') { if ($role === 'COMMERCIALE') {
$paid_status = 2; // ← mettre la valeur correspondant à "En attente" dans votre table $paid_status = 2;
} else { } else {
$paid_status = $this->request->getPost('paid_status'); $paid_status = $this->request->getPost('paid_status');
} }
@ -745,7 +768,7 @@ public function markAsDelivered()
} }
session()->setFlashData('success', 'Commande mise à jour avec succès.'); session()->setFlashData('success', 'Commande mise à jour avec succès.');
return redirect()->to('orders/update/' . $id); return redirect()->to('orders/');
} else { } else {
session()->setFlashData('errors', 'Une erreur est survenue lors de la mise à jour.'); session()->setFlashData('errors', 'Une erreur est survenue lors de la mise à jour.');
return redirect()->to('orders/update/' . $id); return redirect()->to('orders/update/' . $id);
@ -760,6 +783,9 @@ public function markAsDelivered()
$orders_data = $Orders->getOrdersData($id); $orders_data = $Orders->getOrdersData($id);
// ✅ Ajouter un flag pour désactiver le formulaire UNIQUEMENT pour les statuts 1 et 3
$data['is_editable'] = !in_array($orders_data['paid_status'], [1, 3]);
// Montant tranches // Montant tranches
$orders_data['montant_tranches'] = (!empty($orders_data['discount']) && $orders_data['discount'] > 0) $orders_data['montant_tranches'] = (!empty($orders_data['discount']) && $orders_data['discount'] > 0)
? $orders_data['discount'] ? $orders_data['discount']
@ -780,7 +806,6 @@ public function markAsDelivered()
} }
public function lookOrder(int $id) public function lookOrder(int $id)
{ {
$this->verifyRole('viewOrder'); $this->verifyRole('viewOrder');
@ -1546,10 +1571,7 @@ public function print5(int $id)
// Modèles // Modèles
$Orders = new Orders(); $Orders = new Orders();
$Company = new Company(); $Company = new Company();
$Products = new Products();
$OrderItems = new OrderItems(); $OrderItems = new OrderItems();
$Brand = new Brands();
$Category = new Category();
// Récupération des données // Récupération des données
$order = $Orders->getOrdersData($id); $order = $Orders->getOrdersData($id);
@ -1557,17 +1579,23 @@ public function print5(int $id)
$company = $Company->getCompanyData(1); $company = $Company->getCompanyData(1);
$today = date('d/m/Y'); $today = date('d/m/Y');
// ✅ LOGIQUE DE REMISE: Si discount existe, il devient le prix de vente // ✅ Vérifier si c'est une avance "sur mer"
$isAvanceMere = false;
foreach ($items as $item) {
if (!empty($item['product_name']) && empty($item['product_id'])) {
$isAvanceMere = true;
break;
}
}
// Calculs
$discount = (float) $order['discount']; $discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount']; $grossAmount = (float) $order['gross_amount'];
// Si remise existe, elle devient le montant TTC, sinon on prend gross_amount
$totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20; $totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT; $tva = $totalTTC - $totalHT;
$inWords = $this->numberToWords((int) round($totalTTC)); $inWords = $this->numberToWords((int) round($totalTTC));
// Statut paiement
$paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé';
// Début du HTML // Début du HTML
@ -1612,13 +1640,54 @@ public function print5(int $id)
<p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p> <p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p>
<p><strong>Téléphone :</strong> '.esc($order['customer_phone'] ?? '').'</p> <p><strong>Téléphone :</strong> '.esc($order['customer_phone'] ?? '').'</p>
<p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p> <p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p>
</div> </div>';
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
if ($isAvanceMere) {
// ========================================
// TABLE SIMPLIFIÉE POUR AVANCE "SUR MER"
// ========================================
$html .= '
<table>
<thead>
<tr>
<th>Produit</th>
<th class="right">Prix (Ar)</th>
</tr>
</thead>
<tbody>';
foreach ($items as $it) {
$details = $this->getOrderItemDetails($it);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '
<tr>
<td>'.esc($details['product_name']);
// Afficher le commentaire s'il existe
if (!empty($details['commentaire'])) {
$html .= '<br><em style="font-size:12px; color:#666;">'.esc($details['commentaire']).'</em>';
}
$html .= '</td>
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
</tr>';
}
} else {
// ========================================
// TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE
// ========================================
$html .= '
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Désignation</th>
<th>MARQUE</th> <th>MARQUE</th>
<th>Désignation</th>
<th>N° Moteur</th> <th>N° Moteur</th>
<th>N° Châssis</th> <th>N° Châssis</th>
<th>Puissance (CC)</th> <th>Puissance (CC)</th>
@ -1628,30 +1697,23 @@ public function print5(int $id)
<tbody>'; <tbody>';
foreach ($items as $it) { foreach ($items as $it) {
$p = $Products->getProductData($it['product_id']); $details = $this->getOrderItemDetails($it);
// ✅ Récupérer le nom de la marque correctement if (!$details) continue;
$brandName = 'Non définie';
if (!empty($p['marque'])) {
$brandData = $Brand->find($p['marque']);
if ($brandData && isset($brandData['name'])) {
$brandName = $brandData['name'];
}
}
// ✅ LOGIQUE: Si discount existe pour la commande, on l'affiche comme prix $prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$prixAffiche = ($discount > 0) ? $discount : $p['prix_vente'];
$html .= ' $html .= '
<tr> <tr>
<td>'.esc($p['name']).'</td> <td>'.esc($details['marque']).'</td>
<td>'.esc($brandName).'</td> <td>'.esc($details['product_name']).'</td>
<td>'.esc($p['numero_de_moteur']).'</td> <td>'.esc($details['numero_moteur']).'</td>
<td>'.esc($p['chasis'] ?? '').'</td> <td>'.esc($details['numero_chassis']).'</td>
<td>'.esc($p['puissance']).'</td> <td>'.esc($details['puissance']).'</td>
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td> <td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
</tr>'; </tr>';
} }
}
$html .= ' $html .= '
</tbody> </tbody>
@ -1704,7 +1766,6 @@ $html .= '
return $this->response->setBody($html); return $this->response->setBody($html);
} }
/** /**
* Convertit un nombre entier en texte (français, sans décimales). * Convertit un nombre entier en texte (français, sans décimales).
* Usage basique, pour Ariary. * Usage basique, pour Ariary.
@ -1773,9 +1834,88 @@ private function numberToWords(int $num): string
return trim($words) . ' ariary'; return trim($words) . ' ariary';
} }
// ==================================== /**
// PRINT7 - Bon de commande * ✅ NOUVELLE MÉTHODE : Vérifier si une commande provient d'une avance "sur mer"
// ==================================== */
private function isFromAvanceMere($order_id)
{
$db = \Config\Database::connect();
// Vérifier s'il existe une avance "sur mer" liée à cette commande
$avance = $db->table('avances')
->select('type_avance, product_name')
->where('is_order', 1)
->where('customer_name',
$db->table('orders')
->select('customer_name')
->where('id', $order_id)
->get()
->getRow()->customer_name ?? ''
)
->where('type_avance', 'mere')
->get()
->getRowArray();
return $avance !== null;
}
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les détails d'un item de commande
* Gère à la fois les produits normaux et ceux des avances "sur mer"
*/
private function getOrderItemDetails($item)
{
$Products = new Products();
$Brand = new Brands();
// Si l'item a un product_name (avance sur mer), on l'utilise directement
if (!empty($item['product_name']) && empty($item['product_id'])) {
return [
'type' => 'mere',
'product_name' => $item['product_name'],
'marque' => $item['marque'] ?? $item['product_name'],
'numero_moteur' => $item['numero_moteur'] ?? '',
'numero_chassis' => $item['numero_chassis'] ?? '',
'puissance' => $item['puissance'] ?? '',
'commentaire' => $item['commentaire'] ?? '',
'prix' => (float)$item['amount']
];
}
// Sinon, récupérer depuis la table products (avance sur terre ou commande normale)
if (empty($item['product_id'])) {
return null;
}
$product = $Products->getProductData($item['product_id']);
if (!$product) {
return null;
}
// Récupérer le nom de la marque
$brandName = 'N/A';
if (!empty($product['marque'])) {
$brandData = $Brand->find($product['marque']);
if ($brandData && isset($brandData['name'])) {
$brandName = $brandData['name'];
}
}
return [
'type' => 'terre',
'product_name' => $product['name'],
'marque' => $brandName,
'numero_moteur' => $product['numero_de_moteur'] ?? '',
'numero_chassis' => $product['chasis'] ?? $product['sku'] ?? '',
'puissance' => $product['puissance'] ?? '',
'commentaire' => '',
'prix' => (float)$item['amount']
];
}
/**
* ✅ PRINT7 - Bon de commande adapté pour avances "sur mer" et "sur terre"
*/
public function print7(int $id) public function print7(int $id)
{ {
$this->verifyRole('viewOrder'); $this->verifyRole('viewOrder');
@ -1787,10 +1927,7 @@ public function print7(int $id)
// Modèles // Modèles
$Orders = new Orders(); $Orders = new Orders();
$Company = new Company(); $Company = new Company();
$Products = new Products();
$OrderItems = new OrderItems(); $OrderItems = new OrderItems();
$Brand = new Brands();
$Category = new Category();
// Récupération des données // Récupération des données
$order = $Orders->getOrdersData($id); $order = $Orders->getOrdersData($id);
@ -1798,8 +1935,19 @@ public function print7(int $id)
$company = $Company->getCompanyData(1); $company = $Company->getCompanyData(1);
$today = date('d/m/Y'); $today = date('d/m/Y');
// Calculs totaux // ✅ VÉRIFIER SI C'EST UNE AVANCE "SUR MER"
$totalTTC = (float) $order['net_amount']; $isAvanceMere = false;
foreach ($items as $item) {
if (!empty($item['product_name']) && empty($item['product_id'])) {
$isAvanceMere = true;
break;
}
}
// ✅ LOGIQUE DE REMISE
$discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20; $totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT; $tva = $totalTTC - $totalHT;
@ -1827,6 +1975,7 @@ public function print7(int $id)
.conditions { page-break-before: always; padding:20px; line-height:1.5; } .conditions { page-break-before: always; padding:20px; line-height:1.5; }
@media print { @media print {
@page { margin:1cm; } @page { margin:1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
} }
</style> </style>
</head> </head>
@ -1851,8 +2000,51 @@ public function print7(int $id)
<p><strong>Téléphone :</strong> '.esc($order['customer_phone']).'</p> <p><strong>Téléphone :</strong> '.esc($order['customer_phone']).'</p>
<p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p> <p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p>
<p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p> <p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p>
</div> </div>';
// ========================================
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
// ========================================
if ($isAvanceMere) {
// --- TABLE SIMPLIFIÉE POUR AVANCE "SUR MER" (2-3 COLONNES) ---
$html .= '
<table>
<thead>
<tr>
<th>Produit</th>
<th class="right">Prix Unitaire (Ar)</th>
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '
<tr>
<td>'.esc($details['product_name']);
// Afficher le commentaire s'il existe
if (!empty($details['commentaire'])) {
$html .= '<br><em style="font-size:12px; color:#666;">Remarque : '.esc($details['commentaire']).'</em>';
}
$html .= '</td>
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
</tr>';
}
$html .= '
</tbody>
</table>';
} else {
// --- TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE (7 COLONNES) ---
$html .= '
<table> <table>
<thead> <thead>
<tr> <tr>
@ -1867,42 +2059,50 @@ public function print7(int $id)
</thead> </thead>
<tbody>'; <tbody>';
$Products = new Products();
$Brand = new Brands();
$Category = new Category();
foreach ($items as $item) { foreach ($items as $item) {
$p = $Products->getProductData($item['product_id']); $details = $this->getOrderItemDetails($item);
// ✅ Récupérer le nom de la marque correctement if (!$details) continue;
$brandName = 'Non définie';
if (!empty($p['marque'])) {
$brandData = $Brand->find($p['marque']);
if ($brandData && isset($brandData['name'])) {
$brandName = $brandData['name'];
}
}
// ✅ Récupérer le nom de la catégorie $prixAffiche = ($discount > 0) ? $discount : $details['prix'];
// Récupérer la catégorie si c'est un produit terre
$categoryName = 'Non définie'; $categoryName = 'Non définie';
if ($details['type'] === 'terre' && !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) { if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']); $categoryData = $Category->find($p['categorie_id']);
if ($categoryData && isset($categoryData['name'])) { if ($categoryData && isset($categoryData['name'])) {
$categoryName = $categoryData['name']; $categoryName = $categoryData['name'];
} }
} }
}
$html .= '<tr> $html .= '
<td>'.esc($p['name']).'</td> <tr>
<td>'.esc($brandName).'</td> <td>'.esc($details['product_name']).'</td>
<td>'.esc($details['marque']).'</td>
<td>'.esc($categoryName).'</td> <td>'.esc($categoryName).'</td>
<td>'.esc($p['numero_de_moteur']).'</td> <td>'.esc($details['numero_moteur']).'</td>
<td>'.esc($p['chasis'] ?? '').'</td> <td>'.esc($details['numero_chassis']).'</td>
<td>'.esc($p['puissance']).'</td> <td>'.esc($details['puissance']).'</td>
<td class="right">'.number_format($p['prix_vente'], 0, '', ' ').'</td> <td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
</tr>'; </tr>';
} }
$html .= ' $html .= '
</tbody> </tbody>
</table> </table>';
}
// ========================================
// TABLEAU RÉCAPITULATIF (IDENTIQUE POUR TOUS)
// ========================================
$html .= '
<table style="width:calc(100% - 40px); margin:0 20px 20px;"> <table style="width:calc(100% - 40px); margin:0 20px 20px;">
<tr> <tr>
<td><strong>Total HT :</strong></td> <td><strong>Total HT :</strong></td>
@ -1922,12 +2122,29 @@ public function print7(int $id)
</tr>'; </tr>';
if (!empty($order['order_payment_mode'])) { if (!empty($order['order_payment_mode'])) {
$html .= '<tr> $html .= '
<tr>
<td><strong>Mode de paiement :</strong></td> <td><strong>Mode de paiement :</strong></td>
<td class="right">'.esc($order['order_payment_mode']).'</td> <td class="right">'.esc($order['order_payment_mode']).'</td>
</tr>'; </tr>';
} }
if (!empty($order['tranche_1'])) {
$html .= '
<tr>
<td><strong>Tranche 1 :</strong></td>
<td class="right">'.number_format((float)$order['tranche_1'], 0, '', ' ').' Ar</td>
</tr>';
}
if (!empty($order['tranche_2'])) {
$html .= '
<tr>
<td><strong>Tranche 2 :</strong></td>
<td class="right">'.number_format((float)$order['tranche_2'], 0, '', ' ').' Ar</td>
</tr>';
}
$html .= ' $html .= '
</table> </table>
@ -1959,9 +2176,8 @@ public function print7(int $id)
</body> </body>
</html>'; </html>';
echo $html; return $this->response->setBody($html);
} }
// ==================================== // ====================================
// PRINT31 - Facture + Bon de commande (pages séparées) // PRINT31 - Facture + Bon de commande (pages séparées)
// ==================================== // ====================================
@ -1984,39 +2200,171 @@ public function print31(int $id)
$items = $OrderItems->getOrdersItemData($id); $items = $OrderItems->getOrdersItemData($id);
$company_info = $Company->getCompanyData(1); $company_info = $Company->getCompanyData(1);
// ✅ Vérifier si c'est une avance "sur mer"
$isAvanceMere = false;
foreach ($items as $item) {
if (!empty($item['product_name']) && empty($item['product_id'])) {
$isAvanceMere = true;
break;
}
}
$paid_status = $order_data['paid_status'] === 1 $paid_status = $order_data['paid_status'] === 1
? "<span style='color: green; font-weight: bold;'>Validé</span>" ? "<span style='color: green; font-weight: bold;'>Validé</span>"
: "<span style='color: red; font-weight: bold;'>Refusé</span>"; : "<span style='color: red; font-weight: bold;'>Refusé</span>";
// Calculs globaux
$discount = (float) $order_data['discount'];
$grossAmount = (float) $order_data['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT;
// --- FACTURES : Une par produit --- // --- FACTURES : Une par produit ---
foreach ($items as $item) { foreach ($items as $item) {
$p = $Products->getProductData($item['product_id']); // ✅ Utiliser getOrderItemDetails au lieu de getProductData directement
$unitPrice = (float) $item['amount']; $details = $this->getOrderItemDetails($item);
if (!$details) continue;
$unitPrice = $details['prix'];
$quantity = isset($item['qty']) ? (int) $item['qty'] : 1; $quantity = isset($item['qty']) ? (int) $item['qty'] : 1;
$subtotal = $unitPrice * $quantity; $subtotal = $unitPrice * $quantity;
$vatAmount = $subtotal * 0.2;
$discount = (float) $order_data['discount'];
$totalNet = $subtotal + $vatAmount - $discount;
$inWords = $this->numberToWords((int) round($subtotal));
// ✅ Récupérer le nom de la marque // ✅ Pour avance sur mer avec remise, utiliser la remise comme prix
$brandName = 'Non définie'; $prixAffiche = ($discount > 0 && $isAvanceMere) ? $discount : $unitPrice;
if (!empty($p['marque'])) {
$brandData = $Brand->find($p['marque']); echo '<!DOCTYPE html>';
if ($brandData && isset($brandData['name'])) { echo '<html lang="fr">';
$brandName = $brandData['name']; echo '<head><meta charset="utf-8">';
echo '<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">';
echo "<style>
body { font-family: Arial, sans-serif; font-size:14px; color:#000; margin:0; padding:0; }
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
.header .infos { line-height:1.4; }
.header img { max-height:80px; }
.client { margin-bottom:20px; }
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
th, td { border:1px solid #000; padding:6px; text-align:left; }
th { background:#f0f0f0; }
.right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin-top:50px; }
.signature div { text-align:center; }
.page-break { page-break-after: always; }
.to-fill { border-bottom: 1px dotted #000; min-width: 150px; display: inline-block; }
@media print {
body { font-size: 14px; }
@page { margin: 1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
} }
</style>";
echo '</head><body onload="window.print()">';
echo '<div class="page-break">';
echo '<div class="header">';
echo '<div class="infos">';
echo '<h2 style="margin:0;">' . esc($company_info['company_name']) . '</h2>';
echo '<p style="margin:2px 0;"><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>';
echo '<p style="margin:2px 0;"><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>';
echo '<p style="margin:2px 0;"><strong>Contact :</strong> ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '</p>';
echo '<p style="margin:2px 0;"><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>';
echo '</div>';
echo '<div style="text-align:center;">';
echo '<img src="' . base_url('assets/images/company_logo.jpg') . '" alt="Logo">';
echo '<p style="margin:5px 0; font-weight:bold;">Facture N° ' . esc($order_data['bill_no']) . '</p>';
echo '<p style="margin:5px 0;"><em>Antananarivo, le ' . date('d/m/Y') . '</em></p>';
echo '</div>';
echo '</div>';
echo '<div class="client">';
echo '<p><strong>DOIT :</strong> ' . esc($order_data['customer_name']) . '</p>';
echo '<p><strong>Adresse :</strong> ' . esc($order_data['customer_address']) . '</p>';
echo '<p><strong>Téléphone :</strong> ' . esc($order_data['customer_phone']) . '</p>';
echo '<p><strong>CIN :</strong> ' . esc($order_data['customer_cin']) . '</p>';
echo '</div>';
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
if ($isAvanceMere) {
// TABLE SIMPLIFIÉE POUR AVANCE "SUR MER"
echo '<table><thead><tr>';
echo '<th>Désignation</th>';
echo '<th>Produit à compléter</th>';
echo '<th class="right">Prix (Ar)</th>';
echo '</tr></thead><tbody>';
echo '<tr>';
echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td><span class="to-fill">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td>';
echo '<td class="right">' . number_format($prixAffiche, 0, '', ' ') . '</td>';
echo '</tr>';
// Afficher commentaire si existant
if (!empty($details['commentaire'])) {
echo '<tr><td colspan="3"><em style="font-size:12px; color:#666;">Remarque : ' . esc($details['commentaire']) . '</em></td></tr>';
} }
// ✅ Récupérer le nom de la catégorie echo '</tbody></table>';
} else {
// TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE
// Récupérer catégorie
$categoryName = 'Non définie'; $categoryName = 'Non définie';
if ($details['type'] === 'terre' && !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) { if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']); $categoryData = $Category->find($p['categorie_id']);
if ($categoryData && isset($categoryData['name'])) { if ($categoryData && isset($categoryData['name'])) {
$categoryName = $categoryData['name']; $categoryName = $categoryData['name'];
} }
} }
}
echo '<table><thead><tr>';
echo '<th>Nom</th><th>Marque</th><th>Catégorie</th><th>N° Moteur</th><th>Châssis</th><th>Puissance (CC)</th><th class="right">Prix Unit. (Ar)</th>';
echo '</tr></thead><tbody>';
echo '<tr>';
echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td>' . esc($details['marque']) . '</td>';
echo '<td>' . esc($categoryName) . '</td>';
echo '<td>' . esc($details['numero_moteur']) . '</td>';
echo '<td>' . esc($details['numero_chassis']) . '</td>';
echo '<td>' . esc($details['puissance']) . '</td>';
echo '<td class="right">' . number_format($prixAffiche, 0, '', ' ') . '</td>';
echo '</tr>';
echo '</tbody></table>';
}
// Récapitulatif pour cette facture
$itemHT = $prixAffiche / 1.20;
$itemTVA = $prixAffiche - $itemHT;
echo '<table>';
echo '<tr><td><strong>Total HT :</strong></td><td class="right">' . number_format($itemHT, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>TVA (20%) :</strong></td><td class="right">' . number_format($itemTVA, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>Total TTC :</strong></td><td class="right">' . number_format($prixAffiche, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>Statut :</strong></td><td class="right">' . $paid_status . '</td></tr>';
if (!empty($order_data['order_payment_mode'])) {
echo '<tr><td><strong>Mode de paiement :</strong></td><td class="right">' . esc($order_data['order_payment_mode']) . '</td></tr>';
}
if (!empty($order_data['tranche_1'])) {
echo '<tr><td><strong>Tranche 1 :</strong></td><td class="right">' . number_format((float)$order_data['tranche_1'], 0, '', ' ') . ' Ar</td></tr>';
}
if (!empty($order_data['tranche_2'])) {
echo '<tr><td><strong>Tranche 2 :</strong></td><td class="right">' . number_format((float)$order_data['tranche_2'], 0, '', ' ') . ' Ar</td></tr>';
}
echo '</table>';
echo '<div class="signature">';
echo '<div>L\'Acheteur<br><br>__________________</div>';
echo '<div>Le Vendeur<br><br>__________________</div>';
echo '</div>';
echo '</div>'; // fin page-break
echo '</body></html>';
}
// --- BON DE COMMANDE (UNE SEULE PAGE) ---
echo '<!DOCTYPE html>'; echo '<!DOCTYPE html>';
echo '<html lang="fr">'; echo '<html lang="fr">';
echo '<head><meta charset="utf-8">'; echo '<head><meta charset="utf-8">';
@ -2033,26 +2381,26 @@ public function print31(int $id)
.right { text-align:right; } .right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin-top:50px; } .signature { display:flex; justify-content:space-between; margin-top:50px; }
.signature div { text-align:center; } .signature div { text-align:center; }
.page-break { page-break-after: always; } .to-fill { border-bottom: 1px dotted #000; min-width: 150px; display: inline-block; }
.conditions { page-break-before: always; padding:20px; line-height:1.5; }
@media print { @media print {
body { font-size: 14px; } body { font-size: 14px; }
@page { margin: 1cm; } @page { margin: 1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
} }
</style>"; </style>";
echo '</head><body onload="window.print()">'; echo '</head><body>';
echo '<div class="page-break">';
echo '<div class="header">'; echo '<div class="header">';
echo '<div class="infos">'; echo '<div class="infos">';
echo '<h2 style="margin:0;">' . esc($company_info['company_name']) . '</h2>'; echo '<h2 style="margin:0;">' . esc($company_info['company_name']) . '</h2>';
echo '<p style="margin:2px 0;"><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>'; echo '<p style="margin:2px 0;"><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>';
echo '<p style="margin:2px 0;"><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>'; echo '<p style="margin:2px 0;"><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>';
echo '<p style="margin:2px 0;"><strong>Contact :</strong> ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '</p>'; echo '<p style="margin:2px 0;"><strong>Contact :</strong> ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '</p>';
echo '<p style="margin:2px 0;"><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>';
echo '</div>'; echo '</div>';
echo '<div style="text-align:center;">'; echo '<div style="text-align:center;">';
echo '<img src="' . base_url('assets/images/company_logo.jpg') . '" alt="Logo">'; echo '<img src="' . base_url('assets/images/company_logo.jpg') . '" alt="Logo">';
echo '<p style="margin:5px 0; font-weight:bold;">Facture N° ' . esc($order_data['bill_no']) . '</p>'; echo '<p style="margin:5px 0; font-weight:bold;">Bon de Commande N° ' . esc($order_data['bill_no']) . '</p>';
echo '<p style="margin:5px 0;"><em>Antananarivo, le ' . date('d/m/Y') . '</em></p>';
echo '</div>'; echo '</div>';
echo '</div>'; echo '</div>';
@ -2063,28 +2411,68 @@ public function print31(int $id)
echo '<p><strong>CIN :</strong> ' . esc($order_data['customer_cin']) . '</p>'; echo '<p><strong>CIN :</strong> ' . esc($order_data['customer_cin']) . '</p>';
echo '</div>'; echo '</div>';
// ✅ TABLEAU RÉCAPITULATIF SELON LE TYPE
if ($isAvanceMere) {
echo '<table><thead><tr>'; echo '<table><thead><tr>';
echo '<th>Nom</th><th>Marque</th><th>Catégorie</th><th>N° Moteur</th><th>Châssis</th><th>Puissance (CC)</th><th class="right">Prix Unit. (Ar)</th>'; echo '<th>Désignation</th>';
echo '<th>Produit à compléter</th>';
echo '<th class="right">Prix (Ar)</th>';
echo '</tr></thead><tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
echo '<tr>';
echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td><span class="to-fill">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td>';
echo '<td class="right">' . number_format($prixAffiche, 0, '', ' ') . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<table><thead><tr>';
echo '<th>Nom</th><th>Marque</th><th>Catégorie</th><th>N° Moteur</th><th>Châssis</th><th>Puissance</th><th class="right">Prix (Ar)</th>';
echo '</tr></thead><tbody>'; echo '</tr></thead><tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
// Catégorie
$categoryName = 'Non définie';
if ($details['type'] === 'terre' && !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']);
if ($categoryData) $categoryName = $categoryData['name'];
}
}
echo '<tr>'; echo '<tr>';
echo '<td>' . esc($p['name']) . '</td>'; echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td>' . esc($brandName) . '</td>'; echo '<td>' . esc($details['marque']) . '</td>';
echo '<td>' . esc($categoryName) . '</td>'; echo '<td>' . esc($categoryName) . '</td>';
echo '<td>' . esc($p['numero_de_moteur']) . '</td>'; echo '<td>' . esc($details['numero_moteur']) . '</td>';
echo '<td>' . esc($p['chasis'] ?? '') . '</td>'; echo '<td>' . esc($details['numero_chassis']) . '</td>';
echo '<td>' . esc($p['puissance']) . '</td>'; echo '<td>' . esc($details['puissance']) . '</td>';
echo '<td class="right">' . number_format($amount, 0, '', ' ') . '</td>'; echo '<td class="right">' . number_format($prixAffiche, 0, '', ' ') . '</td>';
echo '</tr>'; echo '</tr>';
} }
$tva = $total_ht * 0.2;
$total_ttc = $total_ht + $tva - (float) $order_data['discount'];
echo '</tbody></table>'; echo '</tbody></table>';
}
echo '<table>'; echo '<table>';
echo '<tr><td><strong>Total HT :</strong></td><td class="right">' . number_format($total_ht, 0, '', ' ') . ' Ar</td></tr>'; echo '<tr><td><strong>Total HT :</strong></td><td class="right">' . number_format($totalHT, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>TVA :</strong></td><td class="right">' . number_format($tva, 0, '', ' ') . ' Ar</td></tr>'; echo '<tr><td><strong>TVA :</strong></td><td class="right">' . number_format($tva, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>Réduction :</strong></td><td class="right">' . number_format($order_data['discount'], 0, '', ' ') . ' Ar</td></tr>'; echo '<tr><td><strong>Réduction :</strong></td><td class="right">' . number_format($discount, 0, '', ' ') . ' Ar</td></tr>';
echo '<tr><td><strong>Total TTC :</strong></td><td class="right">' . number_format($total_ttc, 0, '', ' ') . ' Ar</td></tr>'; echo '<tr><td><strong>Total TTC :</strong></td><td class="right">' . number_format($totalTTC, 0, '', ' ') . ' Ar</td></tr>';
echo '</table>'; echo '</table>';
echo '<div class="signature">'; echo '<div class="signature">';
@ -2093,7 +2481,7 @@ public function print31(int $id)
echo '</div>'; echo '</div>';
// --- CONDITIONS GÉNÉRALES --- // --- CONDITIONS GÉNÉRALES ---
echo '<div style="page-break-before: always; break-before: page; padding:20px; line-height:1.5;">'; echo '<div class="conditions">';
echo '<div style="display:flex; justify-content:space-between; align-items:center;">'; echo '<div style="display:flex; justify-content:space-between; align-items:center;">';
echo '<h3 style="margin:0;">Conditions Générales</h3>'; echo '<h3 style="margin:0;">Conditions Générales</h3>';
echo '<img src="' . base_url('assets/images/company_logo.jpg') . '" alt="Logo" style="height:60px;">'; echo '<img src="' . base_url('assets/images/company_logo.jpg') . '" alt="Logo" style="height:60px;">';
@ -2108,7 +2496,6 @@ public function print31(int $id)
echo '<div style="text-align:center; margin-top:50px;">L\'Acheteur</div>'; echo '<div style="text-align:center; margin-top:50px;">L\'Acheteur</div>';
echo '</div>'; echo '</div>';
echo '</div>';
echo '</body></html>'; echo '</body></html>';
} }

12
app/Controllers/ProductCOntroller.php

@ -82,7 +82,15 @@ class ProductCOntroller extends AdminController
return "$name"; return "$name";
} }
$data = $Products->getProductData(); // ✅ Utiliser la nouvelle méthode qui filtre automatiquement par rôle et store
$data = $Products->getProductDataByRole();
// ✅ Debug : Logs pour vérifier le filtrage
$session = session();
$user = $session->get('user');
log_message('debug', '=== fetchProductData ===');
log_message('debug', 'User: ' . ($user['username'] ?? 'N/A') . ' (Group: ' . ($user['group_name'] ?? 'N/A') . ', Store: ' . ($user['store_id'] ?? 'N/A') . ')');
log_message('debug', 'Products found: ' . count($data));
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
// Gestion du nom du magasin // Gestion du nom du magasin
@ -139,7 +147,7 @@ class ProductCOntroller extends AdminController
'<div class="no-image">Aucune image</div>'; '<div class="no-image">Aucune image</div>';
$result['data'][$key] = [ $result['data'][$key] = [
$imageHtml, // Correction : utiliser $imageHtml au lieu de $value['image'] $imageHtml,
convertString($value['sku']), convertString($value['sku']),
$value['name'], $value['name'],
$value['prix_vente'], $value['prix_vente'],

194
app/Helpers/alerts_helper.php

@ -4,13 +4,14 @@ use App\Models\Users;
use App\Models\Avance; use App\Models\Avance;
use App\Models\AlertMail; use App\Models\AlertMail;
/**
* Vérifier les deadlines et envoyer des alertes email
*/
function checkDeadlineAlerts() function checkDeadlineAlerts()
{ {
log_message('info', "=== DÉBUT checkDeadlineAlerts ==="); log_message('info', "=== DÉBUT checkDeadlineAlerts ===");
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt'; $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()); file_put_contents($cacheFile, time());
$avanceModel = new Avance(); $avanceModel = new Avance();
@ -20,15 +21,16 @@ function checkDeadlineAlerts()
$today = date('Y-m-d'); $today = date('Y-m-d');
log_message('info', "Date du jour: {$today}"); log_message('info', "Date du jour: {$today}");
// Modification pour vérifier les avances dans 0-3 jours // Récupération des avances dans 0-3 jours
$avances = $avanceModel $avances = $avanceModel
->where('DATE(deadline) >=', $today) // Inclut le jour même ->where('DATE(deadline) >=', $today)
->where('DATE(deadline) <=', date('Y-m-d', strtotime('+3 days'))) ->where('DATE(deadline) <=', date('Y-m-d', strtotime('+3 days')))
->where('active', 1) ->where('active', 1)
->findAll(); ->findAll();
log_message('info', "Nombre d'avances trouvées (0-3 jours): " . count($avances)); log_message('info', "Nombre d'avances trouvées (0-3 jours): " . count($avances));
// Récupération des utilisateurs DAF
$users = $usersModel->select('users.email, users.firstname, users.lastname') $users = $usersModel->select('users.email, users.firstname, users.lastname')
->join('user_group', 'user_group.user_id = users.id') ->join('user_group', 'user_group.user_id = users.id')
->join('groups', 'groups.id = user_group.group_id') ->join('groups', 'groups.id = user_group.group_id')
@ -56,7 +58,7 @@ function checkDeadlineAlerts()
log_message('info', "Avance ID: {$avance['avance_id']}, Deadline: {$deadline}, Jours restants: {$daysLeft}"); log_message('info', "Avance ID: {$avance['avance_id']}, Deadline: {$deadline}, Jours restants: {$daysLeft}");
// Modification des types d'alerte pour 0, 1, 2, 3 jours // Détermination du type d'alerte
$alertType = match($daysLeft) { $alertType = match($daysLeft) {
3 => 'deadline_3_days', 3 => 'deadline_3_days',
2 => 'deadline_2_days', 2 => 'deadline_2_days',
@ -83,10 +85,12 @@ function checkDeadlineAlerts()
continue; continue;
} }
// Message modifié pour inclure le cas du jour même // Construction du message
$urgencyText = $daysLeft === 0 ? "ÉCHÉANCE AUJOURD'HUI" : "{$daysLeft} jour(s) restant(s)"; $urgencyText = $daysLeft === 0 ? "ÉCHÉANCE AUJOURD'HUI" : "{$daysLeft} jour(s) restant(s)";
$message = " $message = "
<h3>⚠️ URGENT : Avance approchant de la deadline</h3> <html>
<body style='font-family: Arial, sans-serif; color: #333;'>
<h3 style='color: #d9534f;'>⚠️ URGENT : Avance approchant de la deadline</h3>
<p><strong>ID Avance :</strong> {$avance['avance_id']}</p> <p><strong>ID Avance :</strong> {$avance['avance_id']}</p>
<p><strong>Client :</strong> {$avance['customer_name']}</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 avance :</strong> " . number_format($avance['avance_amount'], 0, ',', ' ') . " Ar</p>
@ -95,19 +99,21 @@ function checkDeadlineAlerts()
<p><strong>Statut :</strong> <span style='color: red; font-weight: bold;'>{$urgencyText}</span></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>Téléphone client :</strong> {$avance['customer_phone']}</p>
<p><strong>Adresse client :</strong> {$avance['customer_address']}</p> <p><strong>Adresse client :</strong> {$avance['customer_address']}</p>
<hr> <hr style='border: 1px solid #ddd;'>
<p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p> <p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p>
</body>
</html>
"; ";
$emailsSent = 0; $emailsSent = 0;
foreach ($emails as $to) {
log_message('info', "Tentative d'envoi email à: {$to}");
$subject = $daysLeft === 0 $subject = $daysLeft === 0
? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI" ? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI"
: "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)"; : "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)";
if (sendEmailInBackground($to, $subject, $message)) { foreach ($emails as $to) {
log_message('info', "Tentative d'envoi email à: {$to}");
if (sendEmailWithBrevo($to, $subject, $message)) {
$emailsSent++; $emailsSent++;
log_message('info', "Email envoyé avec succès à: {$to}"); log_message('info', "Email envoyé avec succès à: {$to}");
} else { } else {
@ -115,6 +121,7 @@ function checkDeadlineAlerts()
} }
} }
// Enregistrement de l'alerte si au moins un email a été envoyé
if ($emailsSent > 0) { if ($emailsSent > 0) {
log_message('info', "Insertion alerte pour avance_id={$avance['avance_id']} avec type {$alertType}"); log_message('info', "Insertion alerte pour avance_id={$avance['avance_id']} avec type {$alertType}");
$alertMailModel->insert([ $alertMailModel->insert([
@ -128,49 +135,182 @@ function checkDeadlineAlerts()
log_message('error', "Aucun email envoyé pour avance_id={$avance['avance_id']} avec type {$alertType}"); log_message('error', "Aucun email envoyé pour avance_id={$avance['avance_id']} avec type {$alertType}");
} }
} }
checkAndConvertCompletedAvances();
log_message('info', "=== FIN checkDeadlineAlerts ==="); log_message('info', "=== FIN checkDeadlineAlerts ===");
// ✅ NOUVELLE FONCTIONNALITÉ : Gérer les avances expirées
handleExpiredAvances();
} }
function sendEmailInBackground($to, $subject, $message) /**
* ✅ NOUVELLE FONCTION : Gérer automatiquement les avances expirées
* - Libérer les produits (remettre en stock)
* - Désactiver les avances
*/
function handleExpiredAvances()
{ {
log_message('info', "=== DÉBUT handleExpiredAvances ===");
$avanceModel = new Avance();
$productsModel = new \App\Models\Products();
$today = date('Y-m-d');
log_message('info', "Vérification des avances expirées au: {$today}");
// Récupérer les avances expirées et encore actives
$expiredAvances = $avanceModel
->where('DATE(deadline) <', $today)
->where('active', 1)
->where('is_order', 0)
->findAll();
log_message('info', "Nombre d'avances expirées trouvées: " . count($expiredAvances));
if (empty($expiredAvances)) {
log_message('info', "Aucune avance expirée à traiter");
log_message('info', "=== FIN handleExpiredAvances ===");
return;
}
$processedCount = 0;
$errorCount = 0;
foreach ($expiredAvances as $avance) {
try { try {
log_message('info', "Préparation envoi email à: {$to}"); log_message('info', "Traitement avance expirée ID: {$avance['avance_id']}, Client: {$avance['customer_name']}, Deadline: {$avance['deadline']}");
// ✅ Désactiver l'avance
$updateResult = $avanceModel->update($avance['avance_id'], ['active' => 0]);
if (!$updateResult) {
log_message('error', "Échec désactivation avance ID: {$avance['avance_id']}");
$errorCount++;
continue;
}
log_message('info', "Avance ID {$avance['avance_id']} désactivée avec succès");
// ✅ Libérer le produit UNIQUEMENT pour les avances "sur terre" avec product_id
if ($avance['type_avance'] === 'terre' && !empty($avance['product_id'])) {
$productUpdateResult = $productsModel->update($avance['product_id'], ['product_sold' => 0]);
if ($productUpdateResult) {
log_message('info', "Produit ID {$avance['product_id']} libéré (remis en stock)");
} else {
log_message('warning', "Échec libération produit ID: {$avance['product_id']}");
}
} elseif ($avance['type_avance'] === 'mere') {
log_message('info', "Avance 'sur mer' - Pas de produit à libérer (product_name: {$avance['product_name']})");
} else {
log_message('info', "Pas de product_id à libérer pour avance ID: {$avance['avance_id']}");
}
$processedCount++;
} catch (\Exception $e) {
log_message('error', "Erreur traitement avance expirée ID {$avance['avance_id']}: " . $e->getMessage());
$errorCount++;
}
}
log_message('info', "Avances expirées traitées: {$processedCount}, Erreurs: {$errorCount}");
log_message('info', "=== FIN handleExpiredAvances ===");
}
/**
* ✅ Vérifier et convertir automatiquement les avances complètes en commandes
* À appeler dans checkDeadlineAlerts() ou via un cron job
*/
function checkAndConvertCompletedAvances()
{
log_message('info', "=== DÉBUT checkAndConvertCompletedAvances ===");
$avanceModel = new \App\Models\Avance();
// Récupérer toutes les avances complètes non encore converties
$completedAvances = $avanceModel
->where('amount_due', 0)
->where('is_order', 0) // Pas encore converties
->where('active', 1) // Encore actives
->findAll();
log_message('info', "Avances complètes trouvées : " . count($completedAvances));
$convertedCount = 0;
$errorCount = 0;
foreach ($completedAvances as $avance) {
log_message('info', "Traitement avance complète ID: {$avance['avance_id']}, Client: {$avance['customer_name']}");
$order_id = $avanceModel->convertToOrder($avance['avance_id']);
if ($order_id) {
$convertedCount++;
log_message('info', "✅ Avance {$avance['avance_id']} convertie en commande {$order_id}");
} else {
$errorCount++;
log_message('error', "❌ Échec conversion avance {$avance['avance_id']}");
}
}
log_message('info', "Avances converties : {$convertedCount}, Erreurs : {$errorCount}");
log_message('info', "=== FIN checkAndConvertCompletedAvances ===");
}
/**
* Envoyer un email via Brevo
*/
function sendEmailWithBrevo($to, $subject, $message)
{
try {
log_message('info', "Préparation envoi email via Brevo à: {$to}");
$email = \Config\Services::email(); $email = \Config\Services::email();
// Configuration Brevo depuis le fichier .env
$config = [ $config = [
'protocol' => 'smtp', 'protocol' => env('email.protocol', 'smtp'),
'SMTPHost' => 'smtp.gmail.com', 'SMTPHost' => env('email.SMTPHost', 'smtp-relay.brevo.com'),
'SMTPUser' => 'rey342505@gmail.com', 'SMTPUser' => env('email.SMTPUser'),
'SMTPPass' => 'loirqovmfuxnasrm', 'SMTPPass' => env('email.SMTPPass'),
'SMTPPort' => 587, 'SMTPPort' => env('email.SMTPPort', 587),
'SMTPCrypto' => 'tls', 'SMTPCrypto' => env('email.SMTPCrypto', 'tls'),
'mailType' => 'html', 'mailType' => 'html',
'charset' => 'utf-8', 'charset' => 'utf-8',
'newline' => "\r\n" 'newline' => "\r\n",
'wordWrap' => true,
'validation' => true
]; ];
log_message('info', "Configuration Brevo - Host: {$config['SMTPHost']}, Port: {$config['SMTPPort']}, User: {$config['SMTPUser']}");
$email->initialize($config); $email->initialize($config);
$email->setFrom('rey342505@gmail.com', 'Système Motorbike - Alertes Avances'); // Utilisation de l'email configuré dans .env
$fromEmail = env('email.fromEmail', 'noreply@motorbike.mg');
$fromName = env('email.fromName', 'Système Motorbike - Alertes');
$email->setFrom($fromEmail, $fromName);
$email->setTo($to); $email->setTo($to);
$email->setSubject($subject); $email->setSubject($subject);
$email->setMessage($message); $email->setMessage($message);
log_message('info', "Configuration email terminée, tentative d'envoi..."); log_message('info', "Configuration email Brevo terminée, tentative d'envoi...");
if (!$email->send()) { if (!$email->send()) {
$debugInfo = $email->printDebugger(['headers']); $debugInfo = $email->printDebugger(['headers', 'subject', 'body']);
log_message('error', "Erreur email à {$to}: " . print_r($debugInfo, true)); log_message('error', "Erreur email Brevo à {$to}: " . print_r($debugInfo, true));
return false; return false;
} }
log_message('info', "Email envoyé avec succès à: {$to}"); log_message('info', "Email envoyé avec succès via Brevo à: {$to}");
return true; return true;
} catch (\Exception $e) { } catch (\Exception $e) {
log_message('error', "Exception email à {$to}: " . $e->getMessage()); log_message('error', "Exception email Brevo à {$to}: " . $e->getMessage());
log_message('error', "Stack trace: " . $e->getTraceAsString());
return false; return false;
} }
} }

356
app/Models/Avance.php

@ -11,17 +11,15 @@ class Avance extends Model {
'avance_amount', 'avance_date','user_id', 'avance_amount', 'avance_date','user_id',
'customer_name', 'customer_address', 'customer_phone', 'customer_cin', 'customer_name', 'customer_address', 'customer_phone', 'customer_cin',
'gross_amount','amount_due','product_id','is_order','active','store_id', 'gross_amount','amount_due','product_id','is_order','active','store_id',
'type_avance','type_payment', 'deadline','commentaire','product_name' // Ajout du champ type et deadline 'type_avance','type_payment', 'deadline','commentaire','product_name'
]; ];
public function createAvance(array $data) { public function createAvance(array $data) {
try { try {
// Si la date de création n'est pas définie, on prend aujourd'hui
if (empty($data['avance_date'])) { if (empty($data['avance_date'])) {
$data['avance_date'] = date('Y-m-d'); $data['avance_date'] = date('Y-m-d');
} }
// Calcul de la deadline en fonction du type
if (!empty($data['type'])) { if (!empty($data['type'])) {
if (strtolower($data['type']) === 'avance sur terre') { if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days')); $data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
@ -43,7 +41,6 @@ class Avance extends Model {
return false; return false;
} }
try { try {
// Recalcul de la deadline si le type change
if (!empty($data['type']) && !empty($data['avance_date'])) { if (!empty($data['type']) && !empty($data['avance_date'])) {
if (strtolower($data['type']) === 'avance sur terre') { if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days')); $data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
@ -58,7 +55,6 @@ class Avance extends Model {
} }
} }
// 📌 Le reste de tes fonctions restent inchangées
public function getAllAvanceData(int $id=null) { public function getAllAvanceData(int $id=null) {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
@ -115,48 +111,47 @@ class Avance extends Model {
} }
public function fetchSingleAvance(int $avance_id){ public function fetchSingleAvance(int $avance_id){
return $this->where('avance_id',$avance_id)->first(); return $this->select('avances.*, products.name as product_name_db, products.prix_vente as product_price')
->join('products', 'products.id = avances.product_id', 'left')
->where('avances.avance_id', $avance_id)
->first();
} }
public function removeAvance(int $avance_id){ public function removeAvance(int $avance_id){
return $this->delete($avance_id); return $this->delete($avance_id);
} }
// ✅ CORRECTION : getTotalAvance pour la caissière
public function getTotalAvance() { public function getTotalAvance() {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']); $isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
try { try {
return $this->select('SUM(avance_amount) AS ta') $builder = $this->select('SUM(avance_amount) AS ta')
->where('is_order', 0) ->where('is_order', 0)
->get() ->where('active', 1); // ✅ Ajout du filtre active
->getRowObject();
} catch (\Exception $e) { if (!$isAdmin) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage()); $builder->where('store_id', $users['store_id']); // ✅ Filtre par store pour caissière
return false;
} }
} else {
try { return $builder->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) { } catch (\Exception $e) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage()); log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
return false; return (object) ['ta' => 0]; // ✅ Retourner un objet avec ta = 0 en cas d'erreur
}
} }
} }
// ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière
public function getPaymentModesAvance() public function getPaymentModesAvance()
{ {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']); $isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if ($isAdmin) { try {
return $this->db->table('avances') $builder = $this->db->table('avances')
->select(' ->select('
SUM(avance_amount) AS total, SUM(avance_amount) AS total,
SUM(CASE WHEN LOWER(type_payment) = "mvola" THEN avance_amount ELSE 0 END) AS total_mvola, SUM(CASE WHEN LOWER(type_payment) = "mvola" THEN avance_amount ELSE 0 END) AS total_mvola,
@ -164,9 +159,17 @@ class Avance extends Model {
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
') ')
->where('active', 1) ->where('active', 1)
->get() ->where('is_order', 0); // ✅ Exclure les avances devenues orders
->getRowObject();
} else { // ✅ CORRECTION : Ajouter le filtre store_id pour la caissière
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
$result = $builder->get()->getRowObject();
// ✅ Gérer le cas où il n'y a pas de résultats
if (!$result) {
return (object) [ return (object) [
'total' => 0, 'total' => 0,
'total_mvola' => 0, 'total_mvola' => 0,
@ -174,9 +177,19 @@ class Avance extends Model {
'total_virement_bancaire' => 0 'total_virement_bancaire' => 0
]; ];
} }
}
return $result;
} catch (\Exception $e) {
log_message('error', 'Erreur getPaymentModesAvance: ' . $e->getMessage());
return (object) [
'total' => 0,
'total_mvola' => 0,
'total_espece' => 0,
'total_virement_bancaire' => 0
];
}
}
public function getAllAvanceData1(int $id=null) { public function getAllAvanceData1(int $id=null) {
$session = session(); $session = session();
@ -221,7 +234,9 @@ class Avance extends Model {
} }
try { try {
return $this return $this
->where('is_order',0) ->where('is_order',1) // ✅ Correction: devrait être 1, pas 0
->where('active',1) // ✅ Ajout du filtre active
->where('store_id',$users['store_id']) // ✅ Ajout du filtre store
->orderBy('avance_date', 'DESC') ->orderBy('avance_date', 'DESC')
->findAll(); ->findAll();
} catch (\Exception $e) { } catch (\Exception $e) {
@ -297,13 +312,13 @@ class Avance extends Model {
foreach ($avances as $avance) { foreach ($avances as $avance) {
$this->update($avance['avance_id'], ['active' => '0']); $this->update($avance['avance_id'], ['active' => '0']);
if (!empty($avance['product_id'])) { // ✅ Vérifier que product_id existe
$productModel->update($avance['product_id'], ['product_sold' => 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) public function getAvancesNearDeadline($days = 3)
{ {
$alertDate = date('Y-m-d', strtotime("+{$days} days")); $alertDate = date('Y-m-d', strtotime("+{$days} days"));
@ -316,7 +331,7 @@ public function getAvancesNearDeadline($days = 3)
->where('DATE(avances.deadline)', $alertDate) ->where('DATE(avances.deadline)', $alertDate)
->findAll(); ->findAll();
} }
// Avances incomplètes (reste à payer > 0 et non transformées en commande)
public function getIncompleteAvances(int $id = null) public function getIncompleteAvances(int $id = null)
{ {
$session = session(); $session = session();
@ -338,7 +353,6 @@ public function getIncompleteAvances(int $id = null)
return $builder->orderBy('avance_date', 'DESC')->findAll(); return $builder->orderBy('avance_date', 'DESC')->findAll();
} }
// Avances complètes (reste à payer = 0 et non transformées en commande)
public function getCompletedAvances(int $id = null) public function getCompletedAvances(int $id = null)
{ {
$session = session(); $session = session();
@ -360,7 +374,281 @@ public function getCompletedAvances(int $id = null)
return $builder->orderBy('avance_date', 'DESC')->findAll(); return $builder->orderBy('avance_date', 'DESC')->findAll();
} }
public function markAsPrinted($avance_id)
{
try {
return $this->update($avance_id, [
'is_printed' => 1
]);
} catch (\Exception $e) {
log_message('error', 'Erreur markAsPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Marquer une avance comme non imprimée (quand elle est modifiée)
*/
public function markAsNotPrinted($avance_id)
{
try {
return $this->update($avance_id, [
'is_printed' => 0,
'last_modified_at' => date('Y-m-d H:i:s')
]);
} catch (\Exception $e) {
log_message('error', 'Erreur markAsNotPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Vérifier si une avance a déjà été imprimée
*/
public function isPrinted($avance_id)
{
try {
$avance = $this->find($avance_id);
return $avance ? (bool)$avance['is_printed'] : false;
} catch (\Exception $e) {
log_message('error', 'Erreur isPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Récupérer un produit avec le nom de sa marque
* @param int $product_id
* @return array|null
*/
public function getProductWithBrand($product_id)
{
try {
return $this->select('products.*, brands.name as brand_name')
->join('brands', 'brands.id = products.marque', 'left')
->where('products.id', $product_id)
->first();
} catch (\Exception $e) {
log_message('error', 'Erreur getProductWithBrand: ' . $e->getMessage());
return null;
}
}
// À ajouter dans App\Models\Avance.php
/**
* ✅ Convertir une avance complète en commande
* Appelé automatiquement quand amount_due atteint 0
*
* @param int $avance_id
* @return int|false ID de la commande créée ou false
*/
public function convertToOrder(int $avance_id)
{
try {
$avance = $this->find($avance_id);
if (!$avance) {
log_message('error', "Avance introuvable : {$avance_id}");
return false;
}
// ✅ MODIFICATION PRINCIPALE : Vérifier que c'est bien une avance sur TERRE
if ($avance['type_avance'] !== 'terre') {
log_message('info', "Avance {$avance_id} de type '{$avance['type_avance']}' - Conversion ignorée (seules les avances TERRE sont converties)");
return false;
}
// ✅ Vérifier que l'avance est bien complète
if ((float)$avance['amount_due'] > 0) {
log_message('warning', "Avance TERRE {$avance_id} non complète (amount_due={$avance['amount_due']})");
return false;
} }
// ✅ Vérifier qu'elle n'a pas déjà été convertie
if ((int)$avance['is_order'] === 1) {
log_message('info', "Avance TERRE {$avance_id} déjà convertie en commande");
return false;
}
// ✅ Vérifier que le produit existe (obligatoire pour avance TERRE)
if (empty($avance['product_id'])) {
log_message('error', "Avance TERRE {$avance_id} sans product_id - Impossible de convertir");
return false;
}
$db = \Config\Database::connect();
$db->transStart();
// ✅ 1. Créer la commande
$orderModel = new \App\Models\Orders();
$bill_no = 'BILAV-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
$orderData = [
'bill_no' => $bill_no,
'customer_name' => $avance['customer_name'],
'customer_address' => $avance['customer_address'],
'customer_phone' => $avance['customer_phone'],
'customer_cin' => $avance['customer_cin'],
'date_time' => date('Y-m-d H:i:s'),
'gross_amount' => $avance['gross_amount'],
'net_amount' => $avance['gross_amount'],
'discount' => 0,
'paid_status' => 2, // En attente validation caissière
'user_id' => $avance['user_id'],
'store_id' => $avance['store_id'],
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
'tranche_1' => $avance['avance_amount'],
'tranche_2' => null,
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
'order_payment_mode_1' => null,
];
$order_id = $orderModel->insert($orderData);
if (!$order_id) {
throw new \Exception("Échec création commande pour avance TERRE {$avance_id}");
}
log_message('info', "✅ Commande {$bill_no} créée depuis avance TERRE {$avance_id}");
// ✅ 2. Créer les items de commande
$orderItemModel = new \App\Models\OrderItems();
$productModel = new \App\Models\Products();
$product = $productModel->find($avance['product_id']);
if ($product) {
$orderItemModel->insert([
'order_id' => $order_id,
'product_id' => $avance['product_id'],
'rate' => $avance['gross_amount'],
'qty' => 1,
'amount' => $avance['gross_amount'],
]);
log_message('info', "Item ajouté : produit {$avance['product_id']} (TERRE)");
// ✅ Le produit reste marqué comme vendu (product_sold = 1)
// Il sera géré dans la commande maintenant
} else {
log_message('warning', "Produit {$avance['product_id']} introuvable pour avance TERRE {$avance_id}");
}
// ✅ 3. Marquer l'avance comme convertie
$this->update($avance_id, [
'is_order' => 1,
'active' => 0, // Désactiver l'avance (elle devient commande)
]);
$db->transComplete();
if ($db->transStatus() === false) {
log_message('error', "Transaction échouée pour avance TERRE {$avance_id}");
return false;
}
// ✅ 4. Notification à la caissière
$notificationController = new \App\Controllers\NotificationController();
$notificationController->createNotification(
"Nouvelle commande issue d'une avance TERRE complète - {$bill_no}",
"Caissière",
(int)$avance['store_id'],
"orders"
);
log_message('info', "✅ Avance TERRE {$avance_id} convertie en commande {$order_id} ({$bill_no})");
return $order_id;
} catch (\Exception $e) {
log_message('error', "Erreur conversion avance TERRE→commande : " . $e->getMessage());
return false;
}
}
/**
* ✅ Hook appelé automatiquement lors du paiement d'une avance
* Intégrer ceci dans votre fonction de paiement existante
*/
public function afterPayment(int $avance_id)
{
$avance = $this->find($avance_id);
if (!$avance) {
return false;
}
// ✅ Si l'avance est maintenant complète ET que c'est une avance TERRE
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'terre') {
log_message('info', "💰 Avance TERRE {$avance_id} complète ! Conversion automatique en commande...");
return $this->convertToOrder($avance_id);
}
// ✅ Si c'est une avance MER complète, on ne fait rien (elle reste dans la liste)
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'mere') {
log_message('info', "💰 Avance MER {$avance_id} complète ! Elle reste dans la liste des avances.");
}
return true;
}
/**
* ✅ Générer un numéro de facture unique
*/
private function generateBillNumber($store_id)
{
$db = \Config\Database::connect();
// Récupérer le dernier numéro pour ce store
$query = $db->query(
"SELECT bill_no FROM orders WHERE store_id = ? ORDER BY id DESC LIMIT 1",
[$store_id]
);
$result = $query->getRow();
if ($result && preg_match('/(\d+)$/', $result->bill_no, $matches)) {
$lastNumber = intval($matches[1]);
$newNumber = $lastNumber + 1;
} else {
$newNumber = 1;
}
// Format: BILL-STORE{store_id}-{number}
return 'BILL-STORE' . $store_id . '-' . str_pad($newNumber, 5, '0', STR_PAD_LEFT);
}
/**
* ✅ Récupérer toutes les avances complètes non converties
*/
public function getCompletedNotConverted()
{
return $this->where('amount_due', 0)
->where('is_order', 0)
->where('active', 1)
->where('type_avance', 'terre') // ✅ Uniquement TERRE à convertir
->findAll();
}
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les avances MER complètes (pour statistiques)
*/
public function getCompletedMerAvances()
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
$builder = $this->where('amount_due', 0)
->where('active', 1)
->where('type_avance', 'mere');
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
}
}

124
app/Models/Products.php

@ -6,14 +6,46 @@ use CodeIgniter\Model;
class Products extends Model class Products extends Model
{ {
/**
* table products
* @var string
*/
protected $table = 'products'; protected $table = 'products';
protected $primaryKey = 'id'; protected $primaryKey = 'id';
protected $allowedFields = ['name', 'sku', 'price', 'product_sold', 'qty', 'image', 'description', 'numero_de_moteur', 'marque', 'chasis', 'store_id', 'availability', 'is_piece', 'prix_vente', 'date_arivage', 'puissance', 'cler', 'categorie_id', 'etats','type', 'infoManquekit', 'info', 'infoManque']; protected $allowedFields = ['name', 'sku', 'price', 'product_sold', 'qty', 'image', 'description', 'numero_de_moteur', 'marque', 'chasis', 'store_id', 'availability', 'is_piece', 'prix_vente', 'date_arivage', 'puissance', 'cler', 'categorie_id', 'etats','type', 'infoManquekit', 'info', 'infoManque'];
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les produits selon le rôle et le store de l'utilisateur
* @param int|null $id
* @return array|object|null
*/
public function getProductDataByRole(int $id = null)
{
$session = session();
$user = $session->get('user');
// Vérifier si l'utilisateur est admin (Conseil ou Direction)
$isAdmin = in_array($user['group_name'], ['Conseil', 'Direction']);
$builder = $this->where('is_piece', 0)
->where('product_sold', 0);
// ✅ Si pas admin ET a un store_id valide, filtrer par son magasin
if (!$isAdmin) {
// ✅ Si l'utilisateur n'a pas de store_id (NULL ou 0), ne retourner aucun produit
if (empty($user['store_id']) || $user['store_id'] == 0) {
// Retourner une requête impossible pour avoir 0 résultats
$builder->where('id', -1);
} else {
// Filtrer par le store_id de l'utilisateur
$builder->where('store_id', $user['store_id']);
}
}
// Si un ID spécifique est demandé
if ($id) {
return $builder->where('id', $id)->first();
}
return $builder->orderBy('id', 'DESC')->findAll();
}
/** /**
* get the brand data * get the brand data
* @param int $id * @param int $id
@ -22,7 +54,6 @@ class Products extends Model
public function getProductData(int $id = null) public function getProductData(int $id = null)
{ {
if ($id) { if ($id) {
return $this->where('id', $id)->first(); return $this->where('id', $id)->first();
} }
@ -32,17 +63,12 @@ class Products extends Model
])->orderBy('id', 'DESC')->findAll(); ])->orderBy('id', 'DESC')->findAll();
} }
public function getProductData2(int $id) public function getProductData2(int $id)
{ {
$builder = $this->where('is_piece', 0) $builder = $this->where('is_piece', 0)
->where('product_sold', 0) ->where('product_sold', 0)
->where('store_id', $id); ->where('store_id', $id);
// if ($id != 0) {
// $builder = $builder->where('store_id', $id);
// }
return $builder->join('brands', 'brands.id = products.marque') return $builder->join('brands', 'brands.id = products.marque')
->orderBy('products.id', 'DESC') ->orderBy('products.id', 'DESC')
->select('brands.name as brand_name,COUNT( products.id) as total_product, products.store_id as store_id,products.*') ->select('brands.name as brand_name,COUNT( products.id) as total_product, products.store_id as store_id,products.*')
@ -50,15 +76,12 @@ class Products extends Model
->findAll(); ->findAll();
} }
public function getProductData3(int $id) public function getProductData3(int $id)
{ {
if ($id == 0) { if ($id == 0) {
return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll(); return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll();
} }
// Fetch all products, ordered by ID descending
return $this->where('is_piece', 0)->where('product_sold', 0)->where('store_id', $id)->orderBy('id', 'DESC')->findAll(); return $this->where('is_piece', 0)->where('product_sold', 0)->where('store_id', $id)->orderBy('id', 'DESC')->findAll();
} }
@ -80,7 +103,6 @@ class Products extends Model
$builder->where("id NOT IN ($subQuery)", null, false); $builder->where("id NOT IN ($subQuery)", null, false);
} }
// Si on modifie et qu'on veut inclure le produit actuel dans la liste
if ($currentProductId) { if ($currentProductId) {
$builder->orWhere('id', $currentProductId); $builder->orWhere('id', $currentProductId);
} }
@ -88,29 +110,14 @@ class Products extends Model
return $builder->orderBy('id', 'DESC')->findAll(); return $builder->orderBy('id', 'DESC')->findAll();
} }
/**
* Get active products (availability = 1)
* @return array
*/
public function getActiveProductData() public function getActiveProductData()
{ {
return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll(); return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll();
} }
/**
* Assigner un utilisateur à un magasin
*
* @param int|null $productid ID de l'utilisateur
* @param int|null $storeid ID du magasin
* @return bool Résultat de l'opération (true si success, false sinon)
*/
public function assignToStore($productid = null, $storeid = null) public function assignToStore($productid = null, $storeid = null)
{ {
// Vérifie si l'utilisateur et le magasin sont fournis
if (!is_null($productid) && !is_null($storeid)) { if (!is_null($productid) && !is_null($storeid)) {
// Mise à jour du champ store_id pour l'utilisateur spécifié
$this->db->table('products') $this->db->table('products')
->where('id', $productid) ->where('id', $productid)
->update(['store_id' => $storeid]); ->update(['store_id' => $storeid]);
@ -118,36 +125,19 @@ class Products extends Model
return true; return true;
} }
// Si $userid ou $storeid est null, l'opération échoue
return false; return false;
} }
/**
* create new product
* @param array $data
* @return bool
*/
public function create(array $data) public function create(array $data)
{ {
return $this->insert($data) ? true : false; return $this->insert($data) ? true : false;
} }
/**
* update existing product
* @param array $data
* @param int $id
* @return bool
*/
public function updateProduct(array $data, int $id) public function updateProduct(array $data, int $id)
{ {
return $this->update($id, $data) ? true : false; return $this->update($id, $data) ? true : false;
} }
/**
* remove existing product
* @param int $id
* @return bool
*/
public function remove(int $id) public function remove(int $id)
{ {
return $this->delete($id) ? true : false; return $this->delete($id) ? true : false;
@ -157,23 +147,18 @@ class Products extends Model
{ {
$db = \Config\Database::connect(); $db = \Config\Database::connect();
// Sous-requête pour obtenir les product_id dans avances actives
$subQuery = $db->table('avances') $subQuery = $db->table('avances')
->select('product_id') ->select('product_id')
->where('active', 1) ->where('active', 1)
->where('is_order', 0) ->where('is_order', 0)
->getCompiledSelect(); ->getCompiledSelect();
// Compter les produits disponibles
return $this->where('is_piece', 0) return $this->where('is_piece', 0)
->where('product_sold', 0) ->where('product_sold', 0)
->where("id NOT IN ($subQuery)", null, false) ->where("id NOT IN ($subQuery)", null, false)
->countAllResults(); ->countAllResults();
} }
/**
* count all products including sold and reserved (méthode originale si besoin)
*/
public function countAllProductsIncludingSold() public function countAllProductsIncludingSold()
{ {
return $this->countAll(); return $this->countAll();
@ -185,7 +170,6 @@ class Products extends Model
$total = 0.0; $total = 0.0;
foreach ($productIds as $id) { foreach ($productIds as $id) {
// Récupère le prix du produit courant
$row = $this->select('price') $row = $this->select('price')
->where('id', $id) ->where('id', $id)
->first(); ->first();
@ -197,7 +181,6 @@ class Products extends Model
return $total; return $total;
} catch (\Throwable $th) { } catch (\Throwable $th) {
// Loger l’erreur ici si besoin : log_message('error', $th->getMessage());
return false; return false;
} }
} }
@ -207,12 +190,45 @@ class Products extends Model
$product = $this->where('id', $id)->first(); $product = $this->where('id', $id)->first();
if ($product && isset($product['name'])) { if ($product && isset($product['name'])) {
return $product['name']; // ou un autre champ selon le vrai nom return $product['name'];
} }
return null; return null;
} }
/**
* Compter les produits par store selon le rôle de l'utilisateur
* @return int
*/
public function countProductsByUserStore()
{
$session = session();
$user = $session->get('user');
// Vérifier si l'utilisateur est admin
$isAdmin = in_array($user['group_name'], ['DAF', 'Direction']);
$db = \Config\Database::connect();
// Sous-requête pour exclure les produits en avance
$subQuery = $db->table('avances')
->select('product_id')
->where('active', 1)
->where('is_order', 0)
->getCompiledSelect();
$builder = $this->where('is_piece', 0)
->where('product_sold', 0)
->where("id NOT IN ($subQuery)", null, false);
// Si pas admin ET a un store_id valide, filtrer par son magasin
if (!$isAdmin && !empty($user['store_id']) && $user['store_id'] != 0) {
$builder->where('store_id', $user['store_id']);
} elseif (!$isAdmin) {
// Utilisateur sans store = 0 produits
return 0;
}
return $builder->countAllResults();
}
} }

873
app/Views/avances/avance.php

File diff suppressed because it is too large

269
app/Views/dashboard.php

@ -54,6 +54,10 @@
</ol> </ol>
</section> </section>
<!-- Main content --> <!-- Main content -->
<section class="content"> <section class="content">
<!-- Small boxes (Stat box) --> <!-- Small boxes (Stat box) -->
@ -709,16 +713,13 @@
<?php if ($isCaissier === true): ?> <?php if ($isCaissier === true): ?>
<!-- <div class="content-wrapper"> --> <!-- Section des totaux caisse -->
<!-- <h5>Votre statistique de vente</h5> -->
<!-- performance content wraper: <div class="content-wrapper"> -->
<div class="container-fluid row"> <div class="container-fluid row">
<!-- total en caisse --> <!-- ✅ MODIFIÉ : Utiliser total_caisse au lieu de total -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_caisse, 0, '.', ' '); ?>Ar</h2>
<p>Totale CAISSE</p> <p>Totale CAISSE</p>
</div> </div>
<div class="icon"> <div class="icon">
@ -726,12 +727,12 @@
</div> </div>
</div> </div>
</div> </div>
<!-- total mvola -->
<!-- ✅ MODIFIÉ : Utiliser total_mvola_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_mvola, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?>Ar</h2>
<p>Totale MVOLA</p> <p>Totale MVOLA</p>
</div> </div>
<div class="icon"> <div class="icon">
@ -739,25 +740,25 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Total en espece -->
<!-- ✅ MODIFIÉ : Utiliser total_espece_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_espece, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_espece_caisse, 0, '.', ' '); ?>Ar</h2>
<p>Totale en espece</p> <p>Totale en espèce</p>
</div> </div>
<div class="icon"> <div class="icon">
<i class="fa fa fa-usd"></i> <i class="fa fa-usd"></i>
</div> </div>
</div> </div>
</div> </div>
<!-- Total en virement bancaire -->
<!-- ✅ MODIFIÉ : Utiliser total_vb_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_virement_bancaire, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_vb_caisse, 0, '.', ' '); ?>Ar</h2>
<p>Totale en banque</p> <p>Totale en banque</p>
</div> </div>
<div class="icon"> <div class="icon">
@ -767,6 +768,62 @@
</div> </div>
</div> </div>
<!-- ✅ NOUVEAU : Afficher le détail Orders vs Avances -->
<div class="container-fluid row" style="margin-top: 10px;">
<div class="col-lg-6 col-xs-12">
<div class="info-box bg-aqua">
<span class="info-box-icon"><i class="fa fa-shopping-cart"></i></span>
<div class="info-box-content">
<span class="info-box-text">Total Orders (Ventes complètes)</span>
<span class="info-box-number"><?php echo number_format($total_orders_only, 0, '.', ' '); ?> Ar</span>
</div>
</div>
</div>
<div class="col-lg-6 col-xs-12">
<div class="info-box bg-yellow">
<span class="info-box-icon"><i class="fa fa-clock-o"></i></span>
<div class="info-box-content">
<span class="info-box-text">Total Avances (Paiements partiels)</span>
<span class="info-box-number"><?php echo number_format($total_avances, 0, '.', ' '); ?> Ar</span>
</div>
</div>
</div>
</div>
<!-- ✅ NOUVEAU : Détail des avances par mode de paiement -->
<div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;">
<div class="col-lg-12">
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title"><i class="fa fa-info-circle"></i> Détail des Avances par Mode de Paiement</h3>
</div>
<div class="box-body">
<div class="row">
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_mvola, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">MVOLA</span>
</div>
</div>
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_espece, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">ESPÈCE</span>
</div>
</div>
<div class="col-sm-4">
<div class="description-block">
<h5 class="description-header"><?php echo number_format($total_avances_virement, 0, '.', ' '); ?> Ar</h5>
<span class="description-text">BANQUE</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<section class="content-header"> <section class="content-header">
<h1>Rapport de Performance du Caissier</h1> <h1>Rapport de Performance du Caissier</h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -781,11 +838,9 @@
<div class="col-12"> <div class="col-12">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<!-- Product Details -->
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-12 col-lg-12"> <div class="col-md-12 col-lg-12">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;"> <div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
<div class="col-md-3"> <div class="col-md-3">
@ -798,10 +853,8 @@
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-3 d-flex align-items-end">
<br> <br>
<button id="filteredB1" class="btn btn-primary w-100">Filtrer <button id="filteredB1" class="btn btn-primary w-100">Filtrer 🔍</button>
🔍</button> <button id="ExportBTN1" class="btn btn-success w-100">Exporter 📤</button>
<button id="ExportBTN1" class="btn btn-success w-100">Exporter
📤</button>
</div> </div>
</div> </div>
<table id="caissierperf" class="table table-hover table-striped"> <table id="caissierperf" class="table table-hover table-striped">
@ -825,12 +878,10 @@
</div> </div>
</div> </div>
</section> </section>
<div style="width: 80%; margin: auto;"> <div style="width: 80%; margin: auto;">
<canvas id="salesChart"></canvas> <canvas id="salesChart"></canvas>
</div> </div>
<!-- /.content -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script> <script>
@ -910,6 +961,171 @@
<!-- </div> --> <!-- </div> -->
<?php endif; ?> <?php endif; ?>
<!-- securite -->
<!-- Dashboard pour Sécurité -->
<!-- Dashboard pour Sécurité -->
<?php if ($isSecurite === true): ?>
<!-- Styles spécifiques pour Sécurité -->
<style>
.security-dashboard {
margin: 0 !important;
padding: 0 !important;
}
.security-dashboard .content-header {
margin: 0 !important;
padding: 15px !important;
background-color: #f9f9f9;
}
.security-dashboard .content {
margin: 0 !important;
padding: 15px !important;
}
.security-dashboard .row {
margin-left: 0 !important;
margin-right: 0 !important;
}
.security-dashboard .col-lg-3,
.security-dashboard .col-md-4,
.security-dashboard .col-sm-6,
.security-dashboard .col-xs-12 {
padding-left: 0 !important;
padding-right: 15px !important;
}
.security-dashboard .small-box {
border-radius: 12px;
padding: 20px;
color: white;
position: relative;
overflow: hidden;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.15);
transition: transform 0.3s ease;
margin: 0 !important;
}
.security-dashboard .small-box:hover {
transform: translateY(-5px);
box-shadow: 0px 6px 15px rgba(0, 0, 0, 0.25);
}
.security-dashboard .small-box .inner {
position: relative;
z-index: 10;
}
.security-dashboard .small-box .inner h3 {
font-size: 38px;
font-weight: bold;
margin: 0;
}
.security-dashboard .small-box .inner p {
font-size: 16px;
margin-top: 5px;
}
.security-dashboard .small-box .icon {
position: absolute;
top: 10px;
right: 10px;
font-size: 70px;
opacity: 0.3;
z-index: 0;
}
.security-dashboard .small-box-footer {
display: block;
padding: 10px 0;
margin-top: 10px;
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
border-top: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.security-dashboard .small-box-footer:hover {
color: white;
background-color: rgba(0, 0, 0, 0.1);
text-decoration: none;
}
.security-dashboard .bg-aqua {
background: linear-gradient(135deg, #00c0ef 0%, #0099cc 100%);
}
.security-dashboard .box {
border-radius: 8px;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
margin-left: 0 !important;
margin-right: 0 !important;
}
.security-dashboard .box-header.with-border {
border-bottom: 2px solid #3c8dbc;
}
.security-dashboard .box-title {
font-weight: 600;
}
.security-dashboard .col-md-12 {
padding-left: 0 !important;
padding-right: 0 !important;
}
</style>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
<!-- small box -->
<div class="small-box bg-aqua">
<div class="inner">
<h3><?php echo $total_products ?></h3>
<p>Total Produits</p>
</div>
<div class="icon">
<i class="ion ion-bag"></i>
</div>
<a href="<?php echo base_url('products/') ?>" class="small-box-footer">
Plus d'information <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
<!-- ./col -->
</div>
<!-- /.row -->
<!-- Section informative pour la sécurité -->
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">
<i class="fa fa-shield"></i> Informations Sécurité
</h3>
</div>
<div class="box-body">
<p>
<strong>Bienvenue sur votre tableau de bord sécurité.</strong>
</p>
<p>
Vous avez accès à la consultation du nombre total de produits en stock.
Pour plus de détails, cliquez sur "Plus d'information" dans la carte ci-dessus.
</p>
</div>
</div>
</div>
</div>
</section>
<?php endif; ?>
<!-- mecanicien --> <!-- mecanicien -->
@ -972,6 +1188,7 @@
</div> </div>
</div> </div>
</section> </section>
<!-- Scripts --> <!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script> <script>

233
app/Views/login.php

@ -1,44 +1,172 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="fr">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Log in</title> <title>Connexion | Motorbike</title>
<!-- Tell the browser to be responsive to screen width --> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') ?>">
<!-- Font Awesome --> <!-- Bootstrap -->
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') ?>">
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/font-awesome/css/font-awesome.min.css') ?>"> <link rel="stylesheet" href="<?php echo base_url('assets/bower_components/font-awesome/css/font-awesome.min.css') ?>">
<link rel="stylesheet" href="<?php echo base_url('assets/dist/css/AdminLTE.min.css') ?>">
<!-- Google Font -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<!-- Ionicons --> <style>
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/Ionicons/css/ionicons.min.css') ?>"> body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #e9ecef, #dee2e6);
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
<!-- Theme style --> .login-card {
<link rel="stylesheet" href="<?php echo base_url('assets/dist/css/AdminLTE.min.css') ?>"> display: flex;
<!-- iCheck --> width: 720px;
<link rel="stylesheet" href="<?php echo base_url('assets/plugins/iCheck/square/blue.css') ?>"> max-width: 95%;
background: #fff;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
animation: fadeIn 0.8s ease;
}
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> /* Partie gauche : image moto */
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> .login-image {
<!--[if lt IE 9]> flex: 1;
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> background: url('https://lh3.googleusercontent.com/p/AF1QipN4iewRbD9iIfbsvyPTD2SGUkxyi952uG30pHD9=s1360-w1360-h1020') center/cover no-repeat;
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> /* Alternative possible :
<![endif]--> https://images.unsplash.com/photo-1520975661595-6453be3f7070?auto=format&fit=crop&w=1000&q=80
(casque de moto)
*/
position: relative;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
text-align: center;
}
<!-- Google Font --> .login-image::after {
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
}
.login-image h1 {
position: relative;
z-index: 1;
font-size: 1.8em;
font-weight: 600;
color: #ffcc00;
}
.login-image p {
position: relative;
z-index: 1;
font-size: 1em;
max-width: 250px;
margin: 10px auto 0;
line-height: 1.4;
color: #eee;
}
/* Partie droite : formulaire */
.login-form {
flex: 1;
padding: 35px 30px;
display: flex;
flex-direction: column;
justify-content: center;
}
.login-form h2 {
font-weight: 600;
margin-bottom: 25px;
color: #007bff;
text-align: center;
}
.input-group {
margin-bottom: 15px;
}
.input-group .form-control {
border-radius: 30px;
padding: 10px 20px;
box-shadow: none;
border: 1px solid #ccc;
}
.input-group-addon {
border-radius: 30px 0 0 30px;
background-color: #f8f9fa;
border: 1px solid #ccc;
border-right: none;
color: #007bff;
}
.btn-primary {
border-radius: 30px;
background-color: #007bff;
border: none;
padding: 10px 20px;
font-weight: 600;
transition: 0.3s;
}
.btn-primary:hover {
background-color: #0056b3;
}
.checkbox label {
font-weight: 400;
color: #555;
}
.alert {
border-radius: 10px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 768px) {
.login-card {
flex-direction: column;
width: 90%;
}
.login-image {
height: 160px;
}
.login-form {
padding: 25px;
}
}
</style>
</head> </head>
<body class="hold-transition login-page">
<div class="login-box"> <body>
<div class="login-logo"> <div class="login-card">
<a href=""><b>Login</b></a>
<!-- Image + message -->
<div class="login-image">
<div>
<h1>Bienvenue chez Motorbike</h1>
<p>Gérez vos avances et opérations avec passion pour la moto.</p>
</div>
</div> </div>
<!-- /.login-logo -->
<div class="login-box-body"> <!-- Formulaire -->
<p class="login-box-msg">Sign in to start your session</p> <div class="login-form">
<h2>Connexion</h2>
<?php if (session()->getFlashdata('error')): ?> <?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"> <div class="alert alert-danger">
@ -47,50 +175,27 @@
<?php endif; ?> <?php endif; ?>
<form action="<?= base_url('login') ?>" method="post"> <form action="<?= base_url('login') ?>" method="post">
<div class="form-group has-feedback"> <div class="input-group">
<input type="email" class="form-control" name="email" id="email" placeholder="Email" autocomplete="off"> <span class="input-group-addon"><i class="fa fa-envelope"></i></span>
<span class="glyphicon glyphicon-envelope form-control-feedback"></span> <input type="email" class="form-control" name="email" placeholder="Email" required>
</div>
<div class="form-group has-feedback">
<input type="password" class="form-control" name="password" id="password" placeholder="Password" autocomplete="off">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div> </div>
<div class="row">
<div class="col-xs-8"> <div class="input-group">
<div class="checkbox icheck"> <span class="input-group-addon"><i class="fa fa-lock"></i></span>
<label> <input type="password" class="form-control" name="password" placeholder="Mot de passe" required>
<input type="checkbox"> Remember Me
</label>
</div>
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">Sign In</button>
</div> </div>
<!-- /.col -->
<div class="checkbox">
<label><input type="checkbox"> Se souvenir de moi</label>
</div> </div>
</form>
<button type="submit" class="btn btn-primary btn-block">Se connecter</button>
</form>
</div> </div>
<!-- /.login-box-body -->
</div> </div>
<!-- /.login-box -->
<!-- jQuery 3 -->
<script src="<?php echo base_url('assets/bower_components/jquery/dist/jquery.min.js') ?>"></script> <script src="<?php echo base_url('assets/bower_components/jquery/dist/jquery.min.js') ?>"></script>
<!-- Bootstrap 3.3.7 -->
<script src="<?php echo base_url('assets/bower_components/bootstrap/dist/js/bootstrap.min.js') ?>"></script> <script src="<?php echo base_url('assets/bower_components/bootstrap/dist/js/bootstrap.min.js') ?>"></script>
<!-- iCheck -->
<script src="<?php echo base_url('assets/plugins/iCheck/icheck.min.js') ?>"></script>
<script>
$(function () {
$('input').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue',
increaseArea: '20%' // optional
});
});
</script>
</body> </body>
</html> </html>

236
app/Views/orders/createbyid.php

@ -1,4 +1,10 @@
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
.border-danger {
border: 2px solid #d9534f !important;
box-shadow: 0 0 5px rgba(217, 83, 79, 0.5) !important;
}
</style>
<div class="content-wrapper"> <div class="content-wrapper">
<section class="content-header"> <section class="content-header">
<h1> <h1>
@ -53,7 +59,6 @@
<?= $validation->listErrors() ?> <?= $validation->listErrors() ?>
</ul> </ul>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="box-body"> <div class="box-body">
<div class="form-group"> <div class="form-group">
@ -99,10 +104,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label" <label for="customer_cin" class="col-sm-5 control-label"
style="text-align:left;">CIN du client</label> style="text-align:left;">CIN du client</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="customer_phone" name="customer_cin" <input type="text" class="form-control" id="customer_cin" name="customer_cin"
value="<?= old('customer_cin') ?>" required placeholder="Entrer le CIN du client" value="<?= old('customer_cin') ?>" required placeholder="Entrer le CIN du client"
autocomplete="off"> autocomplete="off">
</div> </div>
@ -113,11 +118,8 @@
<thead> <thead>
<tr> <tr>
<th style="width:50%">Produit</th> <th style="width:50%">Produit</th>
<!-- <th style="width:10%">Quantité</th> -->
<th style="width:10%">Prix unitaire</th> <th style="width:10%">Prix unitaire</th>
<th style="width:20%">Montant</th> <th style="width:20%">Montant</th>
<!-- <th style="width:10%"><button type="button" id="add_row"
class="btn btn-default"><i class="fa fa-plus"></i></button></th> -->
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -134,15 +136,14 @@
<td> <td>
<input type="text" name="rate[]" id="rate_1" class="form-control" disabled <input type="text" name="rate[]" id="rate_1" class="form-control" disabled
autocomplete="off" value="<?= $pu ?>"> autocomplete="off" value="<?= $pu ?>" min="0">
<input type="hidden" name="rate_value[]" value="<?= $pu ?>" <input type="hidden" name="rate_value[]" value="<?= $pu ?>"
id="rate_value_1" class="form-control" autocomplete="off"> id="rate_value_1" class="form-control" autocomplete="off">
<input type="hidden" id="min_price_1" name="min_price[]" value=""> <input type="hidden" id="min_price_1" name="min_price[]" value="">
</td> </td>
<td> <td>
<input type="text" name="amount[]" id="amount_1" class="form-control" <input type="text" name="amount[]" id="amount_1" class="form-control"
disabled autocomplete="off"> disabled autocomplete="off" min="0">
<input type="hidden" name="amount_value[]" id="amount_value_1" <input type="hidden" name="amount_value[]" id="amount_value_1"
class="form-control" autocomplete="off"> class="form-control" autocomplete="off">
</td> </td>
@ -151,47 +152,40 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<input type="hidden" name="qty[]" value="1" id="qty_1" max="<//?= $totalqtt ?>" <input type="hidden" name="qty[]" value="1" id="qty_1" min="1"
class="form-control valueqty" required onkeyup="getTotal(1)"> class="form-control valueqty" required onkeyup="getTotal(1)">
<br /> <br /> <br /> <br />
<div class="col-md-6 col-xs-12 pull pull-right"> <div class="col-md-6 col-xs-12 pull pull-right">
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label">Montant brut</label> <label for="gross_amount" class="col-sm-5 control-label">Prix affiché</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="gross_amount" name="gross_amount" <input type="text" class="form-control" id="gross_amount" name="gross_amount"
disabled autocomplete="off"> disabled autocomplete="off" min="0">
<input type="hidden" class="form-control" id="gross_amount_value" <input type="hidden" class="form-control" id="gross_amount_value"
name="gross_amount_value" autocomplete="off"> name="gross_amount_value" autocomplete="off">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="discount" class="col-sm-5 control-label">Rabais</label> <label for="discount" class="col-sm-5 control-label">Prix demandé</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="discount" name="discount" placeholder="Discount" <div id="discount_error" class="alert alert-danger" style="display:none; padding: 8px; margin-bottom: 5px; font-size: 13px;">
onkeyup="subAmount()" autocomplete="off"> <i class="fa fa-exclamation-triangle"></i> <span id="discount_error_text"></span>
</div>
<input type="number" class="form-control numeric-input" id="discount" name="discount"
placeholder="Prix demandé" min="0" step="0.01"
onkeyup="subAmount()" oninput="validatePrixDemande(this)" autocomplete="off">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="net_amount" class="col-sm-5 control-label">Montant Net</label> <label for="net_amount" class="col-sm-5 control-label">Remise</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="hidden" class="form-control" id="net_amount_value" <input type="hidden" class="form-control" id="net_amount_value"
name="net_amount" hidden autocomplete="off"> name="net_amount" hidden autocomplete="off">
<input type="text" class="form-control" id="net_amount" name="net_amount" <input type="text" class="form-control" id="net_amount" name="net_amount"
disabled autocomplete="off"> disabled autocomplete="off" min="0">
</div>
</div> </div>
</div> </div>
<!--
<div class="form-group">
<label for="paid_status" class="col-sm-5 control-label">Mode de paiement</label>
<div class="col-sm-7">
<select type="text" class="form-control" id="payment_mode" name="payment_mode">
<option value="1">MVOLA</option>
<option value="2">Virement Bancaire</option>
<option value="3">En espece</option>
</select>
</div> </div>
</div> -->
<div class="box-footer"> <div class="box-footer">
<input type="hidden" name="service_charge_rate" <input type="hidden" name="service_charge_rate"
value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off"> value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off">
@ -208,9 +202,71 @@
</div> </div>
</section> </section>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
var base_url = "<?php echo base_url(); ?>"; var base_url = "<?php echo base_url(); ?>";
// ✅ Fonction pour valider les nombres positifs
function validatePositiveNumber(input) {
let value = parseFloat(input.value);
// Si la valeur est négative ou NaN, on la remet à 0
if (isNaN(value) || value < 0) {
input.value = '';
return false;
}
return true;
}
// ✅ Fonction pour afficher l'erreur sur le champ prix demandé
function showDiscountError(message) {
$('#discount_error_text').text(message);
$('#discount_error').slideDown(200);
$('#discount').addClass('border-danger');
}
// ✅ Fonction pour masquer l'erreur sur le champ prix demandé
function hideDiscountError() {
$('#discount_error').slideUp(200);
$('#discount').removeClass('border-danger');
}
// ✅ Fonction pour valider que le prix demandé ne dépasse pas le prix affiché
function validatePrixDemande(input) {
let prixDemande = parseFloat(input.value);
let prixAffiche = parseFloat($('#gross_amount').val()) || 0;
// Vérifier si négatif
if (isNaN(prixDemande) || prixDemande < 0) {
input.value = '';
showDiscountError('Le prix demandé ne peut pas être négatif.');
setTimeout(hideDiscountError, 3000);
return false;
}
// Vérifier si supérieur au prix affiché
if (prixDemande > prixAffiche) {
showDiscountError('Le prix demandé (' + prixDemande.toFixed(2) + ') ne peut pas être supérieur au prix affiché (' + prixAffiche.toFixed(2) + ').');
input.value = prixAffiche;
subAmount();
setTimeout(hideDiscountError, 4000);
return false;
}
// Si tout est valide, masquer l'erreur
hideDiscountError();
return true;
}
// ✅ Fonction pour empêcher la saisie de caractères négatifs
function preventNegativeInput(e) {
// Empêche la saisie du signe moins (-)
if (e.key === '-' || e.key === 'e' || e.key === 'E' || e.key === '+') {
e.preventDefault();
return false;
}
}
$(document).ready(function () { $(document).ready(function () {
getTotal(1); getTotal(1);
$(".select_group").select2(); $(".select_group").select2();
@ -218,6 +274,48 @@
$("#mainOrdersNav").addClass('active'); $("#mainOrdersNav").addClass('active');
$("#addOrderNav").addClass('active'); $("#addOrderNav").addClass('active');
// ✅ Appliquer la validation sur tous les champs numériques
$(document).on('keydown', '.numeric-input, input[type="number"]', function(e) {
preventNegativeInput(e);
});
$(document).on('input', '.numeric-input, input[type="number"]', function() {
validatePositiveNumber(this);
});
// ✅ Validation sur le champ discount
$("#discount").on('input', function() {
validatePrixDemande(this);
});
$("#discount").on('blur', function() {
validatePrixDemande(this);
checkMinimalPrice();
});
// Bloquer la soumission du formulaire
$('form').on('submit', function(e) {
// Vérifier tous les champs numériques avant soumission
let hasNegative = false;
$('.numeric-input, input[type="number"]').each(function() {
if (parseFloat($(this).val()) < 0) {
hasNegative = true;
$(this).val('');
}
});
if (hasNegative) {
alert('Les valeurs négatives ne sont pas autorisées.');
e.preventDefault();
return false;
}
if (!checkMinimalPrice()) {
e.preventDefault();
return false;
}
});
// Add new row in the table // Add new row in the table
$("#add_row").unbind('click').bind('click', function () { $("#add_row").unbind('click').bind('click', function () {
var table = $("#product_info_table"); var table = $("#product_info_table");
@ -239,8 +337,8 @@
html += '</select>' + html += '</select>' +
'</td>' + '</td>' +
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control" disabled><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' + '<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' +
'<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control" disabled><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' + '<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
'<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' + '<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
'</tr>'; '</tr>';
@ -257,24 +355,18 @@
return false; return false;
}); });
// Vérifier lors de la saisie du rabais
$("#discount").on('blur', function() {
checkMinimalPrice();
});
// Bloquer la soumission du formulaire
$('form').on('submit', function(e) {
if (!checkMinimalPrice()) {
e.preventDefault();
return false;
}
});
}); // /document }); // /document
function getTotal(row = null) { function getTotal(row = null) {
if (row) { if (row) {
var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val()); var rate = Number($("#rate_value_" + row).val());
var qty = Number($("#qty_" + row).val());
// ✅ Vérifier que les valeurs sont positives
if (rate < 0) rate = 0;
if (qty < 0) qty = 0;
var total = rate * qty;
total = total.toFixed(2); total = total.toFixed(2);
$("#amount_" + row).val(total); $("#amount_" + row).val(total);
$("#amount_value_" + row).val(total); $("#amount_value_" + row).val(total);
@ -306,15 +398,21 @@
}, },
dataType: 'json', dataType: 'json',
success: function (response) { success: function (response) {
// setting the rate value into the rate input field // ✅ S'assurer que les prix ne sont pas négatifs
$("#rate_" + row_id).val(response.prix_vente); var prixVente = parseFloat(response.prix_vente) || 0;
$("#rate_value_" + row_id).val(response.prix_vente); var prixMinimal = parseFloat(response.prix_minimal) || 0;
$("#min_price_" + row_id).val(response.prix_minimal); // ✅ Stockage du prix minimal
if (prixVente < 0) prixVente = 0;
if (prixMinimal < 0) prixMinimal = 0;
$("#rate_" + row_id).val(prixVente);
$("#rate_value_" + row_id).val(prixVente);
$("#min_price_" + row_id).val(prixMinimal);
$("#qty_" + row_id).val(1); $("#qty_" + row_id).val(1);
$("#qty_value_" + row_id).val(1); $("#qty_value_" + row_id).val(1);
var total = Number(response.prix_vente) * 1; var total = prixVente * 1;
total = total.toFixed(2); total = total.toFixed(2);
$("#amount_" + row_id).val(total); $("#amount_" + row_id).val(total);
$("#amount_value_" + row_id).val(total); $("#amount_value_" + row_id).val(total);
@ -337,7 +435,10 @@
var count = $(tr).attr('id'); var count = $(tr).attr('id');
count = count.substring(4); count = count.substring(4);
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val()); var amount = Number($("#amount_" + count).val());
// ✅ Vérifier que le montant n'est pas négatif
if (amount < 0) amount = 0;
totalSubAmount = Number(totalSubAmount) + amount;
} }
totalSubAmount = totalSubAmount.toFixed(2); totalSubAmount = totalSubAmount.toFixed(2);
@ -362,9 +463,17 @@
var totalAmount = (Number(totalSubAmount)); var totalAmount = (Number(totalSubAmount));
totalAmount = totalAmount.toFixed(2); totalAmount = totalAmount.toFixed(2);
var discount = $("#discount").val(); var discount = Number($("#discount").val()) || 0;
// ✅ S'assurer que le rabais n'est pas négatif
if (discount < 0) {
discount = 0;
$("#discount").val('');
}
if (discount) { if (discount) {
var grandTotal = Number(totalAmount) - Number(discount); var grandTotal = Number(totalAmount) - Number(discount);
// ✅ S'assurer que le total n'est pas négatif
if (grandTotal < 0) grandTotal = 0;
grandTotal = grandTotal.toFixed(2); grandTotal = grandTotal.toFixed(2);
$("#net_amount").val(grandTotal); $("#net_amount").val(grandTotal);
$("#net_amount_value").val(grandTotal); $("#net_amount_value").val(grandTotal);
@ -378,6 +487,14 @@
function checkMinimalPrice() { function checkMinimalPrice() {
var discount = Number($("#discount").val()) || 0; var discount = Number($("#discount").val()) || 0;
// ✅ Vérifier que le rabais n'est pas négatif
if (discount < 0) {
alert("Le prix demandé ne peut pas être négatif.");
$("#discount").val('');
subAmount();
return false;
}
// Si pas de rabais, pas de vérification nécessaire // Si pas de rabais, pas de vérification nécessaire
if (discount === 0) return true; if (discount === 0) return true;
@ -394,7 +511,7 @@
if (minPrice > 0 && discount < minPrice) { if (minPrice > 0 && discount < minPrice) {
error = true; error = true;
var productText = $("#product_" + rowId + " option:selected").text(); var productText = $("#product_" + rowId + " option:selected").text();
messages.push("Le rabais (" + discount + ") pour « " + productText + " » est inférieur au prix minimal (" + minPrice + ")"); messages.push("Le prix demandé (" + discount.toFixed(2) + ") pour « " + productText + " » est inférieur au prix minimal (" + minPrice.toFixed(2) + ")");
} }
} }
@ -430,9 +547,20 @@
net_amount.value = (gross_amount_value.value - remise.value); net_amount.value = (gross_amount_value.value - remise.value);
net_amount_value.value = (gross_amount_value.value - remise.value); net_amount_value.value = (gross_amount_value.value - remise.value);
remise.addEventListener('input', function () { remise.addEventListener('input', function () {
net_amount.value = (gross_amount_value.value - remise.value); // ✅ Validation du prix demandé vs prix affiché
net_amount_value.value = (gross_amount_value.value - remise.value); if (validatePrixDemande(this)) {
var discountValue = parseFloat(this.value) || 0;
var grossValue = parseFloat(gross_amount_value.value) || 0;
var netValue = grossValue - discountValue;
// ✅ S'assurer que le net amount n'est pas négatif
if (netValue < 0) netValue = 0;
net_amount.value = netValue.toFixed(2);
net_amount_value.value = netValue.toFixed(2);
subAmount(); subAmount();
}
}) })
</script> </script>

474
app/Views/orders/edit.php

@ -22,26 +22,70 @@
<?php if (session()->getFlashdata('success')): ?> <?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible" role="alert"> <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> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?php echo session()->getFlashdata('success'); ?> <?php echo session()->getFlashdata('success'); ?>
</div> </div>
<?php elseif (session()->getFlashdata('error')): ?> <?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert"> <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> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<?php echo session()->getFlashdata('error'); ?> <?php echo session()->getFlashdata('error'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($errors = session()->getFlashdata('errors')): ?>
<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>
<?php if (is_array($errors)): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<?= esc($errors) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php
// ✅ VÉRIFIER SI LA COMMANDE EST MODIFIABLE
// Seuls les statuts 1 (Validé) et 3 (Validé et Livré) sont NON modifiables
$is_editable = isset($is_editable) ? $is_editable : true;
$paid_status = $order_data['order']['paid_status'] ?? 2;
?>
<?php if (!$is_editable): ?>
<!-- ✅ ALERTE SI NON MODIFIABLE -->
<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><i class="fa fa-lock"></i> Cette commande ne peut plus être modifiée</strong><br>
Elle a été <?php echo ($paid_status == 1) ? 'validée' : 'validée et livrée'; ?>.
Vous pouvez uniquement consulter les informations.
</div>
<?php endif; ?>
<div class="box"> <div class="box">
<div class="box-header"> <div class="box-header">
<h3 class="box-title">Mise à jour de commande</h3> <h3 class="box-title">
<?php echo $is_editable ? 'Mise à jour de commande' : 'Consultation de commande'; ?>
</h3>
</div> </div>
<!-- /.box-header --> <!-- /.box-header -->
<form role="form" action="<?php base_url('orders/create') ?>" method="post" class="form-horizontal"> <form role="form"
action="<?php base_url('orders/create') ?>"
method="post"
class="form-horizontal"
<?php echo !$is_editable ? 'onsubmit="return false;"' : ''; ?>>
<div class="box-body"> <div class="box-body">
<div class="form-group"> <div class="form-group">
<label for="date" class="col-sm-12 control-label">Date: <?php echo date('Y-m-d') ?></label> <label for="date" class="col-sm-12 control-label">Date: <?php echo date('Y-m-d') ?></label>
</div> </div>
@ -54,7 +98,7 @@
<div class="form-group"> <div class="form-group">
<label for="types" class="col-sm-5 control-label" style="text-align:left;">Types</label> <label for="types" class="col-sm-5 control-label" style="text-align:left;">Types</label>
<div class="col-sm-7"> <div class="col-sm-7">
<select name="" id="typesCommande" class="form-control"> <select name="" id="typesCommande" class="form-control" <?php echo !$is_editable ? 'disabled' : ''; ?>>
<option value="1">Facture</option> <option value="1">Facture</option>
<option value="2">Bon de Livraison & Facture</option> <option value="2">Bon de Livraison & Facture</option>
<option value="3">Bon de Livraison</option> <option value="3">Bon de Livraison</option>
@ -65,33 +109,60 @@
<div class="form-group"> <div class="form-group">
<label for="customer_name" class="col-sm-5 control-label" style="text-align:left;">Nom du client</label> <label for="customer_name" class="col-sm-5 control-label" style="text-align:left;">Nom du client</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="customer_name" name="customer_name" placeholder="Enter Customer Name" value="<?php echo $order_data['order']['customer_name'] ?>" autocomplete="off" /> <input type="text"
class="form-control"
id="customer_name"
name="customer_name"
placeholder="Enter Customer Name"
value="<?php echo $order_data['order']['customer_name'] ?>"
autocomplete="off"
<?php echo !$is_editable ? 'disabled readonly' : ''; ?> />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Adresse du client</label> <label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Adresse du client</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="customer_address" name="customer_address" placeholder="Enter Customer Address" value="<?php echo $order_data['order']['customer_address'] ?>" autocomplete="off"> <input type="text"
class="form-control"
id="customer_address"
name="customer_address"
placeholder="Enter Customer Address"
value="<?php echo $order_data['order']['customer_address'] ?>"
autocomplete="off"
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Téléphone du client</label> <label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Téléphone du client</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="customer_phone" name="customer_phone" placeholder="Enter Customer Phone" value="<?php echo $order_data['order']['customer_phone'] ?>" autocomplete="off"> <input type="text"
class="form-control"
id="customer_phone"
name="customer_phone"
placeholder="Enter Customer Phone"
value="<?php echo $order_data['order']['customer_phone'] ?>"
autocomplete="off"
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">CIN du client</label> <label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">CIN du client</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="customer_cin" name="customer_cin" placeholder="Enter Customer CIN" value="<?php echo $order_data['order']['customer_cin'] ?>" autocomplete="off"> <input type="text"
class="form-control"
id="customer_cin"
name="customer_cin"
placeholder="Enter Customer CIN"
value="<?php echo $order_data['order']['customer_cin'] ?>"
autocomplete="off"
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
</div> </div>
</div> </div>
</div> </div>
<br /> <br /> <br /> <br />
<table class="table table-bordered" id="product_info_table"> <table class="table table-bordered" id="product_info_table">
<thead> <thead>
@ -100,38 +171,80 @@
<th style="width:10%">Quantité</th> <th style="width:10%">Quantité</th>
<th style="width:10%">Prix unitaire</th> <th style="width:10%">Prix unitaire</th>
<th style="width:20%">Montant</th> <th style="width:20%">Montant</th>
<th style="width:10%"><button type="button" id="add_row" class="btn btn-default"><i class="fa fa-plus"></i></button></th> <th style="width:10%">
<?php if ($is_editable): ?>
<button type="button" id="add_row" class="btn btn-default"><i class="fa fa-plus"></i></button>
<?php endif; ?>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php if (isset($order_data['order_item'])): ?> <?php if (isset($order_data['order_item'])): ?>
<?php $x = 1; ?> <?php $x = 1; ?>
<?php foreach ($order_data['order_item'] as $key => $val): ?> <?php foreach ($order_data['order_item'] as $key => $val): ?>
<?php //print_r($v);
?>
<tr id="row_<?php echo $x; ?>"> <tr id="row_<?php echo $x; ?>">
<td> <td>
<select class="form-control select_group product" data-row-id="row_<?php echo $x; ?>" id="product_<?php echo $x; ?>" name="product[]" style="width:100%;" onchange="getProductData(<?php echo $x; ?>)" required> <select class="form-control select_group product"
data-row-id="row_<?php echo $x; ?>"
id="product_<?php echo $x; ?>"
name="product[]"
style="width:100%;"
onchange="getProductData(<?php echo $x; ?>)"
<?php echo !$is_editable ? 'disabled' : 'required'; ?>>
<option value=""></option> <option value=""></option>
<?php foreach ($products as $k => $v): ?> <?php foreach ($products as $k => $v): ?>
<option value="<?php echo $v['id'] ?>" <?php if ($val['product_id'] == $v['id']) { <option value="<?php echo $v['id'] ?>" <?php if ($val['product_id'] == $v['id']) { echo "selected='selected'"; } ?>><?php echo $v['name'] ?></option>
echo "selected='selected'";
} ?>><?php echo $v['name'] ?></option>
<?php endforeach ?> <?php endforeach ?>
</select> </select>
</td> </td>
<td><input type="hidden" name="qty[]" id="qty_<//?php echo $x; ?>" class="form-control" required onkeyup="getTotal(<?php echo $x; ?>)" value="<//?php echo $val['qty'] ?>" autocomplete="off"></td>
<td> <td>
<input type="text" name="rate[]" id="rate_<?php echo $x; ?>" class="form-control" disabled value="<?php echo $val['rate'] ?>" autocomplete="off"> <input type="number"
<input type="hidden" name="rate_value[]" id="rate_value_<?php echo $x; ?>" class="form-control" value="<?php echo $val['rate'] ?>" autocomplete="off"> name="qty[]"
id="qty_<?php echo $x; ?>"
class="form-control"
onkeyup="getTotal(<?php echo $x; ?>)"
value="<?php echo $val['qty'] ?>"
autocomplete="off"
<?php echo !$is_editable ? 'disabled readonly' : 'required'; ?>>
</td> </td>
<td> <td>
<input type="text" name="amount[]" id="amount_<?php echo $x; ?>" class="form-control" disabled value="<?php echo $val['amount'] ?>" autocomplete="off"> <input type="text"
<input type="hidden" name="amount_value[]" id="amount_value_<?php echo $x; ?>" class="form-control" value="<?php echo $val['amount'] ?>" autocomplete="off"> name="rate[]"
id="rate_<?php echo $x; ?>"
class="form-control"
disabled
value="<?php echo $val['rate'] ?>"
autocomplete="off">
<input type="hidden"
name="rate_value[]"
id="rate_value_<?php echo $x; ?>"
class="form-control"
value="<?php echo $val['rate'] ?>"
autocomplete="off">
</td>
<td>
<input type="text"
name="amount[]"
id="amount_<?php echo $x; ?>"
class="form-control"
disabled
value="<?php echo $val['amount'] ?>"
autocomplete="off">
<input type="hidden"
name="amount_value[]"
id="amount_value_<?php echo $x; ?>"
class="form-control"
value="<?php echo $val['amount'] ?>"
autocomplete="off">
</td>
<td>
<?php if ($is_editable): ?>
<button type="button" class="btn btn-default" onclick="removeRow('<?php echo $x; ?>')">
<i class="fa fa-close"></i>
</button>
<?php endif; ?>
</td> </td>
<td><button type="button" class="btn btn-default" onclick="removeRow('<?php echo $x; ?>')"><i class="fa fa-close"></i></button></td>
</tr> </tr>
<?php $x++; ?> <?php $x++; ?>
<?php endforeach; ?> <?php endforeach; ?>
@ -144,63 +257,80 @@
<div class="col-md-6 col-xs-12 pull pull-right"> <div class="col-md-6 col-xs-12 pull pull-right">
<div class="form-group"> <div class="form-group">
<label for="gross_amount" class="col-sm-5 control-label">Montant brut</label> <label for="gross_amount" class="col-sm-5 control-label">Prix affiché</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="gross_amount" name="gross_amount" disabled value="<?php echo $order_data['order']['gross_amount'] ?>" autocomplete="off"> <input type="text"
<input type="hidden" class="form-control" id="gross_amount_value" name="gross_amount_value" value="<?php echo $order_data['order']['gross_amount'] ?>" autocomplete="off"> class="form-control"
id="gross_amount"
name="gross_amount"
disabled
value="<?php echo $order_data['order']['gross_amount'] ?>"
autocomplete="off">
<input type="hidden"
class="form-control"
id="gross_amount_value"
name="gross_amount_value"
value="<?php echo $order_data['order']['gross_amount'] ?>"
autocomplete="off">
</div> </div>
</div> </div>
<!-- <//?php if ($is_service_enabled == true): ?>
<div class="form-group">
<label for="service_charge" class="col-sm-5 control-label">S-Charge <//?php echo $company_data['service_charge_value'] ?> %</label>
<div class="col-sm-7">
<input type="text" class="form-control" id="service_charge" name="service_charge" disabled value="<//?php echo $order_data['order']['service_charge_rate'] ?>" autocomplete="off">
<input type="hidden" class="form-control" id="service_charge_value" name="service_charge_value" value="<//?php echo $order_data['order']['service_charge_rate'] ?>" autocomplete="off"> <!-- ✅ ALERTE VISUELLE INTÉGRÉE -->
<div id="price_alert" style="display: none; margin-bottom: 15px;">
<div class="col-sm-offset-5 col-sm-7">
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 10px 15px; border-radius: 4px; animation: slideDown 0.3s ease-out;">
<i class="fa fa-exclamation-triangle" style="margin-right: 8px;"></i>
<strong>Attention !</strong> <span id="price_alert_message"></span>
</div> </div>
</div> </div>
<//?php endif; ?> -->
<!-- <//?php if ($is_vat_enabled == true): ?>
<div class="form-group">
<label for="vat_charge" class="col-sm-5 control-label">T.V.A <//?php echo $company_data['vat_charge_value'] ?> %</label>
<div class="col-sm-7">
<input type="text" class="form-control" id="vat_charge" name="vat_charge" disabled value="<//?php echo $order_data['order']['vat_charge'] ?>" autocomplete="off">
<input type="hidden" class="form-control" id="vat_charge_value" name="vat_charge_value" value="<//?php echo $order_data['order']['vat_charge'] ?>" autocomplete="off">
</div>
</div> </div>
<//?php endif; ?> -->
<div class="form-group"> <div class="form-group">
<label for="discount" class="col-sm-5 control-label">Rabais</label> <label for="discount" class="col-sm-5 control-label">Prix demandé</label>
<div class="col-sm-7"> <div class="col-sm-7">
<?php <?php
$users = session()->get('user'); $users = session()->get('user');
if($users && $users['group_name'] == 'COMMERCIALE'): if($users && $users['group_name'] == 'COMMERCIALE' && $is_editable):
?> ?>
<input type="text" class="form-control" id="discount" name="discount" placeholder="Discount" onkeyup="subAmount()" value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off"> <input type="text"
class="form-control"
id="discount"
name="discount"
placeholder="Discount"
onkeyup="subAmount()"
value="<?php echo $order_data['order']['discount'] ?>"
autocomplete="off">
<?php else: ?> <?php else: ?>
<input type="text" class="form-control" id="discount" name="discount" readonly value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off"> <input type="text"
class="form-control"
id="discount"
name="discount"
readonly
value="<?php echo $order_data['order']['discount'] ?>"
autocomplete="off">
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="net_amount" class="col-sm-5 control-label">Montant net</label> <label for="net_amount" class="col-sm-5 control-label">Remise</label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" class="form-control" id="net_amount" name="net_amount" disabled value="<?php echo $order_data['order']['net_amount'] ?>" autocomplete="off"> <input type="text"
<input type="hidden" class="form-control" id="net_amount_value" name="net_amount_value" value="<?php echo $order_data['order']['net_amount'] ?>" autocomplete="off"> class="form-control"
id="net_amount"
name="net_amount"
disabled
value="<?php echo $order_data['order']['net_amount'] ?>"
autocomplete="off">
<input type="hidden"
class="form-control"
id="net_amount_value"
name="net_amount_value"
value="<?php echo $order_data['order']['net_amount'] ?>"
autocomplete="off">
</div> </div>
</div> </div>
<!-- <div class="form-group">
<label for="paid_status" class="col-sm-5 control-label">Mode de paiement</label>
<div class="col-sm-7">
<select class="form-control" id="payment_mode" name="payment_mode">
<option value="MVOLA" <//?= ($order_data['order']['order_payment_mode'] == "MVOLA") ? 'selected' : '' ?>>MVOLA</option>
<option value="Virement Bancaire" <//?= ($order_data['order']['order_payment_mode'] == "Virement Bancaire") ? 'selected' : '' ?>>Virement Bancaire</option>
<option value="En espèce" <//?= ($order_data['order']['order_payment_mode'] == "En espèce") ? 'selected' : '' ?>>En espèce</option>
</select>
</div>
</div> -->
<?php <?php
$users = session()->get('user'); $users = session()->get('user');
if ($users && $users['group_name'] !== 'COMMERCIALE'): if ($users && $users['group_name'] !== 'COMMERCIALE'):
@ -209,14 +339,16 @@
<div class="form-group"> <div class="form-group">
<label for="paid_status" class="col-sm-5 control-label">Tranche de paiement</label> <label for="paid_status" class="col-sm-5 control-label">Tranche de paiement</label>
<div class="col-sm-7"> <div class="col-sm-7">
<select class="form-control" id="payment_mode" name="payment_mode"> <select class="form-control"
id="payment_mode"
name="payment_mode"
<?php echo !$is_editable ? 'disabled' : ''; ?>>
<option value="1" selected>une tranche</option> <option value="1" selected>une tranche</option>
<option value="2">deux tranches</option> <option value="2">deux tranches</option>
</select> </select>
</div> </div>
</div> </div>
<!-- ✅ AJOUTEZ CE CHAMP AVANT LES TRANCHES -->
<div class="form-group" id="montant_reference" style="display: none"> <div class="form-group" id="montant_reference" style="display: none">
<label class="col-sm-5 control-label">Montant à répartir</label> <label class="col-sm-5 control-label">Montant à répartir</label>
<div class="col-sm-7"> <div class="col-sm-7">
@ -230,84 +362,57 @@
<div class="form-group" id="paid_status_1" style="display: none"> <div class="form-group" id="paid_status_1" style="display: none">
<label for="paid_status_1" class="col-sm-5 control-label">Tranche 1</label> <label for="paid_status_1" class="col-sm-5 control-label">Tranche 1</label>
<div class="col-sm-3"> <div class="col-sm-3">
<select class="form-control" id="payment_mode_1" name="order_payment_mode_1"> <select class="form-control"
id="payment_mode_1"
name="order_payment_mode_1"
<?php echo !$is_editable ? 'disabled' : ''; ?>>
<option value="MVOLA">MVOLA</option> <option value="MVOLA">MVOLA</option>
<option value="Virement Bancaire">Virement Bancaire</option> <option value="Virement Bancaire">Virement Bancaire</option>
<option value="En espèce">En espèce</option> <option value="En espèce">En espèce</option>
</select> </select>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="number" class="form-control" id="payment_amount_1" <input type="number"
name="tranche_1" placeholder="Montant" onkeyup="calculerTranche2()"> class="form-control"
id="payment_amount_1"
name="tranche_1"
placeholder="Montant"
onkeyup="calculerTranche2()"
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
</div> </div>
</div> </div>
<div class="form-group" id="paid_status_2" style="display: none"> <div class="form-group" id="paid_status_2" style="display: none">
<label for="paid_status_2" class="col-sm-5 control-label">Tranche 2 (Reste)</label> <label for="paid_status_2" class="col-sm-5 control-label">Tranche 2 (Reste)</label>
<div class="col-sm-3"> <div class="col-sm-3">
<select class="form-control" id="payment_mode_2" name="order_payment_mode_2"> <select class="form-control"
id="payment_mode_2"
name="order_payment_mode_2"
<?php echo !$is_editable ? 'disabled' : ''; ?>>
<option value="MVOLA">MVOLA</option> <option value="MVOLA">MVOLA</option>
<option value="Virement Bancaire">Virement Bancaire</option> <option value="Virement Bancaire">Virement Bancaire</option>
<option value="En espèce">En espèce</option> <option value="En espèce">En espèce</option>
</select> </select>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="number" class="form-control" id="payment_amount_2" <input type="number"
name="tranche_2" placeholder="Montant" readonly> class="form-control"
id="payment_amount_2"
name="tranche_2"
placeholder="Montant"
readonly>
</div> </div>
</div> </div>
</div> </div>
<?php endif; ?>
<script>
$(document).ready(function() {
var paymentTranche = 1;
var netAmount = parseFloat($('#net_amount_value').val()) || 0;
// Initialisation : définir la première tranche avec le montant total
$('#payment_amount_1').val(netAmount);
function addPaymentTranche(paymentTranche) {
if (parseInt(paymentTranche) === 2) {
$("#paid_status_1").show();
$("#paid_status_2").show();
// Calculer la deuxième tranche
var amount1 = parseFloat($('#payment_amount_1').val()) || 0;
var amount2 = netAmount - amount1;
$('#payment_amount_2').val(amount2);
} else {
$("#paid_status_1").show();
$("#paid_status_2").hide();
$('#payment_mode_2').val('');
}
}
// Écouter le changement de la sélection pour afficher les tranches
$("#payment_mode").on("change", function() {
addPaymentTranche($(this).val());
});
// Écouter la modification du montant de la première tranche
$('#payment_amount_1').on("input", function() {
var amount1 = parseFloat($(this).val()) || 0;
var amount2 = netAmount - amount1;
$('#payment_amount_2').val(amount2);
});
addPaymentTranche(paymentTranche);
});
</script>
<?php
$users = session()->get('user');
if ($users && $users['group_name'] !== 'COMMERCIALE'):
?>
<div class="form-group"> <div class="form-group">
<label for="paid_status" class="col-sm-5 control-label">Statut payant</label> <label for="paid_status" class="col-sm-5 control-label">Statut payant</label>
<div class="col-sm-7"> <div class="col-sm-7">
<select type="text" class="form-control" id="paid_status" name="paid_status"> <select type="text"
class="form-control"
id="paid_status"
name="paid_status"
<?php echo !$is_editable ? 'disabled' : ''; ?>>
<option value="1">Validé</option> <option value="1">Validé</option>
<option value="2">Refusé</option> <option value="2">Refusé</option>
</select> </select>
@ -320,12 +425,15 @@
<!-- /.box-body --> <!-- /.box-body -->
<div class="box-footer"> <div class="box-footer">
<input type="hidden" name="service_charge_rate" value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off"> <input type="hidden" name="service_charge_rate" value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off">
<input type="hidden" name="vat_charge_rate" value="<?php echo $company_data['vat_charge_value'] ?>" autocomplete="off"> <input type="hidden" name="vat_charge_rate" value="<?php echo $company_data['vat_charge_value'] ?>" autocomplete="off">
<a target="__blank" id="Imprimente" href="<?php echo base_url() . 'orders/printDiv/' . $order_data['order']['id'] ?>" class="btn btn-default">Imprimer</a> <a target="__blank" id="Imprimente" href="<?php echo base_url() . 'orders/printDiv/' . $order_data['order']['id'] ?>" class="btn btn-default">Imprimer</a>
<?php if ($is_editable): ?>
<button type="submit" class="btn btn-primary">Enregistrer</button> <button type="submit" class="btn btn-primary">Enregistrer</button>
<?php endif; ?>
<a href="<?php echo base_url('orders/') ?>" class="btn btn-warning">Retour</a> <a href="<?php echo base_url('orders/') ?>" class="btn btn-warning">Retour</a>
</div> </div>
</form> </form>
@ -336,16 +444,42 @@
<!-- col-md-12 --> <!-- col-md-12 -->
</div> </div>
<!-- /.row --> <!-- /.row -->
</section> </section>
<!-- /.content --> <!-- /.content -->
</div> </div>
<!-- /.content-wrapper --> <!-- /.content-wrapper -->
<style>
/* ✅ Animation pour l'alerte */
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ✅ Style pour l'input en erreur */
.input-error {
border: 2px solid #dc3545 !important;
box-shadow: 0 0 8px rgba(220, 53, 69, 0.5) !important;
animation: pulse 0.5s ease-in-out;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 8px rgba(220, 53, 69, 0.5); }
50% { box-shadow: 0 0 15px rgba(220, 53, 69, 0.8); }
}
</style>
<script type="text/javascript"> <script type="text/javascript">
var base_url = "<?php echo base_url(); ?>"; var base_url = "<?php echo base_url(); ?>";
var idData = "<?php echo $order_data['order']['id']; ?>"; var idData = "<?php echo $order_data['order']['id']; ?>";
var is_editable = <?php echo $is_editable ? 'true' : 'false'; ?>;
let Imprimente = document.getElementById('Imprimente'); let Imprimente = document.getElementById('Imprimente');
let typesCommande = document.getElementById('typesCommande'); let typesCommande = document.getElementById('typesCommande');
@ -362,14 +496,76 @@
} }
}); });
// ============================================
// 🔹 FONCTION D'ALERTE VISUELLE
// ============================================
function showPriceAlert(message) {
const alertBox = $('#price_alert');
const discountInput = $('#discount');
$('#price_alert_message').text(message);
alertBox.slideDown(300);
discountInput.addClass('input-error');
setTimeout(function() {
alertBox.slideUp(300);
discountInput.removeClass('input-error');
}, 3500);
}
function hidePriceAlert() {
$('#price_alert').slideUp(300);
$('#discount').removeClass('input-error');
}
// ============================================
// 🔹 VALIDATION DU PRIX DEMANDÉ
// ============================================
function validateDiscount() {
if (!is_editable) return true; // ✅ Pas de validation si non éditable
var discount = parseFloat($('#discount').val());
var grossAmount = parseFloat($('#gross_amount_value').val()) || 0;
if (discount < 0) {
showPriceAlert("Le prix demandé ne peut pas être négatif !");
$('#discount').val('');
return false;
}
if (discount > grossAmount) {
showPriceAlert("Le prix demandé ne peut pas dépasser le prix affiché (" + grossAmount.toFixed(2) + ") !");
$('#discount').val(grossAmount.toFixed(2));
subAmount();
return false;
}
hidePriceAlert();
return true;
}
$(document).ready(function() { $(document).ready(function() {
$(".select_group").select2(); $(".select_group").select2();
$("#mainOrdersNav").addClass('active'); $("#mainOrdersNav").addClass('active');
$("#manageOrdersNav").addClass('active'); $("#manageOrdersNav").addClass('active');
// ✅ Désactiver toutes les interactions si non éditable
if (!is_editable) {
$('input, select, textarea').prop('disabled', true);
$('#add_row').hide();
$('.btn-default[onclick^="removeRow"]').hide();
}
// ✅ Validation en temps réel du prix demandé
if (is_editable) {
$('#discount').on('keyup change', function() {
validateDiscount();
});
}
// ============================================ // ============================================
// 🔹 AJOUT : GESTION DES TRANCHES DE PAIEMENT // 🔹 GESTION DES TRANCHES DE PAIEMENT
// ============================================ // ============================================
var paymentTranche = 1; var paymentTranche = 1;
var netAmount = parseFloat($('#net_amount_value').val()) || 0; var netAmount = parseFloat($('#net_amount_value').val()) || 0;
@ -390,17 +586,19 @@
} }
} }
// Sélection du mode de paiement
$("#payment_mode").on("change", function() { $("#payment_mode").on("change", function() {
if (is_editable) {
addPaymentTranche($(this).val()); addPaymentTranche($(this).val());
updateMontantTranches(); // ✅ pour mise à jour automatique updateMontantTranches();
}
}); });
// Modification du montant de la première tranche
$('#payment_amount_1').on("input", function() { $('#payment_amount_1').on("input", function() {
if (is_editable) {
var amount1 = parseFloat($(this).val()) || 0; var amount1 = parseFloat($(this).val()) || 0;
var amount2 = netAmount - amount1; var amount2 = netAmount - amount1;
$('#payment_amount_2').val(amount2); $('#payment_amount_2').val(amount2);
}
}); });
addPaymentTranche(paymentTranche); addPaymentTranche(paymentTranche);
@ -408,6 +606,7 @@
// ============================================ // ============================================
// 🔹 TABLEAU DE PRODUITS // 🔹 TABLEAU DE PRODUITS
// ============================================ // ============================================
if (is_editable) {
$("#add_row").unbind('click').bind('click', function() { $("#add_row").unbind('click').bind('click', function() {
var table = $("#product_info_table"); var table = $("#product_info_table");
var count_table_tbody_tr = $("#product_info_table tbody tr").length; var count_table_tbody_tr = $("#product_info_table tbody tr").length;
@ -446,13 +645,15 @@
return false; return false;
}); });
}
}); // /document.ready }); // /document.ready
// ============================================ // ============================================
// 🔹 CALCUL DU TOTAL // 🔹 CALCUL DU TOTAL
// ============================================ // ============================================
function getTotal(row = null) { function getTotal(row = null) {
if (!is_editable) return; // ✅ Bloquer si non éditable
if (row) { if (row) {
var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val()); var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val());
total = total.toFixed(2); total = total.toFixed(2);
@ -466,6 +667,8 @@
// 🔹 OBTENIR LES DONNÉES PRODUIT // 🔹 OBTENIR LES DONNÉES PRODUIT
function getProductData(row_id) { function getProductData(row_id) {
if (!is_editable) return; // ✅ Bloquer si non éditable
var product_id = $("#product_" + row_id).val(); var product_id = $("#product_" + row_id).val();
if (product_id == "") { if (product_id == "") {
$("#rate_" + row_id).val(""); $("#rate_" + row_id).val("");
@ -498,6 +701,8 @@
// 🔹 CALCUL DU MONTANT TOTAL (AVEC TVA, REMISE, ETC.) // 🔹 CALCUL DU MONTANT TOTAL (AVEC TVA, REMISE, ETC.)
// ============================================ // ============================================
function subAmount() { function subAmount() {
if (!is_editable) return; // ✅ Bloquer si non éditable
var service_charge = <?php echo ($company_data['service_charge_value'] > 0) ? $company_data['service_charge_value'] : 0; ?>; var service_charge = <?php echo ($company_data['service_charge_value'] > 0) ? $company_data['service_charge_value'] : 0; ?>;
var vat_charge = <?php echo ($company_data['vat_charge_value'] > 0) ? $company_data['vat_charge_value'] : 0; ?>; var vat_charge = <?php echo ($company_data['vat_charge_value'] > 0) ? $company_data['vat_charge_value'] : 0; ?>;
@ -529,10 +734,12 @@
var discount = $("#discount").val(); var discount = $("#discount").val();
if (discount) { if (discount) {
if (validateDiscount()) {
var grandTotal = Number(totalAmount) - Number(discount); var grandTotal = Number(totalAmount) - Number(discount);
grandTotal = grandTotal.toFixed(2); grandTotal = grandTotal.toFixed(2);
$("#net_amount").val(grandTotal); $("#net_amount").val(grandTotal);
$("#net_amount_value").val(grandTotal); $("#net_amount_value").val(grandTotal);
}
} else { } else {
$("#net_amount").val(totalAmount); $("#net_amount").val(totalAmount);
$("#net_amount_value").val(totalAmount); $("#net_amount_value").val(totalAmount);
@ -546,7 +753,6 @@
$("#remaining_value").val(remaning.toFixed(2)); $("#remaining_value").val(remaning.toFixed(2));
} }
// ✅ Mise à jour automatique des tranches à chaque recalcul
updateMontantTranches(); updateMontantTranches();
} }
@ -554,6 +760,8 @@
// 🔹 AUTRES FONCTIONS // 🔹 AUTRES FONCTIONS
// ============================================ // ============================================
function paidAmount() { function paidAmount() {
if (!is_editable) return;
var grandTotal = $("#net_amount_value").val(); var grandTotal = $("#net_amount_value").val();
if (grandTotal) { if (grandTotal) {
var dueAmount = Number($("#net_amount_value").val()) - Number($("#paid_amount").val()); var dueAmount = Number($("#net_amount_value").val()) - Number($("#paid_amount").val());
@ -564,12 +772,14 @@
} }
function removeRow(tr_id) { function removeRow(tr_id) {
if (!is_editable) return; // ✅ Bloquer si non éditable
$("#product_info_table tbody tr#row_" + tr_id).remove(); $("#product_info_table tbody tr#row_" + tr_id).remove();
subAmount(); subAmount();
} }
// ============================================ // ============================================
// 🔹 GESTION MONTANT DE TRANCHES (FONCTIONS NOUVELLES) // 🔹 GESTION MONTANT DE TRANCHES
// ============================================ // ============================================
function getMontantPourTranches() { function getMontantPourTranches() {
var discount = parseFloat($("#discount").val()) || 0; var discount = parseFloat($("#discount").val()) || 0;
@ -578,6 +788,8 @@
} }
function updateMontantTranches() { function updateMontantTranches() {
if (!is_editable) return;
var montant = getMontantPourTranches(); var montant = getMontantPourTranches();
var discount = parseFloat($("#discount").val()) || 0; var discount = parseFloat($("#discount").val()) || 0;
@ -596,6 +808,8 @@
} }
function calculerTranche2() { function calculerTranche2() {
if (!is_editable) return;
var montantTotal = getMontantPourTranches(); var montantTotal = getMontantPourTranches();
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0; var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
var tranche2 = montantTotal - tranche1; var tranche2 = montantTotal - tranche1;
@ -603,14 +817,16 @@
$("#payment_amount_2").val(tranche2.toFixed(2)); $("#payment_amount_2").val(tranche2.toFixed(2));
} }
if (is_editable) {
$("#discount").on('keyup', function() { $("#discount").on('keyup', function() {
updateMontantTranches(); updateMontantTranches();
}); });
}
const net_amount_value = document.getElementById('net_amount_value'); const net_amount_value = document.getElementById('net_amount_value');
const net_amount = document.getElementById('net_amount'); const net_amount = document.getElementById('net_amount');
const payment_amount_1 = document.getElementById('payment_amount_1'); const payment_amount_1 = document.getElementById('payment_amount_1');
if (payment_amount_1 && net_amount) {
payment_amount_1.value = net_amount.value; payment_amount_1.value = net_amount.value;
}
</script> </script>

2
app/Views/products/create.php

@ -395,4 +395,6 @@ document.querySelector('form').addEventListener('submit', function(e) {
} }
}); });
</script> </script>

5
app/Views/products/index.php

@ -160,9 +160,8 @@
<th>Prix</th> <th>Prix</th>
<th>Magasin</th> <th>Magasin</th>
<th>Disponibilité</th> <th>Disponibilité</th>
<?php if (in_array('updateProduct', $user_permission) || in_array('deleteProduct', $user_permission)): ?> <!-- ✅ MODIFICATION PRINCIPALE : Toujours afficher la colonne Action -->
<th>Action</th> <th>Action</th>
<?php endif; ?>
</tr> </tr>
</thead> </thead>
</table> </table>
@ -336,7 +335,7 @@
}, },
{ data: 4 }, // Magasin { data: 4 }, // Magasin
{ data: 5 }, // Disponibilité { data: 5 }, // Disponibilité
{ data: 6 } // Actions { data: 6 } // Actions - ✅ Toujours présent maintenant
], ],
'columnDefs': [{ 'columnDefs': [{
targets: 3, targets: 3,

1
app/Views/templates/header.php

@ -905,6 +905,7 @@
} }
} }

40
app/Views/templates/header_menu.php

@ -19,7 +19,12 @@
<span id="notificationCount" class="badge badge-warning navbar-badge"></span> <span id="notificationCount" class="badge badge-warning navbar-badge"></span>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right" <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"
style="width: 400px; padding: 5%; max-height: 500px; overflow: auto; margin-right: 5px;"> style="width: 400px; padding: 5%; max-height: 500px; overflow: auto; margin-right: 5px;">
<span class="dropdown-header" id="notificationHeader">0 Notifications</span> <div style="display: flex; justify-content: space-between; align-items: center; padding: 0 10px;">
<span class="dropdown-header" id="notificationHeader" style="padding: 0;">0 Notifications</span>
<button id="markAllAsReadBtn" class="btn btn-sm btn-primary" style="font-size: 12px; padding: 4px 10px;">
<i class="fa fa-check"></i> Marquer tout comme lu
</button>
</div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<div id="notificationList"></div> <div id="notificationList"></div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
@ -41,6 +46,15 @@
.icon-unread { .icon-unread {
color: #007bff; /* bleu */ color: #007bff; /* bleu */
} }
/* Style du bouton */
#markAllAsReadBtn {
white-space: nowrap;
}
#markAllAsReadBtn:hover {
background-color: #0056b3;
}
</style> </style>
<script> <script>
@ -86,8 +100,10 @@ function fetchNotifications() {
// Badge pour non lues // Badge pour non lues
if (notificationCount > 0) { if (notificationCount > 0) {
$('#notificationCount').text(notificationCount).show(); $('#notificationCount').text(notificationCount).show();
$('#markAllAsReadBtn').show();
} else { } else {
$('#notificationCount').hide(); $('#notificationCount').hide();
$('#markAllAsReadBtn').hide();
} }
// Ajouter l'événement clic pour marquer comme lu // Ajouter l'événement clic pour marquer comme lu
@ -115,6 +131,28 @@ function fetchNotifications() {
}); });
} }
// Fonction pour marquer toutes les notifications comme lues
function markAllAsRead() {
fetch("<?= base_url('notifications/markAllAsRead') ?>", {
method: "POST",
headers: { "Content-Type": "application/json" }
})
.then(response => response.json())
.then(data => {
console.log("All notifications marked as read:", data);
// Rafraîchir les notifications immédiatement
fetchNotifications();
})
.catch(error => console.error("Error:", error));
}
// Attacher l'événement au bouton
$(document).on('click', '#markAllAsReadBtn', function(e) {
e.preventDefault();
e.stopPropagation();
markAllAsRead();
});
// Rafraîchir toutes les 10 secondes // Rafraîchir toutes les 10 secondes
setInterval(fetchNotifications, 10000); setInterval(fetchNotifications, 10000);
// Premier chargement // Premier chargement

Loading…
Cancel
Save