From 778e8332eea5cbf6c93a18ae674306ba0fa9ddb8 Mon Sep 17 00:00:00 2001 From: Sarobidy22 Date: Sat, 15 Nov 2025 02:12:58 +0300 Subject: [PATCH] 15112025 --- app/Config/Routes.php | 29 +- app/Controllers/AvanceController.php | 106 ++++-- app/Controllers/OrderController.php | 486 +++++++++++++++++++-------- app/Models/Avance.php | 283 ++++++++-------- app/Models/Orders.php | 25 +- app/Models/Products.php | 121 ++++--- app/Models/Remise.php | 27 ++ app/Views/orders/edit.php | 153 ++++++--- app/Views/orders/index.php | 203 ++++++----- 9 files changed, 925 insertions(+), 508 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index cb3cd98b..064f6389 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -174,11 +174,11 @@ $routes->group('', ['filter' => 'auth'], function ($routes) { $routes->get('update/(:num)', [ProductCOntroller::class, 'update']); $routes->post('update/(:num)', [ProductCOntroller::class, 'update']); $routes->post('remove', [ProductCOntroller::class, 'remove']); - // $routes->get('generateqrcode/(:num)', [QrCodeController::class, 'generate']); $routes->post('assign_store', [ProductCOntroller::class, 'assign_store']); $routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']); - + $routes->post('checkProductAvailability', [ProductCOntroller::class, 'checkProductAvailability']); }); + /** * route for the orders @@ -296,10 +296,12 @@ $routes->group('/sortieCaisse', function ($routes) { }); // avance +// ✅ DANS app/Config/Routes.php + $routes->group('/avances', function ($routes) { $routes->get('/', [AvanceController::class, 'index']); - // ✅ Routes pour récupérer les données (GET) + // Routes pour récupérer les données (GET) $routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']); $routes->get('fetchAvanceBecameOrder', [AvanceController::class, 'fetchAvanceBecameOrder']); $routes->get('fetchExpiredAvance', [AvanceController::class, 'fetchExpiredAvance']); @@ -310,22 +312,27 @@ $routes->group('/avances', function ($routes) { $routes->get('getFullInvoiceForPrint/(:num)', [AvanceController::class, 'getFullInvoiceForPrint/$1']); $routes->get('printInvoice/(:num)', [AvanceController::class, 'printInvoice/$1']); - // ✅ Routes POST pour modifications + // Routes POST pour modifications $routes->post('createAvance', [AvanceController::class, 'createAvance']); $routes->post('updateAvance', [AvanceController::class, 'updateAvance']); $routes->post('deleteAvance', [AvanceController::class, 'removeAvance']); $routes->post('notifyPrintInvoice', [AvanceController::class, 'notifyPrintInvoice']); - - // ✅ AJOUTER CETTE ROUTE MANQUANTE $routes->post('processExpiredAvances', [AvanceController::class, 'processExpiredAvances']); - // ✅ Route CRON (optionnel) + // ✅ CORRECTION : Routes pour paiement et conversion + $routes->post('payAvance', [AvanceController::class, 'payAvance']); + + // ✅ AJOUT : Routes GET ET POST pour la conversion manuelle + $routes->get('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']); + $routes->post('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']); + + // Route pour forcer la conversion d'une avance spécifique + $routes->get('forceConvertToOrder/(:num)', [AvanceController::class, 'forceConvertToOrder/$1']); + + // 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 $routes->group('historique', ['filter' => 'auth'], static function ($routes) { $routes->get('/', 'HistoriqueController::index'); diff --git a/app/Controllers/AvanceController.php b/app/Controllers/AvanceController.php index f09b1181..7579badd 100644 --- a/app/Controllers/AvanceController.php +++ b/app/Controllers/AvanceController.php @@ -2114,6 +2114,13 @@ public function payAvance() $avance_id = $this->request->getPost('avance_id'); $montant_paye = (float)$this->request->getPost('montant_paye'); + if (!$avance_id || $montant_paye <= 0) { + return $this->response->setJSON([ + 'success' => false, + 'message' => 'Données invalides' + ]); + } + $avanceModel = new \App\Models\Avance(); $avance = $avanceModel->find($avance_id); @@ -2124,52 +2131,62 @@ public function payAvance() ]); } - // Calcul du nouveau montant dû + // ✅ Vérifier si déjà convertie + if ($avance['is_order'] == 1) { + return $this->response->setJSON([ + 'success' => false, + 'message' => '⚠️ Cette avance a déjà été convertie en commande' + ]); + } + + // ✅ Calcul nouveau montant dû $amount_due = max(0, (float)$avance['amount_due'] - $montant_paye); - // ✅ Mise à jour de l'avance + // ✅ Mise à jour avance $avanceModel->update($avance_id, [ 'avance_amount' => (float)$avance['avance_amount'] + $montant_paye, 'amount_due' => $amount_due, ]); - // ✅ NOUVEAU : Conversion automatique UNIQUEMENT pour avances TERRE + log_message('info', "💰 Paiement {$montant_paye} Ar sur avance {$avance_id} (Type: {$avance['type_avance']})"); + + // ✅ CONVERSION si paiement complet if ($amount_due <= 0) { if ($avance['type_avance'] === 'terre') { - // ✅ Avance TERRE complète → Conversion en commande - log_message('info', "💰 Avance TERRE {$avance_id} complétée ! Conversion en commande..."); + log_message('info', "🔄 Avance TERRE {$avance_id} complétée → Conversion en commande..."); $order_id = $avanceModel->convertToOrder($avance_id); if ($order_id) { + log_message('info', "✅ Conversion réussie → Commande {$order_id}"); + return $this->response->setJSON([ 'success' => true, - 'message' => '✅ Paiement effectué ! L\'avance TERRE a été convertie en commande.', + 'message' => '✅ Paiement effectué ! L\'avance a été convertie en commande.', 'converted' => true, 'type' => 'terre', 'order_id' => $order_id, 'redirect_url' => site_url('orders/update/' . $order_id) ]); } else { - log_message('error', "Échec conversion avance TERRE {$avance_id} en commande"); + log_message('error', "❌ Échec conversion avance {$avance_id}"); + return $this->response->setJSON([ - 'success' => true, - 'message' => '⚠️ Paiement effectué mais erreur lors de la création de la commande.', - 'converted' => false + 'success' => false, + 'message' => '⚠️ Paiement enregistré mais erreur lors de la conversion. Contactez l\'administrateur.' ]); } } else { - // ✅ Avance MER complète → Reste dans la liste - log_message('info', "💰 Avance MER {$avance_id} complétée ! Elle reste dans la liste des avances."); + // ✅ Avance MER complète + log_message('info', "✅ Avance MER {$avance_id} complétée (pas de conversion)"); return $this->response->setJSON([ 'success' => true, 'message' => '✅ Paiement effectué ! L\'avance MER est maintenant complète.', 'converted' => false, - 'type' => 'mere', - 'status' => 'completed' + 'type' => 'mere' ]); } } @@ -2177,8 +2194,9 @@ public function payAvance() // ✅ Paiement partiel return $this->response->setJSON([ 'success' => true, - 'message' => '✅ Paiement partiel enregistré avec succès', + 'message' => '✅ Paiement partiel enregistré', 'amount_due_remaining' => $amount_due, + 'amount_due_formatted' => number_format($amount_due, 0, ',', ' ') . ' Ar', 'type' => $avance['type_avance'] ]); } @@ -2187,16 +2205,33 @@ public function payAvance() */ public function forceConvertToOrder($avance_id) { - $this->verifyRole('updateAvance'); // Adapter selon vos permissions + $this->verifyRole('updateAvance'); $avanceModel = new \App\Models\Avance(); + $avance = $avanceModel->find($avance_id); + + if (!$avance) { + session()->setFlashdata('errors', 'Avance introuvable'); + return redirect()->back(); + } + + if ($avance['type_avance'] !== 'terre') { + session()->setFlashdata('errors', 'Seules les avances TERRE peuvent être converties'); + return redirect()->back(); + } + + if ($avance['amount_due'] > 0) { + session()->setFlashdata('errors', 'L\'avance doit être complètement payée avant conversion'); + return redirect()->back(); + } + $order_id = $avanceModel->convertToOrder($avance_id); if ($order_id) { session()->setFlashdata('success', 'Avance convertie en commande avec succès !'); return redirect()->to('orders/update/' . $order_id); } else { - session()->setFlashdata('errors', 'Erreur lors de la conversion de l\'avance.'); + session()->setFlashdata('errors', 'Erreur lors de la conversion'); return redirect()->back(); } } @@ -2208,16 +2243,33 @@ public function forceConvertToOrder($avance_id) public function checkAndConvertCompleted() { try { - $Avance = new Avance(); + log_message('info', "=== DÉBUT checkAndConvertCompleted (manuel) ==="); - // ✅ Récupérer uniquement les avances TERRE complètes non converties - $completedTerreAvances = $Avance->getCompletedNotConverted(); + $Avance = new \App\Models\Avance(); + + // ✅ Récupérer toutes les avances TERRE complètes non converties + $completedTerreAvances = $Avance + ->where('type_avance', 'terre') + ->where('amount_due <=', 0) + ->where('is_order', 0) + ->where('active', 1) + ->findAll(); + + if (empty($completedTerreAvances)) { + return $this->response->setJSON([ + 'success' => true, + 'message' => 'Aucune avance complète à convertir', + 'converted' => 0 + ]); + } $convertedCount = 0; $errorCount = 0; $details = []; foreach ($completedTerreAvances as $avance) { + log_message('info', "🔍 Traitement avance {$avance['avance_id']}..."); + $order_id = $Avance->convertToOrder($avance['avance_id']); if ($order_id) { @@ -2225,32 +2277,35 @@ public function checkAndConvertCompleted() $details[] = [ 'avance_id' => $avance['avance_id'], 'customer' => $avance['customer_name'], - 'type' => 'terre', 'order_id' => $order_id, 'status' => 'success' ]; + log_message('info', "✅ Avance {$avance['avance_id']} → Commande {$order_id}"); } else { $errorCount++; $details[] = [ 'avance_id' => $avance['avance_id'], 'customer' => $avance['customer_name'], - 'type' => 'terre', - 'status' => 'error' + 'status' => 'error', + 'reason' => 'Voir logs pour détails' ]; + log_message('error', "❌ Échec conversion avance {$avance['avance_id']}"); } } + log_message('info', "=== FIN checkAndConvertCompleted - Convertis: {$convertedCount}, Erreurs: {$errorCount} ==="); + return $this->response->setJSON([ 'success' => true, 'message' => 'Vérification terminée', 'converted' => $convertedCount, 'errors' => $errorCount, - 'note' => 'Seules les avances TERRE sont converties. Les avances MER restent dans la liste.', + 'total_checked' => count($completedTerreAvances), 'details' => $details ]); } catch (\Exception $e) { - log_message('error', "Erreur checkAndConvertCompleted: " . $e->getMessage()); + log_message('error', "❌ Erreur checkAndConvertCompleted: " . $e->getMessage()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur: ' . $e->getMessage() @@ -2258,4 +2313,5 @@ public function checkAndConvertCompleted() } } + } \ No newline at end of file diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php index 8015e0d1..bd10ea07 100644 --- a/app/Controllers/OrderController.php +++ b/app/Controllers/OrderController.php @@ -41,26 +41,46 @@ class OrderController extends AdminController * @param int $store_id * @return string */ - private function generateBillNo(int $store_id): string + private function generateSimpleSequentialBillNo(int $store_id): string { - // Mapping des préfixes par magasin $storePrefixes = [ - 1 => 'ANTS', // ANTSAKAVIRO - 2 => 'BESA', // BESARETY - 3 => 'BYPA', // BYPASS - 4 => 'TOAM', // TOAMASINA + 1 => 'ANTS', + 2 => 'BESA', + 3 => 'BYPA', + 4 => 'TOAM', ]; - - // Récupérer le préfixe du magasin, ou utiliser un préfixe par défaut - $prefix = $storePrefixes[$store_id] ?? 'BILPR'; - - // Générer un identifiant unique - $uniqueId = strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 6)); - - // Retourner le numéro de facture formaté - return $prefix . '-' . $uniqueId; + + $prefix = $storePrefixes[$store_id] ?? 'STORE'; + + $db = \Config\Database::connect(); + + $lastBill = $db->table('orders') + ->select('bill_no') + ->like('bill_no', $prefix . '-', 'after') + ->orderBy('id', 'DESC') + ->limit(1) + ->get() + ->getRowArray(); + + if ($lastBill && !empty($lastBill['bill_no'])) { + // Extraire le numéro (ex: "BESA-001" -> 1) + preg_match('/-(\d+)$/', $lastBill['bill_no'], $matches); + + if (isset($matches[1])) { + $lastNumber = (int)$matches[1]; + $newNumber = $lastNumber + 1; + } else { + $newNumber = 1; + } + } else { + $newNumber = 1; + } + + // Formater avec zéros (ex: 001, 002, 010, 100) + return $prefix . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT); } + public function fetchOrdersData() { helper(['url', 'form']); @@ -75,53 +95,102 @@ class OrderController extends AdminController // POUR CAISSIÈRE // ======================================== if ($users['group_name'] == "Caissière") { + $Remise = new Remise(); + foreach ($data as $key => $value) { $date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); - + $buttons = ''; - - // Bouton imprimer (sauf pour SECURITE) - if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") { - $buttons .= ''; - } - - // Bouton voir - if (in_array('viewOrder', $this->permission)) { - $buttons .= ' - - - '; - } - - // ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente) - if (in_array('updateOrder', $this->permission) - && $users["store_id"] == $value['store_id'] - && in_array($value['paid_status'], [0, 2])) { - $buttons .= ' '; + $discount = (float)$value['discount']; + + // ✅ VÉRIFICATION : Si la commande est refusée (paid_status = 0), aucun bouton + if ($value['paid_status'] == 0) { + $buttons = ' Accès bloqué'; + } else { + // ✅ Bouton imprimer + if (in_array('viewOrder', $this->permission)) { + // CAS 1 : Commande payée (1) ou livrée (3) → Toujours afficher imprimer + if (in_array($value['paid_status'], [1, 3])) { + $buttons .= ''; + } + // CAS 2 : Commande en attente (2) SANS remise → Afficher imprimer + elseif ($value['paid_status'] == 2 && $discount == 0) { + $buttons .= ''; + } + // CAS 3 : Commande en attente (2) AVEC remise validée → Afficher imprimer + elseif ($value['paid_status'] == 2 && $Remise->hasRemiseValidatedForOrder($value['id'])) { + $buttons .= ''; + } + // CAS 4 : Commande en attente (2) AVEC remise en attente → Indicateur + elseif ($value['paid_status'] == 2 && $Remise->hasRemisePendingForOrder($value['id'])) { + $buttons .= ''; + } + } + + // ✅ Bouton voir + if (in_array('viewOrder', $this->permission)) { + // Afficher pour toutes les commandes sauf celles refusées + // Et pour les commandes en attente : seulement si pas de remise OU remise validée + if ($value['paid_status'] == 2) { + // En attente : vérifier la remise + if ($discount == 0 || $Remise->hasRemiseValidatedForOrder($value['id'])) { + $buttons .= ' + + + '; + } + } else { + // Payé ou Livré : toujours afficher + $buttons .= ' + + + '; + } + } + + // ✅ Bouton modifier (seulement si paid_status = 2) + if (in_array('updateOrder', $this->permission) + && $users["store_id"] == $value['store_id'] + && $value['paid_status'] == 2) { + + // CAS 1 : Pas de remise → Afficher le bouton modifier + if ($discount == 0) { + $buttons .= ' '; + } + // CAS 2 : Remise validée → Afficher le bouton modifier + elseif ($Remise->hasRemiseValidatedForOrder($value['id'])) { + $buttons .= ' '; + } + } } - + // Statut de paiement if ($value['paid_status'] == 1) { - $paid_status = 'payé'; + $paid_status = 'Payé'; } elseif ($value['paid_status'] == 2) { $paid_status = 'En Attente'; } elseif ($value['paid_status'] == 3) { - $paid_status = 'payé et Livré'; + $paid_status = 'Payé et Livré'; } else { $paid_status = 'Refusé'; } - + // Calcul délai $date1 = new DateTime($date_time); $date2 = new DateTime(); $interval = $date1->diff($date2); $daysPassed = $interval->days; - + $statuDate = ''; if ($value['paid_status'] == 2) { if ($daysPassed < 8) { @@ -132,7 +201,7 @@ class OrderController extends AdminController $statuDate = ' depuis ' . $daysPassed . ' Jours'; } } - + $result['data'][$key] = [ $value['product_names'], $value['user_name'], @@ -145,7 +214,7 @@ class OrderController extends AdminController } return $this->response->setJSON($result); } - + // ======================================== // POUR DIRECTION OU DAF // ======================================== @@ -213,7 +282,7 @@ class OrderController extends AdminController } // ======================================== - // POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, etc.) + // POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, Cheffe d'Agence) // ======================================== else { foreach ($data as $key => $value) { @@ -281,20 +350,58 @@ class OrderController extends AdminController } } - $result['data'][$key] = [ - $value['product_names'], - $value['user_name'], - $date_time . "
" . $statuDate, - number_format((int) $value['discount'], 0, ',', ' '), - number_format((int) $value['gross_amount'], 0, ',', ' '), - $paid_status, - $buttons - ]; + // ✅ CONDITION SPÉCIALE POUR SECURITE : remplacer Prix demandé et Prix de vente par Marque et Désignation + if ($users['group_name'] == "SECURITE") { + // Récupérer les infos produit + $OrderItems = new OrderItems(); + $Products = new Products(); + $Brands = new Brands(); + + $order_items = $OrderItems->getOrdersItemData($value['id']); + + $marque = 'N/A'; + $numero_serie = 'N/A'; + + if (!empty($order_items[0])) { + $product = $Products->getProductData($order_items[0]['product_id']); + + if ($product) { + $numero_serie = $product['sku'] ?? 'N/A'; // ✅ Numéro de série + + if (!empty($product['marque'])) { + $brand = $Brands->find($product['marque']); + if ($brand) { + $marque = $brand['name']; + } + } + } + } + + $result['data'][$key] = [ + $value['product_names'], + $value['user_name'], + $date_time . "
" . $statuDate, + $marque, // ✅ Remplace Prix demandé + $numero_serie, // ✅ Remplace Prix de vente + $paid_status, + $buttons + ]; + } else { + // Pour les autres (COMMERCIALE, Cheffe d'Agence) + $result['data'][$key] = [ + $value['product_names'], + $value['user_name'], + $date_time . "
" . $statuDate, + number_format((int) $value['discount'], 0, ',', ' '), + number_format((int) $value['gross_amount'], 0, ',', ' '), + $paid_status, + $buttons + ]; + } } return $this->response->setJSON($result); } } - /** * Affiche le formulaire create avec les données d'une commande existante * Pour le rôle COMMERCIALE @@ -364,19 +471,25 @@ public function create() $Products = new Products(); if ($this->request->getMethod() === 'post' && $validation->run($validationData)) { - + $session = session(); $users = $session->get('user'); $user_id = $users['id']; - $bill_no = $this->generateBillNo($users['store_id']); - + // Générer le numéro séquentiel + $bill_no = $this->generateSimpleSequentialBillNo($users['store_id']); + + // Récupérer le type de document + $document_type = $this->request->getPost('document_type') ?? 'facture'; + $posts = $this->request->getPost('product[]'); $rates = $this->request->getPost('rate_value[]'); $amounts = $this->request->getPost('amount_value[]'); $puissances = $this->request->getPost('puissance[]'); $discount = (float)$this->request->getPost('discount') ?? 0; $gross_amount = $this->calculGross($amounts); + $net_amount = $gross_amount - $discount; + // Vérification prix minimal SI rabais existe if ($discount > 0) { @@ -405,25 +518,38 @@ public function create() } } - $montant_a_payer = ($discount > 0) ? $discount : $gross_amount; + $discount = (float)$this->request->getPost('discount') ?? 0; + $gross_amount = $this->calculGross($amounts); + + + $net_amount = $gross_amount - $discount; $tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0; $tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0; + // Si des tranches sont définies, vérifier la cohérence if ($tranche_1 > 0 && $tranche_2 > 0) { - $net_amount = $tranche_1 + $tranche_2; - } else { - $net_amount = $montant_a_payer; + $total_tranches = $tranche_1 + $tranche_2; + // S'assurer que les tranches correspondent au net_amount + if (abs($total_tranches - $net_amount) > 0.01) { + return redirect()->back() + ->withInput() + ->with('errors', [ + 'Les tranches de paiement ne correspondent pas au montant total (' . + number_format($net_amount, 0, ',', ' ') . ' Ar)' + ]); + } } - + $data = [ 'bill_no' => $bill_no, + 'document_type' => $document_type, 'customer_name' => $this->request->getPost('customer_name'), 'customer_address' => $this->request->getPost('customer_address'), 'customer_phone' => $this->request->getPost('customer_phone'), 'customer_cin' => $this->request->getPost('customer_cin'), - 'customer_type' => $this->request->getPost('customer_type'), - 'source' => $this->request->getPost('source'), + 'customer_type' => $this->request->getPost('customer_type'), + 'source' => $this->request->getPost('source'), 'date_time' => date('Y-m-d H:i:s'), 'service_charge_rate' => 0, 'vat_charge_rate' => 0, @@ -446,7 +572,14 @@ public function create() $order_id = $Orders->create($data, $posts); if ($order_id) { + // ✅ NOUVEAU : Marquer immédiatement les produits comme réservés + $productModel = new Products(); + foreach ($posts as $product_id) { + $productModel->update($product_id, ['product_sold' => 1]); + } + session()->setFlashdata('success', 'Créé avec succès'); + $Notification = new NotificationController(); $Stores = new Stores(); @@ -856,6 +989,7 @@ public function update(int $id) } $dataUpdate = [ + 'document_type' => $this->request->getPost('document_type'), // ✅ AJOUTER CETTE LIGNE 'customer_name' => $this->request->getPost('customer_name'), 'customer_address' => $this->request->getPost('customer_address'), 'customer_phone' => $this->request->getPost('customer_phone'), @@ -1507,21 +1641,35 @@ public function update(int $id) $this->verifyRole('deleteOrder'); $order_id = $this->request->getPost('order_id'); $response = []; - + if ($order_id) { $Orders = new Orders(); + $OrderItems = new OrderItems(); + $Products = new Products(); + + // ✅ Récupérer tous les produits de la commande + $orderItems = $OrderItems->getOrdersItemData($order_id); + + // ✅ Libérer chaque produit (remettre product_sold = 0) + foreach ($orderItems as $item) { + if (!empty($item['product_id'])) { + $Products->update($item['product_id'], ['product_sold' => 0]); + } + } + + // Supprimer la commande if ($Orders->remove($order_id)) { - $response['success'] = true; $response['messages'] = "Successfully removed"; } else { $response['success'] = false; - $response['messages'] = "Error in the database while removing the product information"; + $response['messages'] = "Error in the database while removing the order"; } } else { $response['success'] = false; $response['messages'] = "Refersh the page again!!"; } + return $this->response->setJSON($response); } @@ -1550,6 +1698,39 @@ public function update(int $id) return $this->render_template('orders/createbyid', $data); } + + public function printDiv(int $id) +{ + $Orders = new Orders(); + $order = $Orders->getOrdersData($id); + + $docType = $order['document_type'] ?? 'facture'; + + // Rediriger vers la bonne méthode selon le type + switch($docType) { + case 'facture': + return $this->print31($id); // Factures individuelles + case 'bl': + return $this->print7($id); // Bon de livraison + case 'both': + return $this->print31($id); // Factures + Bon de livraison + default: + return $this->print31($id); + } +} + +public function printDivBL(int $id) +{ + // Force le bon de livraison + return $this->print7($id); +} + +public function printDivBLF(int $id) +{ + // Force facture + bon de livraison + return $this->print31($id); +} + // update caisse public function update_caisse($data) { @@ -1801,6 +1982,24 @@ public function print5(int $id) $company = $Company->getCompanyData(1); $today = date('d/m/Y'); + // ✅ RÉCUPÉRER LE TYPE DE DOCUMENT + $documentType = $order['document_type'] ?? 'facture'; + $documentTitle = ''; + + switch($documentType) { + case 'facture': + $documentTitle = 'FACTURE'; + break; + case 'bl': + $documentTitle = 'BON DE LIVRAISON'; + break; + case 'both': + $documentTitle = 'FACTURE & BON DE LIVRAISON'; + break; + default: + $documentTitle = 'FACTURE'; + } + // ✅ Vérifier si c'est une avance "sur mer" $isAvanceMere = false; foreach ($items as $item) { @@ -1823,7 +2022,7 @@ public function print5(int $id) - Facture '.$order['bill_no'].' + '.$documentTitle.' '.$order['bill_no'].' '; + // --- FACTURES : Une par produit --- foreach ($items as $item) { - // ✅ Utiliser getOrderItemDetails au lieu de getProductData directement $details = $this->getOrderItemDetails($item); if (!$details) continue; $unitPrice = $details['prix']; - $quantity = isset($item['qty']) ? (int) $item['qty'] : 1; - $subtotal = $unitPrice * $quantity; - - // ✅ Pour avance sur mer avec remise, utiliser la remise comme prix $prixAffiche = ($discount > 0 && $isAvanceMere) ? $discount : $unitPrice; echo ''; echo ''; echo ''; echo ''; - echo ""; + echo $style; echo ''; echo '
'; echo '
'; @@ -2668,7 +2886,7 @@ public function print31(int $id) echo '
'; echo '
'; echo 'Logo'; - echo '

Facture N° ' . esc($order_data['bill_no']) . '

'; + echo '

' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '

'; echo '

Antananarivo, le ' . date('d/m/Y') . '

'; echo '
'; echo '
'; @@ -2694,7 +2912,6 @@ public function print31(int $id) echo '' . number_format($prixAffiche, 0, '', ' ') . ''; echo ''; - // Afficher commentaire si existant if (!empty($details['commentaire'])) { echo 'Remarque : ' . esc($details['commentaire']) . ''; } @@ -2702,7 +2919,6 @@ public function print31(int $id) echo ''; } else { // TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE - // Récupérer catégorie $categoryName = 'Non définie'; if ($details['type'] === 'terre' && !empty($item['product_id'])) { $p = $Products->getProductData($item['product_id']); @@ -2762,31 +2978,12 @@ public function print31(int $id) echo ''; } - // --- BON DE COMMANDE (UNE SEULE PAGE) --- + // --- BON DE LIVRAISON (UNE SEULE PAGE) --- echo ''; echo ''; echo ''; echo ''; - echo ""; + echo $style; echo ''; echo '
'; @@ -2798,7 +2995,7 @@ public function print31(int $id) echo '
'; echo '
'; echo 'Logo'; - echo '

Bon de Commande N° ' . esc($order_data['bill_no']) . '

'; + echo '

' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '

'; echo '
'; echo ''; @@ -2842,7 +3039,6 @@ public function print31(int $id) $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']); diff --git a/app/Models/Avance.php b/app/Models/Avance.php index e3ca0b3b..790584e7 100644 --- a/app/Models/Avance.php +++ b/app/Models/Avance.php @@ -443,194 +443,178 @@ public function getProductWithBrand($product_id) */ public function convertToOrder(int $avance_id) { + $db = \Config\Database::connect(); + try { + $db->transStart(); + $avance = $this->find($avance_id); if (!$avance) { - log_message('error', "Avance introuvable : {$avance_id}"); + log_message('error', "❌ Avance {$avance_id} introuvable"); return false; } - // ✅ Vérifier que c'est bien une avance sur TERRE + // ✅ VÉRIFICATION 1 : Type d'avance 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)"); + log_message('info', "⚠️ Avance {$avance_id} est MER, pas de conversion"); 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']})"); + // ✅ VÉRIFICATION 2 : Paiement complet + if ($avance['amount_due'] > 0) { + log_message('warning', "⚠️ Avance {$avance_id} non complète (reste: {$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ÉRIFICATION 3 : Déjà convertie ? + if ($avance['is_order'] == 1) { + log_message('info', "⚠️ Avance {$avance_id} déjà convertie"); + + // ✅ Récupérer l'ID de la commande existante + $existingOrder = $db->table('orders') + ->select('id') + ->where('customer_name', $avance['customer_name']) + ->where('customer_phone', $avance['customer_phone']) + ->where('source', 'Avance convertie') + ->orderBy('id', 'DESC') + ->limit(1) + ->get() + ->getRowArray(); + + return $existingOrder ? $existingOrder['id'] : 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"); + // ✅ VÉRIFICATION 4 : Produit existe ? + $Products = new \App\Models\Products(); + $product = $Products->find($avance['product_id']); + + if (!$product) { + log_message('error', "❌ Produit {$avance['product_id']} introuvable"); return false; } - $db = \Config\Database::connect(); - $db->transStart(); + // ✅ Récupérer l'utilisateur actuel + $session = session(); + $user = $session->get('user'); - // ✅ 1. Créer la commande - $orderModel = new \App\Models\Orders(); - $bill_no = 'BILAV-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4)); + // ✅ Générer le numéro de commande + $bill_no = $this->generateBillNo($avance['store_id']); + // ✅ Préparer les données de commande $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, + 'bill_no' => $bill_no, + 'document_type' => 'facture', + 'customer_name' => $avance['customer_name'], + 'customer_address' => $avance['customer_address'], + 'customer_phone' => $avance['customer_phone'], + 'customer_cin' => $avance['customer_cin'], + 'customer_type' => 'Particulier', + 'source' => 'Avance convertie', // ✅ MARQUEUR IMPORTANT + '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 de validation + 'user_id' => $user['id'] ?? $avance['user_id'], + 'store_id' => $avance['store_id'], + 'tranche_1' => $avance['avance_amount'], + 'tranche_2' => 0, + 'order_payment_mode' => $avance['type_payment'] ?? 'En espèce', + 'service_charge_rate' => 0, + 'vat_charge_rate' => 0, + 'vat_charge' => 0, ]; - $order_id = $orderModel->insert($orderData); + // ✅ Créer la commande + $db->table('orders')->insert($orderData); + $order_id = $db->insertID(); if (!$order_id) { - throw new \Exception("Échec création commande pour avance TERRE {$avance_id}"); + throw new \Exception("Échec création commande pour avance {$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']); + // ✅ CORRECTION CRITIQUE : Créer l'item de commande SANS 'qty' + $orderItemData = [ + 'order_id' => $order_id, + 'product_id' => $avance['product_id'], + 'rate' => $avance['gross_amount'], + 'amount' => $avance['gross_amount'], + 'puissance' => $product['puissance'] ?? '', + ]; - 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)"); - } else { - log_message('warning', "Produit {$avance['product_id']} introuvable pour avance TERRE {$avance_id}"); - } + $db->table('orders_item')->insert($orderItemData); - // ✅ 3. Marquer l'avance comme convertie + // ✅ MARQUER L'AVANCE COMME CONVERTIE $this->update($avance_id, [ 'is_order' => 1, - 'active' => 0, + 'active' => 0 // ✅ Désactiver pour ne plus apparaître dans les listes ]); + // ✅ Le produit RESTE product_sold = 1 (déjà marqué lors de la création de l'avance) + $db->transComplete(); if ($db->transStatus() === false) { - log_message('error', "Transaction échouée pour avance TERRE {$avance_id}"); + log_message('error', "❌ Transaction échouée pour avance {$avance_id}"); return false; } - // ✅ 4. NOUVEAU : Envoyer notifications à TOUS les stores - $this->sendConversionNotifications($avance, $order_id, $bill_no); + log_message('info', "✅ Avance {$avance_id} convertie en commande {$order_id}"); - // ✅ 5. Notification à la caissière du store concerné - $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})"); + // ✅ Envoyer notification + $this->sendConversionNotification($avance, $order_id, $bill_no); return $order_id; } catch (\Exception $e) { - log_message('error', "Erreur conversion avance TERRE→commande : " . $e->getMessage()); + $db->transRollback(); + log_message('error', "❌ Erreur conversion avance {$avance_id}: " . $e->getMessage()); return false; } } -private function sendConversionNotifications($avance, $order_id, $bill_no) +private function sendConversionNotification($avance, $order_id, $bill_no) { try { $Notification = new \App\Controllers\NotificationController(); - $db = \Config\Database::connect(); - - // Récupérer tous les stores - $storesQuery = $db->table('stores')->select('id')->get(); - $allStores = $storesQuery->getResultArray(); - - // Récupérer les infos de l'utilisateur - $userQuery = $db->table('users') - ->select('firstname, lastname') - ->where('id', $avance['user_id']) - ->get(); - $user = $userQuery->getRowArray(); - - $userName = $user ? "{$user['firstname']} {$user['lastname']}" : 'Utilisateur inconnu'; - - // Préparer le message - $customerName = $avance['customer_name']; - $avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT); - $avanceAmount = number_format((float)$avance['gross_amount'], 0, ',', ' '); - $typeAvance = strtoupper($avance['type_avance']); - - $notificationMessage = "🎉 Avance {$typeAvance} N°{$avanceNumber} convertie en COMMANDE {$bill_no} - Client: {$customerName} - Montant: {$avanceAmount} Ar - Par: {$userName}"; - - // ✅ Envoyer notification à DAF, Direction et SuperAdmin de TOUS les stores - foreach ($allStores as $store) { - $storeId = (int)$store['id']; - - // Notification pour DAF - $Notification->createNotification( - $notificationMessage, - "DAF", - $storeId, - 'orders' - ); - - // Notification pour Direction - $Notification->createNotification( - $notificationMessage, - "Direction", - $storeId, - 'orders' - ); - - // Notification pour SuperAdmin - $Notification->createNotification( - $notificationMessage, - "SuperAdmin", - $storeId, - 'orders' - ); + $Stores = new \App\Models\Stores(); + + $allStores = $Stores->getActiveStore(); + + $message = "🔄 Avance TERRE convertie en commande
" . + "N° Avance : #{$avance['avance_id']}
" . + "Client : {$avance['customer_name']}
" . + "Commande : {$bill_no}
" . + "Montant : " . number_format($avance['gross_amount'], 0, ',', ' ') . " Ar"; + + // ✅ Notifier tous les stores (Direction, DAF, SuperAdmin) + if (is_array($allStores) && count($allStores) > 0) { + foreach ($allStores as $store) { + foreach (['Direction', 'DAF', 'SuperAdmin'] as $role) { + $Notification->createNotification( + $message, + $role, + (int)$store['id'], + 'orders' + ); + } + } } - log_message('info', "✅ Notifications conversion envoyées pour avance {$avance['avance_id']} → commande {$order_id} à tous les stores"); + // ✅ Caissière du store concerné + $Notification->createNotification( + $message, + "Caissière", + (int)$avance['store_id'], + 'orders' + ); } catch (\Exception $e) { - log_message('error', "Erreur envoi notifications conversion: " . $e->getMessage()); + log_message('error', '❌ Erreur notification: ' . $e->getMessage()); } } - /** * ✅ Hook appelé automatiquement lors du paiement d'une avance * Intégrer ceci dans votre fonction de paiement existante @@ -660,41 +644,48 @@ public function afterPayment(int $avance_id) /** * ✅ Générer un numéro de facture unique */ - private function generateBillNumber($store_id) + private function generateBillNo(int $store_id): string { - $db = \Config\Database::connect(); + $storePrefixes = [ + 1 => 'ANTS', + 2 => 'BESA', + 3 => 'BYPA', + 4 => 'TOAM', + ]; - // 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] - ); + $prefix = $storePrefixes[$store_id] ?? 'STORE'; - $result = $query->getRow(); + $db = \Config\Database::connect(); - if ($result && preg_match('/(\d+)$/', $result->bill_no, $matches)) { - $lastNumber = intval($matches[1]); - $newNumber = $lastNumber + 1; + $lastBill = $db->table('orders') + ->select('bill_no') + ->like('bill_no', $prefix . '-', 'after') + ->orderBy('id', 'DESC') + ->limit(1) + ->get() + ->getRowArray(); + + if ($lastBill && !empty($lastBill['bill_no'])) { + preg_match('/-(\d+)$/', $lastBill['bill_no'], $matches); + $newNumber = isset($matches[1]) ? (int)$matches[1] + 1 : 1; } else { $newNumber = 1; } - // Format: BILL-STORE{store_id}-{number} - return 'BILL-STORE' . $store_id . '-' . str_pad($newNumber, 5, '0', STR_PAD_LEFT); + return $prefix . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT); } - /** * ✅ Récupérer toutes les avances complètes non converties */ public function getCompletedNotConverted() { - return $this->where('amount_due', 0) + return $this->where('type_avance', 'terre') + ->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) diff --git a/app/Models/Orders.php b/app/Models/Orders.php index 7944b119..a8f4e0e5 100644 --- a/app/Models/Orders.php +++ b/app/Models/Orders.php @@ -16,6 +16,7 @@ class Orders extends Model protected $table = 'orders'; protected $allowedFields = [ 'bill_no', + 'document_type', 'customer_name', 'customer_address', 'customer_phone', @@ -202,34 +203,29 @@ class Orders extends Model $orderItemModel = new OrderItems(); $productModel = new Products(); - // ✅ RÉCUPÉRER LE TABLEAU DES PUISSANCES $puissances = $data['puissance'] ?? []; // Loop through products and insert order items $count_product = count($post); for ($x = 0; $x < $count_product; $x++) { - // ✅ AJOUT DE LA PUISSANCE $items = [ 'order_id' => $order_id, 'product_id' => $post[$x], 'rate' => $data['rate_value'][$x], 'qty' => 1, 'amount' => $data['amount_value'][$x], - 'puissance' => $puissances[$x] ?? 1, // ✅ CORRECTION ICI + 'puissance' => $puissances[$x] ?? 1, ]; $orderItemModel->insert($items); - // Decrease stock for the product - $product_data = $productModel->find($post[$x]); - if((int)$data['paid_status'] == 1){ - if ($product_data) { - $product_sold = true; - $productModel->update($post[$x], ['product_sold' => $product_sold]); - } else { - $product_sold = false; - $productModel->update($post[$x], ['product_sold' => $product_sold]); - } + // ✅ CORRECTION : Marquer product_sold = 1 dès la création + // Peu importe le statut initial (En Attente, Payé, etc.) + if (in_array((int)$data['paid_status'], [1, 2, 3])) { + $productModel->update($post[$x], ['product_sold' => 1]); + } else { + // Si statut = 0 (Refusé), laisser disponible + $productModel->update($post[$x], ['product_sold' => 0]); } } return $order_id; @@ -238,7 +234,6 @@ class Orders extends Model return false; } } - /** * count order item * @param int $order_id @@ -467,7 +462,7 @@ class Orders extends Model ELSE 0 END) AS total_virement_bancaire2 ') - ->whereIn('orders.paid_status', [1, 3]); // ← CHANGEZ CETTE LIGNE + ->whereIn('orders.paid_status', [1, 2, 3]); // ← CHANGEZ CETTE LIGNE if (!$isAdmin) { $baseQuery->where('orders.store_id', $users['store_id']); diff --git a/app/Models/Products.php b/app/Models/Products.php index 2e247c5b..5debdf50 100644 --- a/app/Models/Products.php +++ b/app/Models/Products.php @@ -11,34 +11,26 @@ class Products extends Model 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 + * ✅ Récupérer les produits selon le rôle et le store de l'utilisateur */ 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'], ['SuperAdmin', 'Direction', 'DAF']); $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(); } @@ -46,11 +38,6 @@ class Products extends Model return $builder->orderBy('id', 'DESC')->findAll(); } - /** - * get the brand data - * @param int $id - * @return array|object|null - */ public function getProductData(int $id = null) { if ($id) { @@ -92,17 +79,29 @@ class Products extends Model ->where('availability', 1) ->where('store_id', $store_id); + $db = \Config\Database::connect(); + + // Sous-requête pour exclure les produits en avance if ($excludeAvance) { - $db = \Config\Database::connect(); - $subQuery = $db->table('avances') + $subQueryAvances = $db->table('avances') ->select('product_id') ->where('active', 1) ->where('is_order', 0) ->getCompiledSelect(); - $builder->where("id NOT IN ($subQuery)", null, false); + $builder->where("id NOT IN ($subQueryAvances)", null, false); } + // ✅ LISTE : Exclure TOUS les produits ayant une commande (statuts 1, 2, 3) + $subQueryOrders = $db->table('orders_item') + ->select('orders_item.product_id') + ->join('orders', 'orders.id = orders_item.order_id') + ->whereIn('orders.paid_status', [1, 2, 3]) // ✅ Disparaît de la liste dès qu'il y a une commande + ->getCompiledSelect(); + + $builder->where("id NOT IN ($subQueryOrders)", null, false); + + // Exception pour le produit actuel lors de modification de commande if ($currentProductId) { $builder->orWhere('id', $currentProductId); } @@ -143,19 +142,32 @@ class Products extends Model return $this->delete($id) ? true : false; } + /** + * ✅ NOUVELLE MÉTHODE : Compteur DASHBOARD (exclut uniquement statut 3) + * Utilisé pour afficher le nombre total de produits dans le dashboard + */ public function countTotalProducts() { $db = \Config\Database::connect(); - $subQuery = $db->table('avances') + // Exclure produits en avance + $subQueryAvances = $db->table('avances') ->select('product_id') ->where('active', 1) ->where('is_order', 0) ->getCompiledSelect(); + // ✅ Exclure UNIQUEMENT les produits avec statut 3 (livré) + $subQueryOrders = $db->table('orders_item') + ->select('orders_item.product_id') + ->join('orders', 'orders.id = orders_item.order_id') + ->where('orders.paid_status', 3) // ✅ Décompté UNIQUEMENT quand livré + ->getCompiledSelect(); + return $this->where('is_piece', 0) ->where('product_sold', 0) - ->where("id NOT IN ($subQuery)", null, false) + ->where("id NOT IN ($subQueryAvances)", null, false) + ->where("id NOT IN ($subQueryOrders)", null, false) ->countAllResults(); } @@ -197,38 +209,43 @@ class Products extends Model } /** - * 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', 'SuperAdmin']); - - $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; + * ✅ NOUVELLE MÉTHODE : Compteur DASHBOARD par store (exclut uniquement statut 3) + * Compter les produits par store selon le rôle de l'utilisateur + */ + public function countProductsByUserStore() + { + $session = session(); + $user = $session->get('user'); + + $isAdmin = in_array($user['group_name'], ['DAF', 'Direction', 'SuperAdmin']); + + $db = \Config\Database::connect(); + + // Exclure avances + $subQueryAvances = $db->table('avances') + ->select('product_id') + ->where('active', 1) + ->where('is_order', 0) + ->getCompiledSelect(); + + // ✅ Exclure UNIQUEMENT les produits livrés (statut 3) + $subQueryOrders = $db->table('orders_item') + ->select('orders_item.product_id') + ->join('orders', 'orders.id = orders_item.order_id') + ->where('orders.paid_status', 3) // ✅ Décompté UNIQUEMENT quand livré + ->getCompiledSelect(); + + $builder = $this->where('is_piece', 0) + ->where('product_sold', 0) + ->where("id NOT IN ($subQueryAvances)", null, false) + ->where("id NOT IN ($subQueryOrders)", null, false); + + if (!$isAdmin && !empty($user['store_id']) && $user['store_id'] != 0) { + $builder->where('store_id', $user['store_id']); + } elseif (!$isAdmin) { + return 0; + } + + return $builder->countAllResults(); } - - return $builder->countAllResults(); -} } \ No newline at end of file diff --git a/app/Models/Remise.php b/app/Models/Remise.php index 2e8f4e6a..37ab296d 100644 --- a/app/Models/Remise.php +++ b/app/Models/Remise.php @@ -104,4 +104,31 @@ class Remise extends Model return "Nouvelle remise créée avec succès."; } } + /** + * Vérifier si une commande a une demande de remise en attente + * @param int $order_id + * @return bool true si remise en attente, false sinon + */ +public function hasRemisePendingForOrder(int $order_id): bool +{ + $result = $this->where('id_order', $order_id) + ->where('demande_status', 'En attente') + ->first(); + + return $result !== null; +} + +/** + * Vérifier si une commande a une remise validée + * @param int $order_id + * @return bool + */ +public function hasRemiseValidatedForOrder(int $order_id): bool +{ + $result = $this->where('id_order', $order_id) + ->whereIn('demande_status', ['Accepté', 'Validé']) + ->first(); + + return $result !== null; +} } \ No newline at end of file diff --git a/app/Views/orders/edit.php b/app/Views/orders/edit.php index c93d2f32..cf06cbd6 100644 --- a/app/Views/orders/edit.php +++ b/app/Views/orders/edit.php @@ -64,10 +64,30 @@ -
+
+ +
+ +
+ +
+
+
- +