verifyRole('viewOrder'); $data['page_title'] = $this->pageTitle; return $this->render_template('orders/index', $data); } /** * Génère un numéro de facture personnalisé selon le magasin * @param int $store_id * @return string */ private function generateSimpleSequentialBillNo(int $store_id): string { $storePrefixes = [ 1 => 'ANTS', 2 => 'BESA', 3 => 'BYPA', 4 => 'TOAM', ]; $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']); $Orders = new Orders(); $result = ['data' => []]; $data = $Orders->getOrdersData(); $session = session(); $users = $session->get('user'); // ======================================== // 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 = ''; $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é'; } elseif ($value['paid_status'] == 2) { $paid_status = 'En Attente'; } elseif ($value['paid_status'] == 3) { $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) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } elseif ($daysPassed >= 8 && $daysPassed < 15) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } else { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } } $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); } // ======================================== // POUR DIRECTION OU DAF // ======================================== elseif($users['group_name'] == "Direction" || $users['group_name'] == "DAF" || $users['group_name'] == "SuperAdmin" ){ foreach ($data as $key => $value) { $date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); $buttons = ''; // Bouton imprimer if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") { $buttons .= ''; } // ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente) if (in_array('updateOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) { $buttons .= ' '; } // ✅ Bouton supprimer pour statuts 0 et 2 if (in_array('deleteOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) { $buttons .= ' '; } // Statut de paiement if ($value['paid_status'] == 1) { $paid_status = 'payé'; } elseif ($value['paid_status'] == 2) { $paid_status = 'En Attente'; } elseif ($value['paid_status'] == 3) { $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) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } elseif ($daysPassed >= 8 && $daysPassed < 15) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } else { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } } $result['data'][$key] = [ $value['bill_no'], $value['customer_name'], $value['customer_phone'], $date_time . "
" . $statuDate, number_format((int) $value['discount'], 0, ',', ' '), number_format((int) $value['gross_amount'], 0, ',', ' '), $paid_status, $buttons ]; } return $this->response->setJSON($result); } // ======================================== // POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, Cheffe d'Agence) // ======================================== else { foreach ($data as $key => $value) { $date_time = date('d-m-Y h:i a', strtotime($value['date_time'])); $buttons = ''; // Bouton imprimer if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") { $buttons .= ''; } // ✅ 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 .= ' '; } // Bouton voir if (in_array('viewOrder', $this->permission)) { $buttons .= ' '; } // ✅ 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 .= ' '; } // Statut de paiement if ($value['paid_status'] == 1) { $paid_status = 'Payé'; } elseif ($value['paid_status'] == 2) { $paid_status = 'En Attente'; } elseif ($value['paid_status'] == 3) { $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) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } elseif ($daysPassed >= 8 && $daysPassed < 15) { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } else { $statuDate = ' depuis ' . $daysPassed . ' Jours'; } } // ✅ 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 */ /** * function who check if the product is null * and create notification about it * @param array $product_id id of the product * @param int $store_id id of the store * @return void */ private function checkProductisNull(array $product_id, $store_id) { $notification = new NotificationController(); $product = new Products(); for ($i = 0; $i < count($product_id); $i++) { $singleProduct = $product->getProductData($product_id[$i]); if ($singleProduct['product_sold'] == true) { $notification->createNotification("Produit en rupture de stock", "Direction", $store_id, "products"); } } } private function calculGross($request) { $amount = $request; $montant = 0; for ($i = 0; $i < \count($amount); $i++) { $montant += $amount[$i]; } return $montant; } /** * ✅ AMÉLIORATION : Notifications centralisées pour Direction/DAF/SuperAdmin (tous stores) */ public function create() { $this->verifyRole('createOrder'); $data['page_title'] = $this->pageTitle; $validation = \Config\Services::validation(); $products = $this->request->getPost('product[]'); if ($products !== null && (count($products) !== count(array_unique($products)))) { return redirect()->back()->withInput()->with('errors', ['product' => 'Chaque produit sélectionné doit être unique.']); } $validation->setRules([ 'product[]' => 'required', 'customer_type' => 'required', 'source' => 'required' ]); $validationData = [ 'product[]' => $this->request->getPost('product[]'), 'customer_type' => $this->request->getPost('customer_type'), 'source' => $this->request->getPost('source') ]; $Orders = new Orders(); $Company = new Company(); $Products = new Products(); if ($this->request->getMethod() === 'post' && $validation->run($validationData)) { $session = session(); $users = $session->get('user'); $user_id = $users['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) { $FourchettePrix = new \App\Models\FourchettePrix(); foreach ($posts as $index => $productId) { $productId = (int)$productId; $productData = $Products->getProductData($productId); $fourchette = $FourchettePrix->getFourchettePrixByProductId($productId); if ($fourchette) { $prixMinimal = (float)$fourchette['prix_minimal']; if ($discount < $prixMinimal) { $prixMinimalFormatted = number_format($prixMinimal, 0, ',', ' '); $discountFormatted = number_format($discount, 0, ',', ' '); return redirect()->back() ->withInput() ->with('errors', [ "⚠️ Commande bloquée : Le rabais de {$discountFormatted} Ar pour « {$productData['name']} » est trop élevé." ]); } } } } $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) { $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'), 'date_time' => date('Y-m-d H:i:s'), 'service_charge_rate' => 0, 'vat_charge_rate' => 0, 'vat_charge' => 0, 'net_amount' => $net_amount, 'discount' => $discount, 'paid_status' => 2, 'user_id' => $user_id, 'amount_value' => $amounts, 'gross_amount' => $gross_amount, 'rate_value' => $rates, 'puissance' => $puissances, 'store_id' => $users['store_id'], 'tranche_1' => $tranche_1, 'tranche_2' => $tranche_2, 'order_payment_mode' => $this->request->getPost('order_payment_mode_1'), 'order_payment_mode_1' => $this->request->getPost('order_payment_mode_2') ]; $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(); if ($discount > 0) { // ✅ DEMANDE DE REMISE : NOTIFIER TOUS LES STORES $Order_item1 = new OrderItems(); $order_item_data = $Order_item1->getOrdersItemData($order_id); $product_ids = array_column($order_item_data, 'product_id'); $productData = new Products(); $product_data_results = []; foreach ($product_ids as $prod_id) { $id = (int) $prod_id; $product_data_results[] = $productData->getProductData($id); } $product_lines = []; foreach ($product_data_results as $product) { if (isset($product['sku'], $product['price'])) { $sku = $product['sku']; $price = $product['price']; $product_lines[] = "{$sku}:{$price}"; } } $product_output = implode("\n", $product_lines); $data1 = [ 'date_demande' => date('Y-m-d H:i:s'), 'montant_demande' => $discount, 'total_price' => $amounts, 'id_store' => $users['store_id'], 'id_order' => $order_id, 'product' => $product_output, 'demande_status' => 'En attente' ]; $Remise = new Remise(); $id_remise = $Remise->addDemande($data1); // ✅ RÉCUPÉRER TOUS LES STORES $allStores = $Stores->getActiveStore(); $montantFormatted = number_format($discount, 0, ',', ' '); $message = "💰 Nouvelle demande de remise : {$montantFormatted} Ar
" . "Commande : {$bill_no}
" . "Store : " . $this->returnStore($users['store_id']) . "
" . "Demandeur : {$users['firstname']} {$users['lastname']}"; // ✅ NOTIFIER SUPERADMIN, DIRECTION, DAF DE TOUS LES STORES if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { // SuperAdmin (validation) $Notification->createNotification( $message, "SuperAdmin", (int)$store['id'], 'remise/' ); // Direction (consultation) $Notification->createNotification( $message . "
Pour information", "Direction", (int)$store['id'], 'remise/' ); // DAF (consultation) $Notification->createNotification( $message . "
Pour information", "DAF", (int)$store['id'], 'remise/' ); } } } else { // ✅ COMMANDE SANS REMISE : NOTIFIER CAISSIÈRE DU STORE + TOUS LES CENTRAUX // Caissière du store concerné $Notification->createNotification( "📦 Nouvelle commande à valider : {$bill_no}
" . "Client : {$data['customer_name']}
" . "Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar", "Caissière", (int)$users['store_id'], "orders" ); // ✅ RÉCUPÉRER TOUS LES STORES $allStores = $Stores->getActiveStore(); $messageGlobal = "📋 Nouvelle commande créée : {$bill_no}
" . "Store : " . $this->returnStore($users['store_id']) . "
" . "Client : {$data['customer_name']}
" . "Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar
" . "Créée par : {$users['firstname']} {$users['lastname']}"; // ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { $Notification->createNotification( $messageGlobal, "Direction", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "DAF", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "SuperAdmin", (int)$store['id'], 'orders' ); } } } if ($users["group_name"] != "COMMERCIALE") { $this->checkProductisNull($posts, $users['store_id']); } return redirect()->to('orders/'); } else { session()->setFlashdata('errors', 'Error occurred!!'); return redirect()->to('orders/create/'); } } else { // Affichage du formulaire $company = $Company->getCompanyData(1); $session = session(); $users = $session->get('user'); $store_id = $users['store_id']; $data = [ 'paid_status' => 2, 'company_data' => $company, 'is_vat_enabled' => ($company['vat_charge_value'] > 0), 'is_service_enabled' => ($company['service_charge_value'] > 0), 'products' => $Products->getProductData2($store_id), 'validation' => $validation, 'page_title' => $this->pageTitle, ]; return $this->render_template('orders/create', $data); } } /** * Marquer une commande comme livrée * Accessible uniquement par le rôle SECURITE */ public function markAsDelivered() { // Activer le retour JSON même en cas d'erreur ini_set('display_errors', 0); $order_id = $this->request->getPost('order_id'); $response = ['success' => false, 'messages' => '']; try { $session = session(); $users = $session->get('user'); // Vérifier que l'utilisateur est SECURITE if (!isset($users['group_name']) || $users['group_name'] !== 'SECURITE') { $response['messages'] = "Accès refusé : seule la sécurité peut marquer une commande comme livrée."; return $this->response->setJSON($response); } if (!$order_id) { $response['messages'] = "ID de commande manquant."; return $this->response->setJSON($response); } $Orders = new Orders(); // Récupérer la commande actuelle $current_order = $Orders->getOrdersData((int)$order_id); if (!$current_order) { $response['messages'] = "Commande introuvable."; return $this->response->setJSON($response); } // Vérifier que la commande est validée (paid_status = 1) if ($current_order['paid_status'] != 1) { $response['messages'] = "Cette commande doit être validée avant d'être marquée comme livrée."; return $this->response->setJSON($response); } // Mettre à jour UNIQUEMENT le statut à 3 (Livré) $updated = $Orders->update((int)$order_id, ['paid_status' => 3]); if ($updated) { // ✅ Créer une notification centralisée pour tous les stores try { $Notification = new NotificationController(); $Stores = new Stores(); $allStores = $Stores->getActiveStore(); $messageGlobal = "📦 Commande livrée : {$current_order['bill_no']}
" . "Store : " . $this->returnStore($current_order['store_id']) . "
" . "Client : {$current_order['customer_name']}
" . "Livrée par : {$users['firstname']} {$users['lastname']}"; // ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { $Notification->createNotification( $messageGlobal, "Direction", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "DAF", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "SuperAdmin", (int)$store['id'], 'orders' ); } } } catch (\Exception $e) { // Si la notification échoue, on continue quand même log_message('error', 'Erreur notification: ' . $e->getMessage()); } $response['success'] = true; $response['messages'] = "La commande a été marquée comme livrée avec succès."; } else { $response['messages'] = "Erreur lors de la mise à jour du statut."; } } catch (\Exception $e) { log_message('error', 'Erreur markAsDelivered: ' . $e->getMessage()); $response['messages'] = "Erreur serveur : " . $e->getMessage(); } return $this->response->setJSON($response); } public function getProductValueById() { $product_id = $this->request->getPost('product_id'); if ($product_id) { $Products = new \App\Models\Products(); $product_data = $Products->getProductData($product_id); $FourchettePrix = new \App\Models\FourchettePrix(); $fourchette = $FourchettePrix->where('product_id', $product_id)->first(); // ✅ Extraire la puissance - Plusieurs méthodes $puissance_extracted = ''; // Méthode 1: Chercher dans le champ puissance de la BD if (!empty($product_data['puissance'])) { $puissance_extracted = $product_data['puissance']; } // Méthode 2: Chercher à la fin du nom (format: |150) if (empty($puissance_extracted) && !empty($product_data['name'])) { if (preg_match('/\|(\d+)(?:cc)?$/i', $product_data['name'], $matches)) { $puissance_extracted = $matches[1]; } } // Méthode 3: Chercher n'importe où dans le nom (format: 150cc ou 150 CC) if (empty($puissance_extracted) && !empty($product_data['name'])) { if (preg_match('/(\d+)\s*cc/i', $product_data['name'], $matches)) { $puissance_extracted = $matches[1]; } } // Log pour debug (à retirer en production) log_message('info', 'Product ID: ' . $product_id . ' - Puissance: ' . $puissance_extracted); $response = [ 'id' => $product_data['id'], 'sku' => $product_data['sku'], 'name' => $product_data['name'], 'prix_vente' => $product_data['prix_vente'], 'prix_minimal' => $fourchette ? $fourchette['prix_minimal'] : 0, 'puissance' => $puissance_extracted, 'numero_de_moteur' => $product_data['numero_de_moteur'] ?? '', ]; return $this->response->setJSON($response); } return $this->response->setJSON(['error' => 'Product ID missing']); } public function getTableProductRow() { $Products = new Products(); $session = session(); $users = $session->get('user'); $store_id = $users['store_id']; $product_data = $Products->getProductData2($store_id); // ✅ Nettoyer les données pour ne pas afficher la puissance dans le select foreach ($product_data as &$product) { // Extraire la puissance si elle est dans le nom if (!empty($product['name']) && preg_match('/\|(\d+)(?:cc)?$/i', $product['name'], $matches)) { // Stocker la puissance séparément if (empty($product['puissance'])) { $product['puissance'] = $matches[1]; } } } return $this->response->setJSON($product_data); } public function update(int $id) { $this->verifyRole('updateOrder'); $data['page_title'] = $this->pageTitle; $validation = \Config\Services::validation(); $Orders = new Orders(); $current_order = $Orders->getOrdersData($id); // ✅ AJOUT : Vérification plus détaillée if (!$current_order || !isset($current_order['id'])) { log_message('error', 'Commande introuvable pour ID: ' . $id); session()->setFlashData('errors', 'Commande introuvable.'); return redirect()->to('orders/'); } 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/'); } $validation->setRules([ 'product' => 'required' ]); $validationData = [ 'product' => $this->request->getPost('product') ]; $Company = new Company(); $Products = new Products(); $OrderItems = new OrderItems(); $session = session(); $user = $session->get('user'); $role = $user['group_name'] ?? null; if ($this->request->getMethod() === 'post' && $validation->run($validationData)) { $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']; if ($role === 'COMMERCIALE') { $paid_status = 2; } else { $paid_status = $this->request->getPost('paid_status'); } $validated_by = $current_order['validated_by'] ?? null; $validated_at = $current_order['validated_at'] ?? null; if ($old_paid_status != 1 && $paid_status == 1 && $role === 'Caissière') { $validated_by = $user['id']; $validated_at = date('Y-m-d H:i:s'); } if (in_array($paid_status, [0, 2])) { $validated_by = null; $validated_at = null; } $discount = $this->request->getPost('discount'); $original_discount = $this->request->getPost('original_discount'); if ($discount === '' || $discount === null) { $discount = $original_discount; } $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'), 'customer_cin' => $this->request->getPost('customer_cin'), 'customer_type' => $this->request->getPost('customer_type'), 'source' => $this->request->getPost('source'), 'gross_amount' => $this->request->getPost('gross_amount_value'), 'service_charge_rate' => $this->request->getPost('service_charge_rate'), 'service_charge' => max(0, (float)$this->request->getPost('service_charge_value')), 'vat_charge_rate' => $this->request->getPost('vat_charge_rate'), 'vat_charge' => max(0, (float)$this->request->getPost('vat_charge_value')), 'net_amount' => $this->request->getPost('net_amount_value'), 'discount' => (float)$discount, 'paid_status' => $paid_status, 'product' => $this->request->getPost('product'), 'product_sold' => true, 'rate_value' => $this->request->getPost('rate_value'), 'amount_value' => $this->request->getPost('amount_value'), 'puissance' => $this->request->getPost('puissance'), 'tranche_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_1') : null, 'tranche_2' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_2') : null, 'order_payment_mode' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_1') : null, 'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null, 'validated_by' => $validated_by, 'validated_at' => $validated_at ]; if ($Orders->updates($id, $dataUpdate)) { $order_item_data = $OrderItems->getOrdersItemData($id); $product_ids = array_column($order_item_data, 'product_id'); $Notification = new NotificationController(); // ✅ NOTIFICATION CENTRALISÉE POUR VALIDATION if ($old_paid_status == 2 && $paid_status == 1) { $customer_name = $this->request->getPost('customer_name'); $bill_no = $current_order['bill_no']; // ✅ Notification SECURITE du store concerné $Notification->createNotification( "Commande validée: {$bill_no} - Client: {$customer_name}", "SECURITE", (int)$user['store_id'], 'orders' ); // ✅ RÉCUPÉRER TOUS LES STORES $Stores = new Stores(); $allStores = $Stores->getActiveStore(); $messageGlobal = "✅ Commande validée : {$bill_no}
" . "Store : " . $this->returnStore($user['store_id']) . "
" . "Client : {$customer_name}
" . "Validée par : {$user['firstname']} {$user['lastname']}"; // ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { $Notification->createNotification( $messageGlobal, "Direction", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "DAF", (int)$store['id'], 'orders' ); $Notification->createNotification( $messageGlobal, "SuperAdmin", (int)$store['id'], 'orders' ); } } } if ((float)$discount > 0) { $productData = new Products(); $product_data_results = []; foreach ($product_ids as $product_id) { $product_data_results[] = $productData->getProductData((int) $product_id); } $product_lines = []; foreach ($product_data_results as $product) { if (isset($product['sku'], $product['price'])) { $product_lines[] = "{$product['sku']}:{$product['price']}"; } } $product_output = implode("\n", $product_lines); $data1 = [ 'date_demande' => date('Y-m-d H:i:s'), 'montant_demande' => $this->request->getPost('discount'), 'total_price' => $this->request->getPost('amount_value'), 'id_store' => $user['store_id'], 'id_order' => $id, 'product' => $product_output, 'demande_status' => 'En attente' ]; $Remise = new Remise(); $Remise->updateRemise1($id, $data1); $Notification->createNotification( "Une nouvelle demande de remise a été ajoutée", "Direction", (int)$user['store_id'] ?? null, "remise/" ); } session()->setFlashData('success', 'Commande mise à jour avec succès.'); return redirect()->to('orders/'); } else { session()->setFlashData('errors', 'Une erreur est survenue lors de la mise à jour.'); return redirect()->to('orders/update/' . $id); } } // ✅ Affichage du formulaire $company = $Company->getCompanyData(1); $data['company_data'] = $company; $data['is_vat_enabled'] = ($company['vat_charge_value'] > 0); $data['is_service_enabled'] = ($company['service_charge_value'] > 0); $orders_data = $Orders->getOrdersData($id); // ✅ VÉRIFICATION SUPPLÉMENTAIRE if (!$orders_data || !isset($orders_data['id'])) { log_message('error', 'Données de commande vides pour ID: ' . $id); session()->setFlashData('errors', 'Impossible de charger les données de la commande.'); return redirect()->to('orders/'); } $data['is_editable'] = !in_array($orders_data['paid_status'], [1, 3]); $orders_data['montant_tranches'] = (!empty($orders_data['discount']) && $orders_data['discount'] > 0) ? $orders_data['discount'] : $orders_data['gross_amount']; $result = ['order' => $orders_data]; $orders_item = $OrderItems->getOrdersItemData($orders_data['id']); foreach ($orders_item as $item) { $result['order_item'][] = $item; } $data['order_data'] = $result; $data['products'] = $Products->getActiveProductData(); $data['validation'] = $validation; return $this->render_template('orders/edit', $data); } public function lookOrder(int $id) { $this->verifyRole('viewOrder'); $data['page_title'] = $this->pageTitle; $Orders = new Orders(); $Company = new Company(); $Products = new Products(); $OrderItems = new OrderItems(); $Brands = new Brands(); // En cas d’échec de la validation ou si GET $company = $Company->getCompanyData(1); $data['company_data'] = $company; $data['is_vat_enabled'] = ($company['vat_charge_value'] > 0); $data['is_service_enabled'] = ($company['service_charge_value'] > 0); $orders_data = $Orders->getOrdersData($id); // $sum_order_item = $OrderItems->getSumOrdersItemData($orders_data['id']); $result = [ 'order' => $orders_data, // 'sum_order_data' => $sum_order_item ]; $orders_item = $OrderItems->getOrdersItemData($orders_data['id']); foreach ($orders_item as $item) { $result['order_item'][] = $item; } $data['order_data'] = $result; $data['products'] = $Products->getActiveProductData(); $data['brands'] = $Brands->findAll(); return $this->response->setJSON($data); } /** * return storename * @param int $id * @return string */ private function returnStore($id) { $Stores = new Stores(); $store = $Stores->getActiveStore(); $name = ""; foreach ($store as $key => $value) { if ($value['id'] == $id) { $name = $value['name']; } } return $name; } public function print2(int $id) { $this->verifyRole('viewOrder'); if ($id) { $Orders = new Orders(); $Company = new Company(); $Products = new Products(); $OrderItems = new OrderItems(); // Récupération des données $order_data = $Orders->getOrdersData($id); $orders_items = $OrderItems->getOrdersItemData($id); $company_info = $Company->getCompanyData(1); // die(\var_dump($orders_items)); $html = ''; // Vérifier si l'utilisateur a payé if ($order_data['paid_status'] == 1) { $paid_status = "Payé"; } elseif ($order_data['paid_status'] == 2) { $paid_status = "En Attente"; } else { $paid_status = "Refusé"; } // Génération du HTML $html .= '
' . esc($company_info['company_name']) . '

Facture ID : ' . esc($order_data['bill_no']) . '

NIF : ' . esc($company_info['NIF']) . '

STAT : ' . esc($company_info['STAT']) . '

Contact : ' . esc($company_info['phone']) . ' ' . esc($company_info['phone2']) . '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

Nom: ' . esc($order_data['customer_name']) . '

Adresse: ' . esc($order_data['customer_address']) . '

Téléphone: ' . esc($order_data['customer_phone']) . '

CIN: ' . esc($order_data['customer_cin']) . '



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

'; foreach ($orders_items as $item) { $product_data = $Products->getProductData($item['product_id']); // ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE $puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : ($product_data['puissance'] ?? ''); $html .= ' ✅ '; } $html .= '
Marque Moteur Puissance Prix
' . esc($product_data['sku']) . ' ' . esc($product_data['numero_de_moteur']) . ' ' . number_format((float) $item['amount'], 2, '.', ' ') . '
'; $html .= ''; // Vérification et ajout des informations de paiement if (!empty($order_data['order_payment_mode'])) { $html .= ''; } if (!empty($order_data['tranche_1'])) { $html .= ''; } if (!empty($order_data['tranche_2'])) { $html .= ''; } $html .= '
Total: ' . number_format(((float) $order_data['gross_amount'] - ((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '
TVA: ' . number_format((((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '
Réduction: ' . number_format((float) $order_data['discount'], 2, '.', ' ') . '
Total à payer: ' . number_format((float) ($order_data['net_amount']), 2, '.', ' ') . '
Statut: ' . $paid_status . '
Mode de paiement: ' . esc($order_data['order_payment_mode']) . '
Tranche 1: ' . number_format((float) $order_data['tranche_1'], 2, '.', ' ') . '
Tranche 2: ' . number_format((float) $order_data['tranche_2'], 2, '.', ' ') . '

L\'acheteur

Le vendeur

'; return $this->response->setBody($html); } } public function print(int $id) { $this->verifyRole('viewOrder'); if ($id) { $Orders = new Orders(); $Company = new Company(); $Products = new Products(); $OrderItems = new OrderItems(); $order_data = $Orders->getOrdersData($id); $orders_items = $OrderItems->getOrdersItemData($id); $company_info = $Company->getCompanyData(1); $paid_status = ($order_data['paid_status'] == 1) ? "Payé" : "Non payé"; foreach ($orders_items as $index => $item) { $product_data = $Products->getProductData($item['product_id']); // ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE (si modifiée), sinon celle du produit $puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : ($product_data['puissance'] ?? ''); echo '
' . esc($company_info['company_name']) . '

Facture ID : ' . esc($order_data['bill_no']) . '

NIF : ' . esc($company_info['NIF']) . '

STAT : ' . esc($company_info['STAT']) . '

Contact : ' . esc($company_info['phone']) . ' ' . esc($company_info['phone2']) . '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

Nom: ' . esc($order_data['customer_name']) . '

Adresse: ' . esc($order_data['customer_address']) . '

Téléphone: ' . esc($order_data['customer_phone']) . '

CIN: ' . esc($order_data['customer_cin']) . '



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

Marque Moteur Puissance Prix
' . esc($product_data['sku']) . ' ' . esc($product_data['numero_de_moteur']) . ' ' . number_format((float)$item['amount'], 2, '.', ' ') . '
'; if (!empty($order_data['order_payment_mode'])) { echo ''; } if (!empty($order_data['tranche_1'])) { echo ''; } if (!empty($order_data['tranche_2'])) { echo ''; } echo '
Total: ' . number_format($item['amount'] - ($item['amount'] * 0.2), 2, '.', ' ') . '
TVA: ' . number_format($item['amount'] * 0.2, 2, '.', ' ') . '
Réduction: ' . number_format($order_data['discount'], 2, '.', ' ') . '
Total à payer: ' . number_format($item['amount'] - $order_data['discount'], 2, '.', ' ') . '
Statut: ' . $paid_status . '
Mode de paiement:' . esc($order_data['order_payment_mode']) . '
Tranche 1:' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . '
Tranche 2:' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . '

L\'acheteur

Le vendeur

'; } } } public function remove() { $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 order"; } } else { $response['success'] = false; $response['messages'] = "Refersh the page again!!"; } return $this->response->setJSON($response); } public function createById(int $id) { $this->verifyRole('createOrder'); $data['page_title'] = $this->pageTitle; $Company = new Company(); $Products = new Products(); // If validation fails $company = $Company->getCompanyData(1); // Prepare data for the view $data = [ 'company_data' => $company, 'is_vat_enabled' => ($company['vat_charge_value'] > 0), 'is_service_enabled' => ($company['service_charge_value'] > 0), 'products' => $Products->getProductData($id), // 'discount' => $Products->getProductData($id)['discount'], 'pu' => $Products->getProductData($id)['prix_vente'], 'page_title' => $this->pageTitle, ]; 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) { $p1 = 0; $p2 = 0; $op = ""; $p3 = 0; $dest = ""; if ($data['tranche2']) { if ($data['order']) { # code... } } } public function print3(int $id) { $this->verifyRole('viewOrder'); if (!$id) { return $this->response->setStatusCode(400, 'ID manquant'); } $Orders = new Orders(); $Company = new Company(); $OrderItems = new OrderItems(); $Products = new Products(); $order_data = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($id); $company_info = $Company->getCompanyData(1); $paid_status = $order_data['paid_status'] == 1 ? "Payé" : "Refusé"; // STYLE COMMUN $style = ' '; // --- FACTURES : Une par produit --- foreach ($items as $item) { $product_data = $Products->getProductData($item['product_id']); // ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE $puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : (!empty($product_data['puissance']) ? $product_data['puissance'] : ''); $unitPrice = (float)$item['amount']; $quantity = isset($item['qty']) ? (int)$item['qty'] : 1; $subtotal = $unitPrice * $quantity; $vatAmount = $subtotal * 0.2; $discount = (float)$order_data['discount']; $totalNet = $subtotal + $vatAmount - $discount; echo ' ' . $style . '
FACTURE

Facture ID : ' . esc($order_data['bill_no']) . '

NIF : ' . esc($company_info['NIF']) . '

STAT : ' . esc($company_info['STAT']) . '

Contact : ' . esc($company_info['phone']) . ' / ' . esc($company_info['phone2']) . '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

Nom: ' . esc($order_data['customer_name']) . '

Adresse: ' . esc($order_data['customer_address']) . '

Téléphone: ' . esc($order_data['customer_phone']) . '

CIN: ' . esc($order_data['customer_cin']) . '

MarqueMoteurPuissance (CC)Prix unitaire
' . esc($product_data['sku']) . ' ' . esc($product_data['numero_de_moteur']) . ' ' . number_format($unitPrice, 2, '.', ' ') . '
'; if (!empty($order_data['order_payment_mode'])) { echo ''; } if (!empty($order_data['tranche_1'])) { echo ''; } if (!empty($order_data['tranche_2'])) { echo ''; } echo '
Total HT:' . number_format($subtotal, 2, '.', ' ') . ' Ar
TVA (20%):' . number_format($vatAmount, 2, '.', ' ') . ' Ar
Réduction:' . number_format($discount, 2, '.', ' ') . ' Ar
Total à payer:' . number_format($totalNet, 2, '.', ' ') . ' Ar
Statut:' . $paid_status . '
Mode de paiement:' . esc($order_data['order_payment_mode']) . '
Tranche 1:' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . ' Ar
Tranche 2:' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . ' Ar

L\'acheteur

Le vendeur

'; } // --- BON DE COMMANDE : une seule fois après la boucle --- echo ' ' . $style . '
BON DE COMMANDE

Commande ID : ' . esc($order_data['order_no'] ?? $order_data['bill_no']) . '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

Nom: ' . esc($order_data['customer_name']) . '

Adresse: ' . esc($order_data['customer_address']) . '

Téléphone: ' . esc($order_data['customer_phone']) . '

CIN: ' . esc($order_data['customer_cin']) . '

'; $total_ht = 0; foreach ($items as $item) { $product_data = $Products->getProductData($item['product_id']); // ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE $puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : (!empty($product_data['puissance']) ? $product_data['puissance'] : ''); $total_ht += (float)$item['amount']; echo ''; } $tva = $total_ht * 0.2; $total_ttc = $total_ht + $tva - $order_data['discount']; echo '
Marque Moteur Puissance (CC) Prix
' . esc($product_data['sku']) . ' ' . esc($product_data['numero_de_moteur']) . ' ' . number_format((float)$item['amount'], 2, '.', ' ') . '
Total HT:' . number_format($total_ht, 2, '.', ' ') . ' Ar
TVA (20%):' . number_format($tva, 2, '.', ' ') . ' Ar
Réduction:' . number_format($order_data['discount'], 2, '.', ' ') . ' Ar
Total à payer:' . number_format($total_ttc, 2, '.', ' ') . ' Ar

L\'acheteur

Le vendeur

'; } // PRINT5 - Facture détaillée avec conditions générales // ==================================== public function print5(int $id) { $this->verifyRole('viewOrder'); if (!$id) { throw new \CodeIgniter\Exceptions\PageNotFoundException(); } $Orders = new Orders(); $Company = new Company(); $OrderItems = new OrderItems(); $order = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($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) { if (!empty($item['product_name']) && empty($item['product_id'])) { $isAvanceMere = true; break; } } $discount = (float) $order['discount']; $grossAmount = (float) $order['gross_amount']; $totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalHT = $totalTTC / 1.20; $tva = $totalTTC - $totalHT; $inWords = $this->numberToWords((int) round($totalTTC)); $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; $html = ' '.$documentTitle.' '.$order['bill_no'].'
'; // ✅ GÉNÉRER 2 DOCUMENTS IDENTIQUES for ($i = 0; $i < 2; $i++) { $html .= '

'.esc($company['company_name']).'

NIF : '.esc($company['NIF']).'

STAT : '.esc($company['STAT']).'

Contact : '.esc($company['phone']).' | '.esc($company['phone2']).'

Logo

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

DOIT Nom : '.esc($order['customer_name']).'

Adresse : '.esc($order['customer_address']).'

CIN : '.esc($order['customer_cin']).'

Téléphone : '.esc($order['customer_phone'] ?? '').'

Antananarivo, le '.$today.'

'; // ✅ TABLEAU ADAPTÉ SELON LE TYPE if ($isAvanceMere) { $html .= ' '; foreach ($items as $it) { $details = $this->getOrderItemDetails($it); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; $html .= ''; } $html .= '
Produit Prix (Ar)
'.esc($details['product_name']); if (!empty($details['commentaire'])) { $html .= '
'.esc($details['commentaire']).''; } $html .= '
'.number_format($prixAffiche, 0, '', ' ').'
'; } else { $html .= ' '; foreach ($items as $it) { $details = $this->getOrderItemDetails($it); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; $html .= ' '; } $html .= '
MARQUE Désignation N° Moteur N° Châssis Puissance (CC) PRIX (Ar)
'.esc($details['marque']).' '.esc($details['product_name']).' '.esc($details['numero_moteur']).' '.esc($details['numero_chassis']).' '.esc($details['puissance']).' '.number_format($prixAffiche, 0, '', ' ').'
'; } $html .= '
Prix (HT) : '.number_format($totalHT, 0, '', ' ').' Ar
TVA (20%) : '.number_format($tva, 0, '', ' ').' Ar
Total (TTC) : '.number_format($totalTTC, 0, '', ' ').' Ar
Arrêté à la somme de :
'.$inWords.'
L\'Acheteur

__________________
Le Vendeur

__________________
'; } $html .= '
'; // ✅ GÉNÉRER 2 CONDITIONS IDENTIQUES for ($i = 0; $i < 2; $i++) { $html .= '

Conditions Générales

Logo
L\'Acheteur
'; } $html .= '
'; return $this->response->setBody($html); } /** * Convertit un nombre entier en texte (français, sans décimales). * Usage basique, pour Ariary. */ private function numberToWords(int $num): string { // Cas zéro if ($num === 0) { return 'zéro ariary'; } // Tableaux de base $units = [ '', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf' ]; $tens = [ 2 => 'vingt', 3 => 'trente', 4 => 'quarante', 5 => 'cinquante', 6 => 'soixante', 7 => 'soixante-dix', 8 => 'quatre-vingt', 9 => 'quatre-vingt-dix' ]; // Fonction récursive interne (sans la monnaie) $convert = function(int $n) use (&$convert, $units, $tens): string { if ($n < 20) { return $units[$n]; } if ($n < 100) { $d = intdiv($n, 10); $r = $n % 10; // 70–79 et 90–99 if ($d === 7 || $d === 9) { $base = $d === 7 ? 60 : 80; return $tens[$d] . ($r ? '-' . $units[$n - $base] : ''); } // 20–69 ou 80–89 return $tens[$d] . ($r ? '-' . $units[$r] : ''); } if ($n < 1000) { $h = intdiv($n, 100); $rest = $n % 100; $hundredText = $h > 1 ? $units[$h] . ' cent' : 'cent'; // « deux cents » prend un « s » si pas de reste if ($h > 1 && $rest === 0) { $hundredText .= 's'; } return $hundredText . ($rest ? ' ' . $convert($rest) : ''); } if ($n < 1000000) { $k = intdiv($n, 1000); $rest = $n % 1000; $thousandText = $k > 1 ? $convert($k) . ' mille' : 'mille'; return $thousandText . ($rest ? ' ' . $convert($rest) : ''); } // millions et plus $m = intdiv($n, 1000000); $rest = $n % 1000000; $millionText = $m > 1 ? $convert($m) . ' million' : 'un million'; // pas de 's' à million en francais return $millionText . ($rest ? ' ' . $convert($rest) : ''); }; // Construit le texte sans la monnaie, puis ajoute 'ariary' à la fin $words = $convert($num); return trim($words) . ' ariary'; } /** * ✅ 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' => !empty($item['puissance']) ? $item['puissance'] : ($product['puissance'] ?? ''), // ✅ Priorité à la commande 'commentaire' => '', 'prix' => (float)$item['amount'] ]; } /** * ✅ PRINT7 - Bon de commande adapté pour avances "sur mer" et "sur terre" */ public function print7(int $id) { $this->verifyRole('viewOrder'); if (!$id) { throw new \CodeIgniter\Exceptions\PageNotFoundException(); } $Orders = new Orders(); $Company = new Company(); $OrderItems = new OrderItems(); $order = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($id); $company = $Company->getCompanyData(1); $today = date('d/m/Y'); // ✅ RÉCUPÉRER LE TYPE DE DOCUMENT $documentType = $order['document_type'] ?? 'bl'; $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 = 'BON DE LIVRAISON'; } // ✅ 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; } } $discount = (float) $order['discount']; $grossAmount = (float) $order['gross_amount']; $totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalHT = $totalTTC / 1.20; $tva = $totalTTC - $totalHT; $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; $html = ' '.$documentTitle.' '.$order['bill_no'].'

'.esc($company['company_name']).'

NIF : '.esc($company['NIF']).'

STAT : '.esc($company['STAT']).'

Contact : '.esc($company['phone']).' | '.esc($company['phone2']).'

Logo

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

Client : '.esc($order['customer_name']).'

Adresse : '.esc($order['customer_address']).'

Téléphone : '.esc($order['customer_phone']).'

CIN : '.esc($order['customer_cin']).'

Antananarivo, le '.$today.'

'; // ✅ TABLEAU ADAPTÉ SELON LE TYPE if ($isAvanceMere) { $html .= ' '; foreach ($items as $item) { $details = $this->getOrderItemDetails($item); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; $html .= ' '; } $html .= '
Produit Prix Unitaire (Ar)
'.esc($details['product_name']); if (!empty($details['commentaire'])) { $html .= '
Remarque : '.esc($details['commentaire']).''; } $html .= '
'.number_format($prixAffiche, 0, '', ' ').'
'; } else { $html .= ' '; $Products = new Products(); $Brand = new Brands(); $Category = new Category(); foreach ($items as $item) { $details = $this->getOrderItemDetails($item); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; $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 && isset($categoryData['name'])) { $categoryName = $categoryData['name']; } } } $html .= ' '; } $html .= '
Nom Marque Catégorie N° Moteur Châssis Puissance (CC) Prix Unitaire (Ar)
'.esc($details['product_name']).' '.esc($details['marque']).' '.esc($categoryName).' '.esc($details['numero_moteur']).' '.esc($details['numero_chassis']).' '.esc($details['puissance']).' '.number_format($prixAffiche, 0, '', ' ').'
'; } $html .= ' '; if (!empty($order['order_payment_mode'])) { $html .= ' '; } if (!empty($order['tranche_1'])) { $html .= ' '; } if (!empty($order['tranche_2'])) { $html .= ' '; } $html .= '
Total HT : '.number_format($totalHT, 0, '', ' ').' Ar
TVA (20%) : '.number_format($tva, 0, '', ' ').' Ar
Total TTC : '.number_format($totalTTC, 0, '', ' ').' Ar
Statut : '.$paidLabel.'
Mode de paiement : '.esc($order['order_payment_mode']).'
Tranche 1 : '.number_format((float)$order['tranche_1'], 0, '', ' ').' Ar
Tranche 2 : '.number_format((float)$order['tranche_2'], 0, '', ' ').' Ar
L\'acheteur

__________________
Le vendeur

__________________

Conditions Générales

Logo
L\'Acheteur
'; return $this->response->setBody($html); } // ==================================== // PRINT31 - Facture + Bon de commande (pages séparées) // ==================================== public function print31(int $id) { $this->verifyRole('viewOrder'); if (!$id) { return $this->response->setStatusCode(400, 'ID manquant'); } $Orders = new Orders(); $Company = new Company(); $OrderItems = new OrderItems(); $Products = new Products(); $Brand = new Brands(); $Category = new Category(); $order_data = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($id); $company_info = $Company->getCompanyData(1); // ✅ RÉCUPÉRER LE TYPE DE DOCUMENT $documentType = $order_data['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) { if (!empty($item['product_name']) && empty($item['product_id'])) { $isAvanceMere = true; break; } } $paid_status = $order_data['paid_status'] === 1 ? "Payé" : "Refusé"; $discount = (float) $order_data['discount']; $grossAmount = (float) $order_data['gross_amount']; $totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalHT = $totalTTC / 1.20; $tva = $totalTTC - $totalHT; $style = ' '; // --- FACTURES : Une par produit --- foreach ($items as $item) { $details = $this->getOrderItemDetails($item); if (!$details) continue; $unitPrice = $details['prix']; $prixAffiche = ($discount > 0 && $isAvanceMere) ? $discount : $unitPrice; echo ''; echo ''; echo ''; echo ''; echo $style; echo ''; echo '
'; echo '
'; echo '
'; echo '

' . esc($company_info['company_name']) . '

'; echo '

NIF : ' . esc($company_info['NIF']) . '

'; echo '

STAT : ' . esc($company_info['STAT']) . '

'; echo '

Contact : ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '

'; echo '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

'; echo '
'; echo '
'; echo 'Logo'; echo '

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

'; echo '

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

'; echo '
'; echo '
'; echo '
'; echo '

DOIT : ' . esc($order_data['customer_name']) . '

'; echo '

Adresse : ' . esc($order_data['customer_address']) . '

'; echo '

Téléphone : ' . esc($order_data['customer_phone']) . '

'; echo '

CIN : ' . esc($order_data['customer_cin']) . '

'; echo '
'; // ✅ TABLEAU ADAPTÉ SELON LE TYPE if ($isAvanceMere) { // TABLE SIMPLIFIÉE POUR AVANCE "SUR MER" echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; if (!empty($details['commentaire'])) { echo ''; } echo '
DésignationProduit à compléterPrix (Ar)
' . esc($details['product_name']) . '                    ' . number_format($prixAffiche, 0, '', ' ') . '
Remarque : ' . esc($details['commentaire']) . '
'; } else { // TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE $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 && isset($categoryData['name'])) { $categoryName = $categoryData['name']; } } } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
NomMarqueCatégorieN° MoteurChâssisPuissance (CC)Prix Unit. (Ar)
' . esc($details['product_name']) . '' . esc($details['marque']) . '' . esc($categoryName) . '' . esc($details['numero_moteur']) . '' . esc($details['numero_chassis']) . '' . esc($details['puissance']) . '' . number_format($prixAffiche, 0, '', ' ') . '
'; } // Récapitulatif pour cette facture $itemHT = $prixAffiche / 1.20; $itemTVA = $prixAffiche - $itemHT; echo ''; echo ''; echo ''; echo ''; echo ''; if (!empty($order_data['order_payment_mode'])) { echo ''; } if (!empty($order_data['tranche_1'])) { echo ''; } if (!empty($order_data['tranche_2'])) { echo ''; } echo '
Total HT :' . number_format($itemHT, 0, '', ' ') . ' Ar
TVA (20%) :' . number_format($itemTVA, 0, '', ' ') . ' Ar
Total TTC :' . number_format($prixAffiche, 0, '', ' ') . ' Ar
Statut :' . $paid_status . '
Mode de paiement :' . esc($order_data['order_payment_mode']) . '
Tranche 1 :' . number_format((float)$order_data['tranche_1'], 0, '', ' ') . ' Ar
Tranche 2 :' . number_format((float)$order_data['tranche_2'], 0, '', ' ') . ' Ar
'; echo '
'; echo '
L\'Acheteur

__________________
'; echo '
Le Vendeur

__________________
'; echo '
'; echo '
'; // fin page-break echo ''; } // --- BON DE LIVRAISON (UNE SEULE PAGE) --- echo ''; echo ''; echo ''; echo ''; echo $style; echo ''; echo '
'; echo '
'; echo '

' . esc($company_info['company_name']) . '

'; echo '

NIF : ' . esc($company_info['NIF']) . '

'; echo '

STAT : ' . esc($company_info['STAT']) . '

'; echo '

Contact : ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '

'; echo '
'; echo '
'; echo 'Logo'; echo '

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

'; echo '
'; echo '
'; echo '
'; echo '

Client : ' . esc($order_data['customer_name']) . '

'; echo '

Adresse : ' . esc($order_data['customer_address']) . '

'; echo '

Téléphone : ' . esc($order_data['customer_phone']) . '

'; echo '

CIN : ' . esc($order_data['customer_cin']) . '

'; echo '
'; // ✅ TABLEAU RÉCAPITULATIF SELON LE TYPE if ($isAvanceMere) { echo ''; echo ''; echo ''; echo ''; echo ''; foreach ($items as $item) { $details = $this->getOrderItemDetails($item); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
DésignationProduit à compléterPrix (Ar)
' . esc($details['product_name']) . '                    ' . number_format($prixAffiche, 0, '', ' ') . '
'; } else { echo ''; echo ''; echo ''; foreach ($items as $item) { $details = $this->getOrderItemDetails($item); if (!$details) continue; $prixAffiche = ($discount > 0) ? $discount : $details['prix']; $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 ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
NomMarqueCatégorieN° MoteurChâssisPuissancePrix (Ar)
' . esc($details['product_name']) . '' . esc($details['marque']) . '' . esc($categoryName) . '' . esc($details['numero_moteur']) . '' . esc($details['numero_chassis']) . '' . esc($details['puissance']) . '' . number_format($prixAffiche, 0, '', ' ') . '
'; } echo ''; echo ''; echo ''; echo ''; echo ''; echo '
Total HT :' . number_format($totalHT, 0, '', ' ') . ' Ar
TVA :' . number_format($tva, 0, '', ' ') . ' Ar
Réduction :' . number_format($discount, 0, '', ' ') . ' Ar
Total TTC :' . number_format($totalTTC, 0, '', ' ') . ' Ar
'; echo '
'; echo '
L\'Acheteur

__________________
'; echo '
Le Vendeur

__________________
'; echo '
'; // --- CONDITIONS GÉNÉRALES --- echo '
'; echo '
'; echo '

Conditions Générales

'; echo 'Logo'; echo '
'; echo ''; echo '
L\'Acheteur
'; echo '
'; echo ''; } }