motorbike/app/Controllers/OrderController.php
Stephane 3c7585b3a2 feat: afficher les infos complètes du moto dans les notifications de remise
- Remise.php : ajout méthode getFullProductInfoByDemandeId() avec JOIN orders_item/products/brands
- RemiseController.php : utilisation des infos complètes (modèle, marque, N° série, N° moteur, châssis, puissance) dans les notifications de validation/refus
- OrderController.php : enrichissement du message de notification lors de la création d'une demande de remise
- header_menu.php : refonte complète du design des notifications (cartes colorées par type, badge, horloge relative, point non-lu)
2026-03-05 12:57:13 +03:00

3186 lines
127 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controllers;
use DateTime;
use Mpdf\Mpdf;
use App\Models\Brands;
use App\Models\Caisse;
use App\Models\Orders;
use App\Models\Stores;
use App\Models\Company;
use App\Models\Category;
use App\Models\Products;
use App\Models\Attributes;
use App\Models\OrderItems;
use App\Models\Remise;
use App\Models\FourchettePrix;
use PhpParser\Node\Stmt\Else_;
class OrderController extends AdminController
{
public function __construct()
{
parent::__construct();
}
private $pageTitle = 'Orders';
public function index()
{
$this->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 = '<span class="label label-danger"><i class="fa fa-ban"></i> Accès bloqué</span>';
} 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 .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 2 : Commande en attente (2) SANS remise → Afficher imprimer
elseif ($value['paid_status'] == 2 && $discount == 0) {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 3 : Commande en attente (2) AVEC remise validée → Afficher imprimer
elseif ($value['paid_status'] == 2 && $Remise->hasRemiseValidatedForOrder($value['id'])) {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 4 : Commande en attente (2) AVEC remise en attente → Indicateur
elseif ($value['paid_status'] == 2 && $Remise->hasRemisePendingForOrder($value['id'])) {
$buttons .= '<button class="btn btn-warning btn-sm" disabled title="En attente de validation"><i class="fa fa-clock-o"></i></button>';
}
}
// ✅ 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 .= '
<a
href="#"
data-order-id="' . $value['id'] . '"
class="btn btn-default btn-view"
data-toggle="tooltip"
title="Voir">
<i class="fa fa-eye"></i>
</a>';
}
} else {
// Payé ou Livré : toujours afficher
$buttons .= '
<a
href="#"
data-order-id="' . $value['id'] . '"
class="btn btn-default btn-view"
data-toggle="tooltip"
title="Voir">
<i class="fa fa-eye"></i>
</a>';
}
}
// ✅ 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 .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>';
}
// CAS 2 : Remise validée → Afficher le bouton modifier
elseif ($Remise->hasRemiseValidatedForOrder($value['id'])) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>';
}
}
}
// Statut de paiement
if ($value['paid_status'] == 1) {
$paid_status = '<span class="label label-success">Payé</span>';
} elseif ($value['paid_status'] == 2) {
$paid_status = '<span class="label label-warning">En Attente</span>';
} elseif ($value['paid_status'] == 3) {
$paid_status = '<span class="label label-info">Payé et Livré</span>';
} else {
$paid_status = '<span class="label label-danger">Refusé</span>';
}
// 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 = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
} else {
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
}
}
$result['data'][$key] = [
$value['product_names'],
$value['user_name'],
$date_time . " <br >" . $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(in_array($users['group_name'], ["Direction", "DAF", "SuperAdmin", "Administrator"])){
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 .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente)
if (in_array('updateOrder', $this->permission) && 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 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">payé</span>';
} elseif ($value['paid_status'] == 2) {
$paid_status = '<span class="label label-warning">En Attente</span>';
} elseif ($value['paid_status'] == 3) {
$paid_status = '<span class="label label-info">Payé et Livré</span>';
} else {
$paid_status = '<span class="label label-danger">Refusé</span>';
}
// 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 = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
} else {
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
}
}
$result['data'][$key] = [
$value['bill_no'],
$value['customer_name'],
$value['customer_phone'],
$date_time . " <br >" . $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 .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// ✅ Bouton modifier pour statuts 0 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
href="#"
data-order-id="' . $value['id'] . '"
class="btn btn-default btn-view"
data-toggle="tooltip"
title="Voir">
<i class="fa fa-eye"></i>
</a>';
}
// ✅ 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">Payé</span>';
} elseif ($value['paid_status'] == 2) {
$paid_status = '<span class="label label-warning">En Attente</span>';
} elseif ($value['paid_status'] == 3) {
$paid_status = '<span class="label label-info">Payé et Livré</span>';
} else {
$paid_status = '<span class="label label-danger">Refusé</span>';
}
// 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 = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
} else {
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
}
}
// ✅ 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 . " <br >" . $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 . " <br >" . $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'];
$bill_no = $this->generateSimpleSequentialBillNo($users['store_id']);
$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[]');
$quantities = $this->request->getPost('qty[]'); // ✅ RÉCUPÉRER LES QUANTITÉS
$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é."
]);
}
}
}
}
$tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0;
$tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0;
if ($tranche_1 > 0 && $tranche_2 > 0) {
$total_tranches = $tranche_1 + $tranche_2;
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,
'qty' => $quantities, // ✅ AJOUTER LES QUANTITÉS
'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) {
// ✅ MARQUER LES PRODUITS COMME VENDUS (SEULEMENT SI QTY = 1)
$productModel = new Products();
foreach ($posts as $index => $product_id) {
$qty = isset($quantities[$index]) ? (int)$quantities[$index] : 1;
if ($qty == 1) {
// Vente unitaire : marquer comme vendu
$productModel->update($product_id, ['product_sold' => 1]);
}
// Si qty > 1, vous devriez décrémenter un stock si vous en avez un
}
session()->setFlashdata('success', 'Créé avec succès');
$Notification = new NotificationController();
$Stores = new Stores();
if ($discount > 0) {
// Demande de remise
$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 = [];
$product_info_lines = [];
foreach ($product_data_results as $product) {
if (isset($product['sku'], $product['price'])) {
$product_lines[] = $product['sku'] . ':' . $product['price'];
}
$product_info_lines[] = "" . ($product['name'] ?? '-') .
" | N° Série : " . ($product['sku'] ?? '-') .
" | N° Moteur : " . ($product['numero_de_moteur'] ?? '-') .
" | Châssis : " . ($product['chasis'] ?? '-');
}
$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);
$allStores = $Stores->getActiveStore();
$montantFormatted = number_format($discount, 0, ',', ' ');
$message = "💰 Nouvelle demande de remise : {$montantFormatted} Ar<br>" .
"Commande : {$bill_no}<br>" .
"Store : " . $this->returnStore($users['store_id']) . "<br>" .
"Demandeur : {$users['firstname']} {$users['lastname']}<br>" .
implode("<br>", $product_info_lines);
if (is_array($allStores) && count($allStores) > 0) {
foreach ($allStores as $store) {
$Notification->createNotification($message, "SuperAdmin", (int)$store['id'], 'remise/');
$Notification->createNotification($message . "<br><em>Pour information</em>", "Direction", (int)$store['id'], 'remise/');
$Notification->createNotification($message . "<br><em>Pour information</em>", "DAF", (int)$store['id'], 'remise/');
}
}
} else {
// Commande sans remise
$Notification->createNotification(
"📦 Nouvelle commande à valider : {$bill_no}<br>" .
"Client : {$data['customer_name']}<br>" .
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar",
"Caissière",
(int)$users['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);
}
// ✅ MISE À JOUR : Enregistrer delivered_by et delivered_at
$updateData = [
'paid_status' => 3,
'delivered_by' => $users['id'], // ← NOUVEAU
'delivered_at' => date('Y-m-d H:i:s') // ← NOUVEAU
];
$updated = $Orders->update((int)$order_id, $updateData);
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']}<br>" .
"Store : " . $this->returnStore($current_order['store_id']) . "<br>" .
"Client : {$current_order['customer_name']}<br>" .
"Livrée par : {$users['firstname']} {$users['lastname']}<br>" .
"Date livraison : " . date('d/m/Y H:i');
// ✅ 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);
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',
'customer_type' => 'required',
'source' => 'required'
]);
$validationData = [
'product' => $this->request->getPost('product'),
'customer_type' => $this->request->getPost('customer_type'),
'source' => $this->request->getPost('source')
];
$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;
}
// ✅ RÉCUPÉRER LES QUANTITÉS
$posts = $this->request->getPost('product');
$rates = $this->request->getPost('rate_value');
$amounts = $this->request->getPost('amount_value');
$puissances = $this->request->getPost('puissance');
$quantities = $this->request->getPost('qty'); // ✅ NOUVEAU
$gross_amount = $this->calculGross($amounts);
$net_amount = $gross_amount - (float)$discount;
// ✅ Vérification prix minimal SI rabais existe
if ((float)$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 ((float)$discount < $prixMinimal) {
$prixMinimalFormatted = number_format($prixMinimal, 0, ',', ' ');
$discountFormatted = number_format($discount, 0, ',', ' ');
return redirect()->back()
->withInput()
->with('errors', [
"⚠️ Mise à jour bloquée : Le rabais de {$discountFormatted} Ar pour « {$productData['name']} » est trop élevé."
]);
}
}
}
}
$dataUpdate = [
'document_type' => $this->request->getPost('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'),
'gross_amount' => $gross_amount,
'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' => $net_amount,
'discount' => (float)$discount,
'paid_status' => $paid_status,
'product' => $posts,
'product_sold' => true,
'rate_value' => $rates,
'amount_value' => $amounts,
'puissance' => $puissances,
'qty' => $quantities, // ✅ AJOUTER LES QUANTITÉS
'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)) {
// ✅ GÉRER LE STATUT product_sold SELON LA QUANTITÉ
$productModel = new Products();
foreach ($posts as $index => $product_id) {
$qty = isset($quantities[$index]) ? (int)$quantities[$index] : 1;
if ($qty == 1) {
// Vente unitaire : marquer comme vendu
$productModel->update($product_id, ['product_sold' => 1]);
} else {
// Vente multiple : ne pas marquer comme vendu (géré par stock si applicable)
$productModel->update($product_id, ['product_sold' => 0]);
}
}
$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}<br>" .
"Store : " . $this->returnStore($user['store_id']) . "<br>" .
"Client : {$customer_name}<br>" .
"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' => $amounts,
'id_store' => $user['store_id'],
'id_order' => $id,
'product' => $product_output,
'demande_status' => 'En attente'
];
$Remise = new Remise();
$Remise->updateRemise1($id, $data1);
$Stores = new Stores();
$allStores = $Stores->getActiveStore();
$montantFormatted = number_format($discount, 0, ',', ' ');
$message = "💰 Demande de remise mise à jour : {$montantFormatted} Ar<br>" .
"Commande : {$bill_no}<br>" .
"Store : " . $this->returnStore($user['store_id']) . "<br>" .
"Demandeur : {$user['firstname']} {$user['lastname']}";
if (is_array($allStores) && count($allStores) > 0) {
foreach ($allStores as $store) {
$Notification->createNotification($message, "SuperAdmin", (int)$store['id'], 'remise/');
$Notification->createNotification($message . "<br><em>Pour information</em>", "Direction", (int)$store['id'], 'remise/');
$Notification->createNotification($message . "<br><em>Pour information</em>", "DAF", (int)$store['id'], '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);
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 = "<span style='color: green; font-weight: bold;'>Payé</span>";
} elseif ($order_data['paid_status'] == 2) {
$paid_status = "<span style='color: orange; font-weight: bold;'>En Attente</span>";
} else {
$paid_status = "<span style='color: red; font-weight: bold;'>Refusé</span>";
}
// Génération du HTML
$html .= '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
<style>
body { font-size: 14px; font-family: Arial, sans-serif; }
.invoice-container {
max-width: 750px; /* Réduire la largeur du cadre */
margin: 20px auto;
padding: 20px;
border: 2px solid #007bff; /* Bordure plus visible */
border-radius: 10px;
background: #f0f8ff; /* Couleur de fond plus douce */
}
.invoice-header {
background: #007bff;
color: white;
text-align: center;
font-size: 18px;
font-weight: bold;
padding: 10px;
border-radius: 10px 10px 0 0;
}
.invoice-footer {
background: #007bff;
color: white;
text-align: center;
font-size: 14px;
padding: 10px;
border-radius: 0 0 10px 10px;
margin-top: 12px;
}
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #e9ecef; }
p, strong { color: #333; }
@media print {
body { font-size: 14px; font-family: Arial, sans-serif, margin: 1cm; }
@page { margin: 0; }
.invoice-container {
max-width: 750px;
margin: 20px auto;
padding: 20px;
border: 2px solid #007bff;
border-radius: 10px;
background: #f0f8ff;
}
.invoice-header {
background: #007bff !important;
color: white !important;
text-align: center;
font-size: 18px;
font-weight: bold;
padding: 10px;
border-radius: 10px 10px 0 0;
}
input {
border: none;
outline: none;
width: 100%;
font-size: 14px;
}
.invoice-footer {
background: #007bff !important;
color: white !important;
text-align: center;
font-size: 14px;
padding: 10px;
border-radius: 0 0 10px 10px;
margin-top: 12px;
}
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #e9ecef; }
p, strong { color: #333; } a
/* Supprimer l\'ombre et les bordures non nécessaires */
.invoice-container { box-shadow: none !important; border: none !important; }
/* Éviter que les couleurs soient supprimées à l\'impression */
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
</style>
</head>
<body onload="window.print();">
<div class="invoice-container">
<div class="invoice-header" style="background: #007bff;color: white;text-align: center;font-size: 18px;font-weight: bold;padding: 10px;border-radius: 10px 10px 0 0; ">
' . esc($company_info['company_name']) . '
</div>
<div style="display: flex;justify-content: space-around;margin-top: 3%;">
<div>
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
<p style="display: flex; gap: 10px;">
<strong>Contact :</strong>
<span>' . esc($company_info['phone']) . '</span>
<span>' . esc($company_info['phone2']) . '</span>
</p>
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
</div>
<div>
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
<br >
<br >
<p><strong>Antananarivo le</strong> ' . esc(date('d/m/Y')) . '</p>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Marque</th>
<th>Moteur</th>
<th>Puissance</th>
<th>Prix</th>
</tr>
</thead>
<tbody>';
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 .= '<tr>
<td>' . esc($product_data['sku']) . '</td>
<td>' . esc($product_data['numero_de_moteur']) . '</td>
<td><input type="text" value="' . esc($puissance_affichee) . '"></td> ✅
<td style="text-align:right;">' . number_format((float) $item['amount'], 2, '.', ' ') . '</td>
</tr>';
}
$html .= ' </tbody>
</table>
<table class="table">
<tr>
<th>Total:</th>
<td>' . number_format(((float) $order_data['gross_amount'] - ((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '</td>
</tr>
<tr>
<th>TVA:</th>
<td>' . number_format((((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '</td>
</tr>';
$html .= '<tr>
<th>Réduction:</th>
<td>' . number_format((float) $order_data['discount'], 2, '.', ' ') . '</td>
</tr>
<tr>
<th>Total à payer:</th>
<td><strong>' . number_format((float) ($order_data['net_amount']), 2, '.', ' ') . '</strong></td>
</tr>
<tr>
<th>Statut:</th>
<td>' . $paid_status . '</td>
</tr>';
// Vérification et ajout des informations de paiement
if (!empty($order_data['order_payment_mode'])) {
$html .= '<tr>
<th>Mode de paiement:</th>
<td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td>
</tr>';
}
if (!empty($order_data['tranche_1'])) {
$html .= '<tr>
<th>Tranche 1:</th>
<td><strong>' . number_format((float) $order_data['tranche_1'], 2, '.', ' ') . '</strong></td>
</tr>';
}
if (!empty($order_data['tranche_2'])) {
$html .= '<tr>
<th>Tranche 2:</th>
<td><strong>' . number_format((float) $order_data['tranche_2'], 2, '.', ' ') . '</strong></td>
</tr>';
}
$html .= '</table>
<div style="display: flex;align-items: center;justify-content: space-around;margin-bottom: 5%;">
<div>
<p style="font-weight:bold;">L\'acheteur</p>
</div>
<div>
<p style="font-weight:bold;">Le vendeur</p>
</div>
</div>
<div class="invoice-footer">
Merci pour votre achat !<br>
<strong style="color:white;">Original</strong>
</div>
</div>
</body>
</html>';
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)
? "<span style='color: green; font-weight: bold;'>Payé</span>"
: "<span style='color: red; font-weight: bold;'>Non payé</span>";
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 '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
<style>
body { font-size: 14px; font-family: Arial, sans-serif; margin: 0; padding: 0; }
.invoice-container {
max-width: 750px;
margin: 20px auto;
padding: 20px;
border: 2px solid #007bff;
border-radius: 10px;
background: #f0f8ff;
page-break-after: always;
}
.invoice-header {
background: #007bff !important;
color: white;
text-align: center;
font-size: 18px;
font-weight: bold;
padding: 10px;
border-radius: 10px 10px 0 0;
}
.invoice-footer {
background: #007bff !important;
color: white;
text-align: center;
font-size: 14px;
padding: 10px;
border-radius: 0 0 10px 10px;
margin-top: 12px;
}
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #e9ecef; }
input {
border: none;
outline: none;
width: 100%;
font-size: 14px;
}
@media print {
body { font-size: 14px; font-family: Arial, sans-serif, margin: 1cm; }
@page { margin: 0; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body, .invoice-container {
margin: 0;
padding: 0;
border: none;
box-shadow: none;
}
.invoice-container {
page-break-after: always;
}
}
</style>
</head>
<body onload="window.print()">
<div class="invoice-container">
<div class="invoice-header">' . esc($company_info['company_name']) . '</div>
<div style="display: flex; justify-content: space-around; margin-top: 3%;">
<div>
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
<p style="display: flex; gap: 10px;">
<strong>Contact :</strong>
<span>' . esc($company_info['phone']) . '</span>
<span>' . esc($company_info['phone2']) . '</span>
</p>
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
</div>
<div>
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
<br><br>
<p><strong>Antananarivo le</strong> ' . esc(date('d/m/Y')) . '</p>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>Marque</th>
<th>Moteur</th>
<th>Puissance</th>
<th>Prix</th>
</tr>
</thead>
<tbody>
<tr>
<td>' . esc($product_data['sku']) . '</td>
<td>' . esc($product_data['numero_de_moteur']) . '</td>
<td><input type="text" value="' . esc($puissance_affichee) . '" placeholder="Remplir ici"></td>
<td style="text-align:right;">' . number_format((float)$item['amount'], 2, '.', ' ') . '</td>
</tr>
</tbody>
</table>
<table class="table">
<tr>
<th>Total:</th>
<td>' . number_format($item['amount'] - ($item['amount'] * 0.2), 2, '.', ' ') . '</td>
</tr>
<tr>
<th>TVA:</th>
<td>' . number_format($item['amount'] * 0.2, 2, '.', ' ') . '</td>
</tr>
<tr>
<th>Réduction:</th>
<td>' . number_format($order_data['discount'], 2, '.', ' ') . '</td>
</tr>
<tr>
<th>Total à payer:</th>
<td><strong>' . number_format($item['amount'] - $order_data['discount'], 2, '.', ' ') . '</strong></td>
</tr>
<tr>
<th>Statut:</th>
<td>' . $paid_status . '</td>
</tr>';
if (!empty($order_data['order_payment_mode'])) {
echo '<tr><th>Mode de paiement:</th><td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td></tr>';
}
if (!empty($order_data['tranche_1'])) {
echo '<tr><th>Tranche 1:</th><td><strong>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . '</strong></td></tr>';
}
if (!empty($order_data['tranche_2'])) {
echo '<tr><th>Tranche 2:</th><td><strong>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . '</strong></td></tr>';
}
echo '</table>
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
<div><p style="font-weight:bold;">L\'acheteur</p></div>
<div><p style="font-weight:bold;">Le vendeur</p></div>
</div>
<div class="invoice-footer">
Merci pour votre achat !<br>
<strong style="color:white;">Original</strong>
</div>
</div>
</body>
</html>';
}
}
}
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
? "<span style='color: green; font-weight: bold;'>Payé</span>"
: "<span style='color: red; font-weight: bold;'>Refusé</span>";
// STYLE COMMUN
$style = '
<style>
body { font-family: Arial, sans-serif; font-size:14px; margin:0; padding:0; }
.invoice-container { max-width:750px; margin:20px auto; padding:20px;
border:2px solid #007bff; border-radius:10px; page-break-after:always; }
.invoice-header { background:#007bff !important; color:#fff; text-align:center;
font-size:18px; padding:10px; border-radius:10px 10px 0 0; }
.invoice-footer { background:#007bff !important; color:#fff; text-align:center;
font-size:14px; padding:10px; border-radius:0 0 10px 10px; margin-top:12px; }
.info { display:flex; justify-content:space-between; margin-top:15px; }
table { width:100%; border-collapse:collapse; margin-top:15px; }
th, td { border:1px solid #ddd; padding:8px; text-align:left; }
th { background:#e9ecef; }
input { border:none; outline:none; width:100%; font-size:14px; background:transparent; }
.summary { width: 80%; margin: 20px 0; margin-right: 0; }
.summary th, .summary td { border:none; padding:4px; text-align:right; }
.summary th { text-align:left; }
@media print {
body { font-size: 14px; font-family: Arial, sans-serif; margin: 1cm; }
@page { margin: 0; }
* { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}
</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 '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
' . $style . '
</head>
<body onload="window.print()">
<div class="invoice-container">
<div class="invoice-header">FACTURE</div>
<div class="info">
<div>
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
<p><strong>Contact :</strong> ' . esc($company_info['phone']) . ' / ' . esc($company_info['phone2']) . '</p>
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
</div>
<div>
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
</div>
</div>
<table>
<thead><tr><th>Marque</th><th>Moteur</th><th>Puissance (CC)</th><th>Prix unitaire</th></tr></thead>
<tbody>
<tr>
<td>' . esc($product_data['sku']) . '</td>
<td>' . esc($product_data['numero_de_moteur']) . '</td>
<td><input type="text" value="' . esc($puissance_affichee) . '" readonly></td>
<td>' . number_format($unitPrice, 2, '.', ' ') . '</td>
</tr>
</tbody>
</table>
<table class="table summary">
<tr><th>Total HT:</th><td>' . number_format($subtotal, 2, '.', ' ') . ' Ar</td></tr>
<tr><th>TVA (20%):</th><td>' . number_format($vatAmount, 2, '.', ' ') . ' Ar</td></tr>
<tr><th>Réduction:</th><td>' . number_format($discount, 2, '.', ' ') . ' Ar</td></tr>
<tr><th>Total à payer:</th><td><strong>' . number_format($totalNet, 2, '.', ' ') . ' Ar</strong></td></tr>
<tr><th>Statut:</th><td>' . $paid_status . '</td></tr>';
if (!empty($order_data['order_payment_mode'])) {
echo '<tr><th>Mode de paiement:</th><td>' . esc($order_data['order_payment_mode']) . '</td></tr>';
}
if (!empty($order_data['tranche_1'])) {
echo '<tr><th>Tranche 1:</th><td>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . ' Ar</td></tr>';
}
if (!empty($order_data['tranche_2'])) {
echo '<tr><th>Tranche 2:</th><td>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . ' Ar</td></tr>';
}
echo '</table>
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
<div><p style="font-weight:bold;">L\'acheteur</p></div>
<div><p style="font-weight:bold;">Le vendeur</p></div>
</div>
<div class="invoice-footer">
Merci pour votre achat !<br>
<strong style="color:white;">Original</strong>
</div>
</div>
</body>
</html>';
}
// --- BON DE COMMANDE : une seule fois après la boucle ---
echo '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
' . $style . '
</head>
<body onload="window.print()">
<div class="invoice-container">
<div class="invoice-header">BON DE COMMANDE</div>
<div class="info">
<div>
<p><strong>Commande ID :</strong> ' . esc($order_data['order_no'] ?? $order_data['bill_no']) . '</p>
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
</div>
<div>
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Marque</th>
<th>Moteur</th>
<th>Puissance (CC)</th>
<th>Prix</th>
</tr>
</thead>
<tbody>';
$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 '<tr>
<td>' . esc($product_data['sku']) . '</td>
<td>' . esc($product_data['numero_de_moteur']) . '</td>
<td><input type="text" value="' . esc($puissance_affichee) . '" style="border:none;width:100%;outline:none;"></td>
<td style="text-align:right;">' . number_format((float)$item['amount'], 2, '.', ' ') . '</td>
</tr>';
}
$tva = $total_ht * 0.2;
$total_ttc = $total_ht + $tva - $order_data['discount'];
echo '</tbody>
</table>
<table class="table summary">
<tr><th>Total HT:</th><td>' . number_format($total_ht, 2, '.', ' ') . ' Ar</td></tr>
<tr><th>TVA (20%):</th><td>' . number_format($tva, 2, '.', ' ') . ' Ar</td></tr>
<tr><th>Réduction:</th><td>' . number_format($order_data['discount'], 2, '.', ' ') . ' Ar</td></tr>
<tr><th>Total à payer:</th><td><strong>' . number_format($total_ttc, 2, '.', ' ') . ' Ar</strong></td></tr>
</table>
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
<div><p style="font-weight:bold;">L\'acheteur</p></div>
<div><p style="font-weight:bold;">Le vendeur</p></div>
</div>
<div class="invoice-footer">
Merci pour votre commande !<br>
<strong style="color:white;">Original</strong>
</div>
</div>
</body>
</html>';
}
// 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 = '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>'.$documentTitle.' '.$order['bill_no'].'</title>
<style>
/* ✅ FORMAT A4 PAYSAGE DIVISÉ EN 2 */
@page {
size: A4 landscape;
margin: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 10px;
color: #000;
margin: 0;
padding: 0;
}
/* ✅ CONTENEUR : 2 COLONNES CÔTE À CÔTE */
.page {
display: flex;
width: 297mm;
height: 210mm;
margin: 0;
padding: 0;
}
/* ✅ CHAQUE FACTURE = 50% DE LA LARGEUR */
.facture-box {
flex: 1;
width: 148.5mm;
padding: 10mm;
box-sizing: border-box;
border-right: 2px dashed #999;
}
.facture-box:last-child {
border-right: none;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.header .infos {
line-height: 1.4;
}
.header .infos h2 {
margin: 0;
font-size: 14px;
}
.header .infos p {
margin: 2px 0;
font-size: 9px;
}
.header img {
max-height: 60px;
}
.header p.facture-num {
margin: 5px 0;
font-weight: bold;
font-size: 10px;
}
.client {
margin-bottom: 15px;
}
.client p {
margin: 3px 0;
font-size: 9px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 15px;
font-size: 9px;
}
th, td {
border: 1px solid #000;
padding: 4px;
}
th {
background: #f0f0f0;
}
.right {
text-align: right;
}
.words-box {
border: 1px solid #000;
padding: 8px;
margin-bottom: 20px;
font-size: 9px;
}
.words-box strong {
font-size: 9px;
}
.signature {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.signature div {
text-align: center;
font-size: 9px;
}
/* ✅ CONDITIONS SUR PAGE SÉPARÉE (VERSO) */
.conditions-page {
page-break-before: always;
display: flex;
width: 297mm;
height: 210mm;
margin: 0;
padding: 0;
}
.conditions-box {
flex: 1;
width: 148.5mm;
padding: 15px;
box-sizing: border-box;
line-height: 1.5;
border-right: 2px dashed #999;
}
.conditions-box:last-child {
border-right: none;
}
.conditions-box h3 {
margin: 0 0 15px 0;
font-size: 12px;
}
.conditions-box ul {
margin: 0;
padding-left: 20px;
font-size: 9px;
}
.conditions-box li {
margin-bottom: 8px;
}
.conditions-box .buyer-signature {
text-align: center;
margin-top: 40px;
font-size: 10px;
}
.conditions-box img {
height: 50px;
}
@media print {
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body onload="window.print()">
<!-- ✅ PAGE 1 : RECTO - 2 DOCUMENTS CÔTE À CÔTE -->
<div class="page">';
// ✅ GÉNÉRER 2 DOCUMENTS IDENTIQUES
for ($i = 0; $i < 2; $i++) {
$html .= '
<div class="facture-box">
<div class="header">
<div class="infos">
<h2>'.esc($company['company_name']).'</h2>
<p><strong>NIF :</strong> '.esc($company['NIF']).'</p>
<p><strong>STAT :</strong> '.esc($company['STAT']).'</p>
<p><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p>
</div>
<div style="text-align:center;">
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
<p class="facture-num">'.$documentTitle.' N° '.esc($order['bill_no']).'</p>
</div>
</div>
<div class="client">
<p><strong>DOIT Nom :</strong> '.esc($order['customer_name']).'</p>
<p><strong>Adresse :</strong> '.esc($order['customer_address']).'</p>
<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>';
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
// Dans la boucle foreach ($items as $item)
if ($isAvanceMere) {
$html .= '
<table>
<thead>
<tr>
<th>Produit</th>
<th>Quantité</th> <!-- ✅ AJOUT -->
<th class="right">Prix unitaire (Ar)</th>
<th class="right">Montant (Ar)</th> <!-- ✅ AJOUT -->
</tr>
</thead>
<tbody>';
foreach ($items as $it) {
$details = $this->getOrderItemDetails($it);
if (!$details) continue;
$qty = isset($it['qty']) ? (int)$it['qty'] : 1; // ✅ RÉCUPÉRATION
$prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix'];
$prixTotal = $prixUnitaire * $qty;
$html .= '<tr><td>'.esc($details['product_name']);
if (!empty($details['commentaire'])) {
$html .= '<br><em style="font-size:8px; color:#666;">'.esc($details['commentaire']).'</em>';
}
$html .= '</td>
<td class="right">'.esc($qty).'</td> <!-- ✅ AFFICHAGE -->
<td class="right">'.number_format($prixUnitaire, 0, '', ' ').'</td>
<td class="right">'.number_format($prixTotal, 0, '', ' ').'</td>
</tr>';
}
$html .= '
</tbody>
</table>';
} else {
$html .= '
<table>
<thead>
<tr>
<th>MARQUE</th>
<th>Désignation</th>
<th>N° Moteur</th>
<th>N° Châssis</th>
<th>Puissance (CC)</th>
<th>Quantité</th> <!-- ✅ AJOUT -->
<th class="right">Prix Unit. (Ar)</th>
<th class="right">Montant (Ar)</th> <!-- ✅ AJOUT -->
</tr>
</thead>
<tbody>';
foreach ($items as $it) {
$details = $this->getOrderItemDetails($it);
if (!$details) continue;
$qty = isset($it['qty']) ? (int)$it['qty'] : 1; // ✅ RÉCUPÉRATION
$prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix'];
$prixTotal = $prixUnitaire * $qty;
$html .= '
<tr>
<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">'.esc($qty).'</td> <!-- ✅ AFFICHAGE -->
<td class="right">'.number_format($prixUnitaire, 0, '', ' ').'</td>
<td class="right">'.number_format($prixTotal, 0, '', ' ').'</td>
</tr>';
}
$html .= '
</tbody>
</table>';
}
$html .= '
<table>
<tr>
<td><strong>Prix (HT) :</strong></td>
<td class="right">'.number_format($totalHT, 0, '', ' ').' Ar</td>
</tr>
<tr>
<td><strong>TVA (20%) :</strong></td>
<td class="right">'.number_format($tva, 0, '', ' ').' Ar</td>
</tr>
<tr>
<td><strong>Total (TTC) :</strong></td>
<td class="right">'.number_format($totalTTC, 0, '', ' ').' Ar</td>
</tr>
</table>
<div class="words-box">
<strong>Arrêté à la somme de :</strong><br>
'.$inWords.'
</div>
<div class="signature">
<div>L\'Acheteur<br><br>__________________</div>
<div>Le Vendeur<br><br>__________________</div>
</div>
</div>';
}
$html .= '
</div>
<!-- ✅ PAGE 2 : VERSO - 2 CONDITIONS GÉNÉRALES CÔTE À CÔTE -->
<div class="conditions-page">';
// ✅ GÉNÉRER 2 CONDITIONS IDENTIQUES
for ($i = 0; $i < 2; $i++) {
$html .= '
<div class="conditions-box">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3>Conditions Générales</h3>
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
</div>
<ul>
<li>Aucun accessoire (casque, rétroviseur, batterie, etc.) n\'est inclus avec la moto. Si le client en a besoin, il doit les acheter séparément.</li>
<li>Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.</li>
<li>Aucun service après-vente n\'est fourni.</li>
<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</li>
<li>La facture étant un document provisoire ne peut se substituer au certificat modèle (si requis) délivré au client au moment de l\'achat. Il appartient à ce dernier de procéder à l\'immatriculation dans le délai prévu par la loi.</li>
</ul>
<div class="buyer-signature">L\'Acheteur</div>
</div>';
}
$html .= '
</div>
</body>
</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;
// 7079 et 9099
if ($d === 7 || $d === 9) {
$base = $d === 7 ? 60 : 80;
return $tens[$d] . ($r ? '-' . $units[$n - $base] : '');
}
// 2069 ou 8089
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 = '<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>'.$documentTitle.' '.$order['bill_no'].'</title>
<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:20px; }
.header .infos { line-height:1.4; }
.header img { max-height:80px; }
.client { margin:20px; }
table { width:calc(100% - 40px); margin:0 20px 20px; border-collapse:collapse; }
th, td { border:1px solid #000; padding:8px; }
th { background:#f0f0f0; }
.right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin:50px 20px 20px; }
.signature div { text-align:center; }
.footer { text-align:center; margin:20px; font-size:12px; color:#666; }
.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>
<body onload="window.print()">
<div class="header">
<div class="infos">
<h2 style="margin:0;">'.esc($company['company_name']).'</h2>
<p style="margin:2px 0;"><strong>NIF :</strong> '.esc($company['NIF']).'</p>
<p style="margin:2px 0;"><strong>STAT :</strong> '.esc($company['STAT']).'</p>
<p style="margin:2px 0;"><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p>
</div>
<div style="text-align:center;">
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
<p style="margin:5px 0; font-weight:bold;">'.$documentTitle.' N° '.esc($order['bill_no']).'</p>
</div>
</div>
<div class="client">
<p><strong>Client :</strong> '.esc($order['customer_name']).'</p>
<p><strong>Adresse :</strong> '.esc($order['customer_address']).'</p>
<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>';
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
if ($isAvanceMere) {
$html .= '
<table>
<thead>
<tr>
<th>Produit</th>
<th>Quantité</th> <!-- ✅ AJOUT -->
<th class="right">Prix Unitaire (Ar)</th>
<th class="right">Montant (Ar)</th> <!-- ✅ AJOUT -->
</tr>
</thead>
<tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$qty = isset($item['qty']) ? (int)$item['qty'] : 1; // ✅ RÉCUPÉRATION
$prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix'];
$prixTotal = $prixUnitaire * $qty;
$html .= '
<tr>
<td>'.esc($details['product_name']);
if (!empty($details['commentaire'])) {
$html .= '<br><em style="font-size:12px; color:#666;">Remarque : '.esc($details['commentaire']).'</em>';
}
$html .= '</td>
<td class="right">'.esc($qty).'</td> <!-- ✅ AFFICHAGE -->
<td class="right">'.number_format($prixUnitaire, 0, '', ' ').'</td>
<td class="right">'.number_format($prixTotal, 0, '', ' ').'</td>
</tr>';
}
$html .= '
</tbody>
</table>';
} else {
$html .= '
<table>
<thead>
<tr>
<th>Nom</th>
<th>Marque</th>
<th>Catégorie</th>
<th>N° Moteur</th>
<th>Châssis</th>
<th>Puissance (CC)</th>
<th>Quantité</th> <!-- ✅ AJOUT -->
<th class="right">Prix Unit. (Ar)</th>
<th class="right">Montant (Ar)</th> <!-- ✅ AJOUT -->
</tr>
</thead>
<tbody>';
$Products = new Products();
$Brand = new Brands();
$Category = new Category();
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$qty = isset($item['qty']) ? (int)$item['qty'] : 1; // ✅ RÉCUPÉRATION
$prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix'];
$prixTotal = $prixUnitaire * $qty;
$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($details['product_name']).'</td>
<td>'.esc($details['marque']).'</td>
<td>'.esc($categoryName).'</td>
<td>'.esc($details['numero_moteur']).'</td>
<td>'.esc($details['numero_chassis']).'</td>
<td>'.esc($details['puissance']).'</td>
<td class="right">'.esc($qty).'</td> <!-- ✅ AFFICHAGE -->
<td class="right">'.number_format($prixUnitaire, 0, '', ' ').'</td>
<td class="right">'.number_format($prixTotal, 0, '', ' ').'</td>
</tr>';
}
$html .= '
</tbody>
</table>';
}
$html .= '
<table style="width:calc(100% - 40px); margin:0 20px 20px;">
<tr>
<td><strong>Total HT :</strong></td>
<td class="right">'.number_format($totalHT, 0, '', ' ').' Ar</td>
</tr>
<tr>
<td><strong>TVA (20%) :</strong></td>
<td class="right">'.number_format($tva, 0, '', ' ').' Ar</td>
</tr>
<tr>
<td><strong>Total TTC :</strong></td>
<td class="right">'.number_format($totalTTC, 0, '', ' ').' Ar</td>
</tr>
<tr>
<td><strong>Statut :</strong></td>
<td class="right">'.$paidLabel.'</td>
</tr>';
if (!empty($order['order_payment_mode'])) {
$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>
<div class="signature">
<div>L\'acheteur<br><br>__________________</div>
<div>Le vendeur<br><br>__________________</div>
</div>
<div class="footer">
Merci pour votre confiance
</div>
<div class="conditions">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0;">Conditions Générales</h3>
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo" style="height:60px;">
</div>
<ul>
<li>Aucun accessoire (casque, rétroviseur, batterie, etc.) n\'est inclus avec la moto. Si le client en a besoin, il doit les acheter séparément.</li>
<li>Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.</li>
<li>Aucun service après-vente n\'est fourni.</li>
<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</li>
<li>La facture étant un document provisoire ne peut se substituer au certificat modèle (si requis) délivré au client au moment de l\'achat. Il appartient à ce dernier de procéder à l\'immatriculation dans le délai prévu par la loi.</li>
</ul>
<div style="text-align:center; margin-top:50px;">L\'Acheteur</div>
</div>
</body>
</html>';
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
? "<span style='color: green; font-weight: bold;'>Payé</span>"
: "<span style='color: red; font-weight: bold;'>Refusé</span>";
$discount = (float) $order_data['discount'];
$grossAmount = (float) $order_data['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT;
$style = '
<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; }
.center { text-align:center; }
.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>';
// --- FACTURES : Une par produit ---
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
// ✅ RÉCUPÉRATION DE LA QUANTITÉ
$qty = isset($item['qty']) ? (int)$item['qty'] : 1;
$unitPrice = $details['prix'];
// Calcul du prix selon la quantité et la remise
if ($discount > 0 && $isAvanceMere) {
$prixTotal = $discount;
$prixUnitaire = $discount / $qty;
} else {
$prixUnitaire = $unitPrice / $qty;
$prixTotal = $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;
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;">' . $documentTitle . ' 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) {
echo '<table><thead><tr>';
echo '<th>Désignation</th>';
echo '<th>Produit à compléter</th>';
echo '<th class="center">Quantité</th>';
echo '<th class="right">Prix Unit. (Ar)</th>';
echo '<th class="right">Montant (Ar)</th>';
echo '</tr></thead><tbody>';
echo '<tr>';
echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td><span class="to-fill">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td>';
echo '<td class="center">' . esc($qty) . '</td>';
echo '<td class="right">' . number_format($prixUnitaire, 0, '', ' ') . '</td>';
echo '<td class="right">' . number_format($prixTotal, 0, '', ' ') . '</td>';
echo '</tr>';
if (!empty($details['commentaire'])) {
echo '<tr><td colspan="5"><em style="font-size:12px; color:#666;">Remarque : ' . esc($details['commentaire']) . '</em></td></tr>';
}
echo '</tbody></table>';
} 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 '<table><thead><tr>';
echo '<th>Nom</th>';
echo '<th>Marque</th>';
echo '<th>Catégorie</th>';
echo '<th>N° Moteur</th>';
echo '<th>Châssis</th>';
echo '<th>Puissance (CC)</th>';
echo '<th class="center">Qté</th>';
echo '<th class="right">Prix Unit. (Ar)</th>';
echo '<th class="right">Montant (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="center">' . esc($qty) . '</td>';
echo '<td class="right">' . number_format($prixUnitaire, 0, '', ' ') . '</td>';
echo '<td class="right">' . number_format($prixTotal, 0, '', ' ') . '</td>';
echo '</tr>';
echo '</tbody></table>';
}
// Récapitulatif pour cette facture
$itemHT = $prixTotal / 1.20;
$itemTVA = $prixTotal - $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($prixTotal, 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 LIVRAISON (UNE SEULE PAGE) ---
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;
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 '</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;">' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '</p>';
echo '</div>';
echo '</div>';
echo '<div class="client">';
echo '<p><strong>Client :</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 RÉCAPITULATIF SELON LE TYPE
if ($isAvanceMere) {
echo '<table><thead><tr>';
echo '<th>Désignation</th>';
echo '<th>Produit à compléter</th>';
echo '<th class="center">Quantité</th>';
echo '<th class="right">Prix Unit. (Ar)</th>';
echo '<th class="right">Montant (Ar)</th>';
echo '</tr></thead><tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$qty = isset($item['qty']) ? (int)$item['qty'] : 1;
if ($discount > 0) {
$prixTotal = $discount;
$prixUnitaire = $discount / $qty;
} else {
$prixUnitaire = $details['prix'] / $qty;
$prixTotal = $details['prix'];
}
echo '<tr>';
echo '<td>' . esc($details['product_name']) . '</td>';
echo '<td><span class="to-fill">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td>';
echo '<td class="center">' . esc($qty) . '</td>';
echo '<td class="right">' . number_format($prixUnitaire, 0, '', ' ') . '</td>';
echo '<td class="right">' . number_format($prixTotal, 0, '', ' ') . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<table><thead><tr>';
echo '<th>Nom</th>';
echo '<th>Marque</th>';
echo '<th>Catégorie</th>';
echo '<th>N° Moteur</th>';
echo '<th>Châssis</th>';
echo '<th>Puissance</th>';
echo '<th class="center">Qté</th>';
echo '<th class="right">Prix Unit. (Ar)</th>';
echo '<th class="right">Montant (Ar)</th>';
echo '</tr></thead><tbody>';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$qty = isset($item['qty']) ? (int)$item['qty'] : 1;
if ($discount > 0) {
$prixTotal = $discount;
$prixUnitaire = $discount / $qty;
} else {
$prixUnitaire = $details['prix'] / $qty;
$prixTotal = $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 '<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="center">' . esc($qty) . '</td>';
echo '<td class="right">' . number_format($prixUnitaire, 0, '', ' ') . '</td>';
echo '<td class="right">' . number_format($prixTotal, 0, '', ' ') . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
echo '<table>';
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($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">';
echo '<div>L\'Acheteur<br><br>__________________</div>';
echo '<div>Le Vendeur<br><br>__________________</div>';
echo '</div>';
// --- CONDITIONS GÉNÉRALES ---
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;">';
echo '</div>';
echo '<ul>';
echo '<li>Aucun accessoire (casque, rétroviseur, batterie, etc.) n\'est inclus avec la moto. Si le client en a besoin, il doit les acheter séparément.</li>';
echo '<li>Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.</li>';
echo '<li>Aucun service après-vente n\'est fourni.</li>';
echo '<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</li>';
echo '<li>La facture étant un document provisoire ne peut se substituer au certificat modèle (si requis) délivré au client au moment de l\'achat. Il appartient à ce dernier de procéder à l\'immatriculation dans le délai prévu par la loi.</li>';
echo '</ul>';
echo '<div style="text-align:center; margin-top:50px;">L\'Acheteur</div>';
echo '</div>';
echo '</body></html>';
}
}