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