<?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 generateBillNo(int $store_id): string
{
// Mapping des préfixes par magasin
$storePrefixes = [
1 => 'ANTS', // ANTSAKAVIRO
2 => 'BESA', // BESARETY
3 => 'BYPA', // BYPASS
4 => 'TOAM', // TOAMASINA
];
// Récupérer le préfixe du magasin, ou utiliser un préfixe par défaut
$prefix = $storePrefixes[$store_id] ?? 'BILPR';
// Générer un identifiant unique
$uniqueId = strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 6));
// Retourner le numéro de facture formaté
return $prefix . '-' . $uniqueId;
}
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") {
foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
$buttons = '';
// Bouton imprimer (sauf pour SECURITE)
if (in_array('viewOrder', $this->permission) & & $users['group_name'] != "SECURITE" & & $users['group_name'] != "COMMERCIALE") {
$buttons .= '< a target = "_blank" href = "' . site_url('orders/printDiv/' . $value['id']) . '" class = "btn btn-default" > < i class = "fa fa-print" > < / i > < / a > ';
}
// Bouton voir
if (in_array('viewOrder', $this->permission)) {
$buttons .= '
< 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 pour statuts 0 (Refusé) et 2 (En Attente)
if (in_array('updateOrder', $this->permission)
& & $users["store_id"] == $value['store_id']
& & in_array($value['paid_status'], [0, 2])) {
$buttons .= ' < a href = "' . site_url('orders/update/' . $value['id']) . '" class = "btn btn-primary" > < i class = "fa fa-pencil" > < / i > < / a > ';
}
// Statut de paiement
if ($value['paid_status'] == 1) {
$paid_status = '< span class = "label label-success" > Validé< / 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" > Validé 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($users['group_name'] == "Direction" || $users['group_name'] == "DAF"){
foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
$buttons = '';
// Bouton imprimer
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" > Validé< / 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" > Validé 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, etc.)
// ========================================
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" > Validé< / 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" > Validé 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);
}
}
/**
* 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;
}
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.']);
}
// ✅ AJOUT DES RÈGLES DE VALIDATION
$validation->setRules([
'product[]' => 'required',
'customer_type' => 'required',
'source' => 'required'
]);
// ✅ AJOUT DES DONNÉES DE VALIDATION
$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->generateBillNo($users['store_id']);
// Récupération des produits
$posts = $this->request->getPost('product[]');
$rates = $this->request->getPost('rate_value[]');
$amounts = $this->request->getPost('amount_value[]');
$puissances = $this->request->getPost('puissance[]');
$discount = (float)$this->request->getPost('discount') ?? 0;
$gross_amount = $this->calculGross($amounts);
// 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é."
]);
}
}
}
}
// Calculer le montant à payer et net_amount
$montant_a_payer = ($discount > 0) ? $discount : $gross_amount;
$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) {
$net_amount = $tranche_1 + $tranche_2;
} else {
$net_amount = $montant_a_payer;
}
// ✅ AJOUT DES NOUVEAUX CHAMPS ICI
$data = [
'bill_no' => $bill_no,
'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'), // ✅ NOUVEAU
'source' => $this->request->getPost('source'), // ✅ NOUVEAU
'date_time' => date('Y-m-d H:i:s'),
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
'net_amount' => $net_amount,
'discount' => $discount,
'paid_status' => 2,
'user_id' => $user_id,
'amount_value' => $amounts,
'gross_amount' => $gross_amount,
'rate_value' => $rates,
'puissance' => $puissances,
'store_id' => $users['store_id'],
'tranche_1' => $tranche_1,
'tranche_2' => $tranche_2,
'order_payment_mode' => $this->request->getPost('order_payment_mode_1'),
'order_payment_mode_1' => $this->request->getPost('order_payment_mode_2')
];
$order_id = $Orders->create($data, $posts);
if ($order_id) {
session()->setFlashdata('success', 'Créé avec succès');
$Notification = new NotificationController();
if ($discount > 0) {
// Logique 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 = [];
foreach ($product_data_results as $product) {
if (isset($product['sku'], $product['price'])) {
$sku = $product['sku'];
$price = $product['price'];
$product_lines[] = "{$sku}:{$price}";
}
}
$product_output = implode("\n", $product_lines);
$data1 = [
'date_demande' => date('Y-m-d H:i:s'),
'montant_demande' => $discount,
'total_price' => $amounts,
'id_store' => $users['store_id'],
'id_order' => $order_id,
'product' => $product_output,
'demande_status' => 'En attente'
];
$Remise = new Remise();
$id_remise = $Remise->addDemande($data1);
$Notification->createNotification(
"Nouvelle demande de remise à valider - Commande " . $bill_no,
"Direction",
(int)$users['store_id'],
"remise/"
);
} else {
$Notification->createNotification(
"Nouvelle commande à valider - " . $bill_no,
"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);
}
// Mettre à jour UNIQUEMENT le statut à 3 (Livré)
$updated = $Orders->update((int)$order_id, ['paid_status' => 3]);
if ($updated) {
// Créer une notification
try {
$Notification = new NotificationController();
$Notification->createNotification(
"Commande " . $current_order['bill_no'] . " livrée avec succès",
"Direction",
(int)$current_order['store_id'],
"orders"
);
} catch (\Exception $e) {
// Si la notification échoue, on continue quand même
log_message('error', 'Erreur notification: ' . $e->getMessage());
}
$response['success'] = true;
$response['messages'] = "La commande a été marquée comme livrée avec succès.";
} else {
$response['messages'] = "Erreur lors de la mise à jour du statut.";
}
} catch (\Exception $e) {
log_message('error', 'Erreur markAsDelivered: ' . $e->getMessage());
$response['messages'] = "Erreur serveur : " . $e->getMessage();
}
return $this->response->setJSON($response);
}
public function getProductValueById()
{
$product_id = $this->request->getPost('product_id');
if ($product_id) {
$Products = new \App\Models\Products();
$product_data = $Products->getProductData($product_id);
$FourchettePrix = new \App\Models\FourchettePrix();
$fourchette = $FourchettePrix->where('product_id', $product_id)->first();
// ✅ Extraire la puissance - Plusieurs méthodes
$puissance_extracted = '';
// Méthode 1: Chercher dans le champ puissance de la BD
if (!empty($product_data['puissance'])) {
$puissance_extracted = $product_data['puissance'];
}
// Méthode 2: Chercher à la fin du nom (format: |150)
if (empty($puissance_extracted) & & !empty($product_data['name'])) {
if (preg_match('/\|(\d+)(?:cc)?$/i', $product_data['name'], $matches)) {
$puissance_extracted = $matches[1];
}
}
// Méthode 3: Chercher n'importe où dans le nom (format: 150cc ou 150 CC)
if (empty($puissance_extracted) & & !empty($product_data['name'])) {
if (preg_match('/(\d+)\s*cc/i', $product_data['name'], $matches)) {
$puissance_extracted = $matches[1];
}
}
// Log pour debug (à retirer en production)
log_message('info', 'Product ID: ' . $product_id . ' - Puissance: ' . $puissance_extracted);
$response = [
'id' => $product_data['id'],
'sku' => $product_data['sku'],
'name' => $product_data['name'],
'prix_vente' => $product_data['prix_vente'],
'prix_minimal' => $fourchette ? $fourchette['prix_minimal'] : 0,
'puissance' => $puissance_extracted,
'numero_de_moteur' => $product_data['numero_de_moteur'] ?? '',
];
return $this->response->setJSON($response);
}
return $this->response->setJSON(['error' => 'Product ID missing']);
}
public function getTableProductRow()
{
$Products = new Products();
$session = session();
$users = $session->get('user');
$store_id = $users['store_id'];
$product_data = $Products->getProductData2($store_id);
// ✅ Nettoyer les données pour ne pas afficher la puissance dans le select
foreach ($product_data as & $product) {
// Extraire la puissance si elle est dans le nom
if (!empty($product['name']) & & preg_match('/\|(\d+)(?:cc)?$/i', $product['name'], $matches)) {
// Stocker la puissance séparément
if (empty($product['puissance'])) {
$product['puissance'] = $matches[1];
}
}
}
return $this->response->setJSON($product_data);
}
public function update(int $id)
{
$this->verifyRole('updateOrder');
$data['page_title'] = $this->pageTitle;
$validation = \Config\Services::validation();
$Orders = new Orders();
$current_order = $Orders->getOrdersData($id);
// ✅ AJOUT : Vérification plus détaillée
if (!$current_order || !isset($current_order['id'])) {
log_message('error', 'Commande introuvable pour ID: ' . $id);
session()->setFlashData('errors', 'Commande introuvable.');
return redirect()->to('orders/');
}
if (in_array($current_order['paid_status'], [1, 3])) {
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
return redirect()->to('orders/');
}
$validation->setRules([
'product' => 'required'
]);
$validationData = [
'product' => $this->request->getPost('product')
];
$Company = new Company();
$Products = new Products();
$OrderItems = new OrderItems();
$session = session();
$user = $session->get('user');
$role = $user['group_name'] ?? null;
if ($this->request->getMethod() === 'post' & & $validation->run($validationData)) {
$current_order_check = $Orders->getOrdersData($id);
if (in_array($current_order_check['paid_status'], [1, 3])) {
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
return redirect()->to('orders/');
}
$old_paid_status = $current_order['paid_status'];
if ($role === 'COMMERCIALE') {
$paid_status = 2;
} else {
$paid_status = $this->request->getPost('paid_status');
}
$validated_by = $current_order['validated_by'] ?? null;
$validated_at = $current_order['validated_at'] ?? null;
if ($old_paid_status != 1 & & $paid_status == 1 & & $role === 'Caissière') {
$validated_by = $user['id'];
$validated_at = date('Y-m-d H:i:s');
}
if (in_array($paid_status, [0, 2])) {
$validated_by = null;
$validated_at = null;
}
$discount = $this->request->getPost('discount');
$original_discount = $this->request->getPost('original_discount');
if ($discount === '' || $discount === null) {
$discount = $original_discount;
}
$dataUpdate = [
'customer_name' => $this->request->getPost('customer_name'),
'customer_address' => $this->request->getPost('customer_address'),
'customer_phone' => $this->request->getPost('customer_phone'),
'customer_cin' => $this->request->getPost('customer_cin'),
'customer_type' => $this->request->getPost('customer_type'),
'source' => $this->request->getPost('source'),
'gross_amount' => $this->request->getPost('gross_amount_value'),
'service_charge_rate' => $this->request->getPost('service_charge_rate'),
'service_charge' => max(0, (float)$this->request->getPost('service_charge_value')),
'vat_charge_rate' => $this->request->getPost('vat_charge_rate'),
'vat_charge' => max(0, (float)$this->request->getPost('vat_charge_value')),
'net_amount' => $this->request->getPost('net_amount_value'),
'discount' => (float)$discount,
'paid_status' => $paid_status,
'product' => $this->request->getPost('product'),
'product_sold' => true,
'rate_value' => $this->request->getPost('rate_value'),
'amount_value' => $this->request->getPost('amount_value'),
'puissance' => $this->request->getPost('puissance'),
'tranche_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_1') : null,
'tranche_2' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_2') : null,
'order_payment_mode' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_1') : null,
'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null,
'validated_by' => $validated_by,
'validated_at' => $validated_at
];
if ($Orders->updates($id, $dataUpdate)) {
$order_item_data = $OrderItems->getOrdersItemData($id);
$product_ids = array_column($order_item_data, 'product_id');
$Notification = new NotificationController();
if ($old_paid_status == 2 & & $paid_status == 1) {
$customer_name = $this->request->getPost('customer_name');
$bill_no = $current_order['bill_no'];
$Notification->createNotification(
"Commande validée: {$bill_no} - Client: {$customer_name}",
"SECURITE",
(int)$user['store_id'],
'orders'
);
if ($role === 'Caissière') {
$Notification->createNotification(
"Commande validée par la caisse: {$bill_no}",
"Direction",
(int)$user['store_id'],
'orders'
);
}
}
if ((float)$discount > 0) {
$productData = new Products();
$product_data_results = [];
foreach ($product_ids as $product_id) {
$product_data_results[] = $productData->getProductData((int) $product_id);
}
$product_lines = [];
foreach ($product_data_results as $product) {
if (isset($product['sku'], $product['price'])) {
$product_lines[] = "{$product['sku']}:{$product['price']}";
}
}
$product_output = implode("\n", $product_lines);
$data1 = [
'date_demande' => date('Y-m-d H:i:s'),
'montant_demande' => $this->request->getPost('discount'),
'total_price' => $this->request->getPost('amount_value'),
'id_store' => $user['store_id'],
'id_order' => $id,
'product' => $product_output,
'demande_status' => 'En attente'
];
$Remise = new Remise();
$Remise->updateRemise1($id, $data1);
$Notification->createNotification(
"Une nouvelle demande de remise a été ajoutée",
"Direction",
(int)$user['store_id'] ?? null,
"remise/"
);
}
session()->setFlashData('success', 'Commande mise à jour avec succès.');
return redirect()->to('orders/');
} else {
session()->setFlashData('errors', 'Une erreur est survenue lors de la mise à jour.');
return redirect()->to('orders/update/' . $id);
}
}
// ✅ Affichage du formulaire
$company = $Company->getCompanyData(1);
$data['company_data'] = $company;
$data['is_vat_enabled'] = ($company['vat_charge_value'] > 0);
$data['is_service_enabled'] = ($company['service_charge_value'] > 0);
$orders_data = $Orders->getOrdersData($id);
// ✅ VÉRIFICATION SUPPLÉMENTAIRE
if (!$orders_data || !isset($orders_data['id'])) {
log_message('error', 'Données de commande vides pour ID: ' . $id);
session()->setFlashData('errors', 'Impossible de charger les données de la commande.');
return redirect()->to('orders/');
}
$data['is_editable'] = !in_array($orders_data['paid_status'], [1, 3]);
$orders_data['montant_tranches'] = (!empty($orders_data['discount']) & & $orders_data['discount'] > 0)
? $orders_data['discount']
: $orders_data['gross_amount'];
$result = ['order' => $orders_data];
$orders_item = $OrderItems->getOrdersItemData($orders_data['id']);
foreach ($orders_item as $item) {
$result['order_item'][] = $item;
}
$data['order_data'] = $result;
$data['products'] = $Products->getActiveProductData();
$data['validation'] = $validation;
return $this->render_template('orders/edit', $data);
}
public function lookOrder(int $id)
{
$this->verifyRole('viewOrder');
$data['page_title'] = $this->pageTitle;
$Orders = new Orders();
$Company = new Company();
$Products = new Products();
$OrderItems = new OrderItems();
$Brands = new Brands();
// En cas d’échec de la validation ou si GET
$company = $Company->getCompanyData(1);
$data['company_data'] = $company;
$data['is_vat_enabled'] = ($company['vat_charge_value'] > 0);
$data['is_service_enabled'] = ($company['service_charge_value'] > 0);
$orders_data = $Orders->getOrdersData($id);
// $sum_order_item = $OrderItems->getSumOrdersItemData($orders_data['id']);
$result = [
'order' => $orders_data,
// 'sum_order_data' => $sum_order_item
];
$orders_item = $OrderItems->getOrdersItemData($orders_data['id']);
foreach ($orders_item as $item) {
$result['order_item'][] = $item;
}
$data['order_data'] = $result;
$data['products'] = $Products->getActiveProductData();
$data['brands'] = $Brands->findAll();
return $this->response->setJSON($data);
}
/**
* return storename
* @param int $id
* @return string
*/
private function returnStore($id)
{
$Stores = new Stores();
$store = $Stores->getActiveStore();
$name = "";
foreach ($store as $key => $value) {
if ($value['id'] == $id) {
$name = $value['name'];
}
}
return $name;
}
public function print2(int $id)
{
$this->verifyRole('viewOrder');
if ($id) {
$Orders = new Orders();
$Company = new Company();
$Products = new Products();
$OrderItems = new OrderItems();
// Récupération des données
$order_data = $Orders->getOrdersData($id);
$orders_items = $OrderItems->getOrdersItemData($id);
$company_info = $Company->getCompanyData(1);
// die(\var_dump($orders_items));
$html = '';
// Vérifier si l'utilisateur a payé
if ($order_data['paid_status'] == 1) {
$paid_status = "< span style = 'color: green; font-weight: bold;' > Validé< / 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();
if ($Orders->remove($order_id)) {
$response['success'] = true;
$response['messages'] = "Successfully removed";
} else {
$response['success'] = false;
$response['messages'] = "Error in the database while removing the product information";
}
} 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);
}
// 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;' > Validé< / 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');
// ✅ 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 > Facture '.$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 FACTURES CÔTE À CÔTE -->
< div class = "page" > ';
// ✅ GÉNÉRER 2 FACTURES 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" > Facture 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
if ($isAvanceMere) {
$html .= '
< table >
< thead >
< tr >
< th > Produit< / th >
< th class = "right" > Prix (Ar)< / th >
< / tr >
< / thead >
< tbody > ';
foreach ($items as $it) {
$details = $this->getOrderItemDetails($it);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '< tr > < td > '.esc($details['product_name']);
if (!empty($details['commentaire'])) {
$html .= '< br > < em style = "font-size:8px; color:#666;" > '.esc($details['commentaire']).'< / em > ';
}
$html .= '< / td >
< td class = "right" > '.number_format($prixAffiche, 0, '', ' ').'< / td >
< / tr > ';
}
// ✅ CORRECTION : Fermer le tableau pour avance
$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 class = "right" > PRIX (Ar)< / th >
< / tr >
< / thead >
< tbody > ';
foreach ($items as $it) {
$details = $this->getOrderItemDetails($it);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '
< tr >
< td > '.esc($details['marque']).'< / td >
< td > '.esc($details['product_name']).'< / td >
< td > '.esc($details['numero_moteur']).'< / td >
< td > '.esc($details['numero_chassis']).'< / td >
< td > '.esc($details['puissance']).'< / td >
< td class = "right" > '.number_format($prixAffiche, 0, '', ' ').'< / td >
< / tr > ';
}
// ✅ Fermer le tableau pour produit normal
$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;
// 70–79 et 90–99
if ($d === 7 || $d === 9) {
$base = $d === 7 ? 60 : 80;
return $tens[$d] . ($r ? '-' . $units[$n - $base] : '');
}
// 20–69 ou 80–89
return $tens[$d] . ($r ? '-' . $units[$r] : '');
}
if ($n < 1000 ) {
$h = intdiv($n, 100);
$rest = $n % 100;
$hundredText = $h > 1 ? $units[$h] . ' cent' : 'cent';
// « deux cents » prend un « s » si pas de reste
if ($h > 1 & & $rest === 0) {
$hundredText .= 's';
}
return $hundredText . ($rest ? ' ' . $convert($rest) : '');
}
if ($n < 1000000 ) {
$k = intdiv($n, 1000);
$rest = $n % 1000;
$thousandText = $k > 1 ? $convert($k) . ' mille' : 'mille';
return $thousandText . ($rest ? ' ' . $convert($rest) : '');
}
// millions et plus
$m = intdiv($n, 1000000);
$rest = $n % 1000000;
$millionText = $m > 1 ? $convert($m) . ' million' : 'un million';
// pas de 's' à million en francais
return $millionText . ($rest ? ' ' . $convert($rest) : '');
};
// Construit le texte sans la monnaie, puis ajoute 'ariary' à la fin
$words = $convert($num);
return trim($words) . ' ariary';
}
/**
* ✅ NOUVELLE MÉTHODE : Vérifier si une commande provient d'une avance "sur mer"
*/
private function isFromAvanceMere($order_id)
{
$db = \Config\Database::connect();
// Vérifier s'il existe une avance "sur mer" liée à cette commande
$avance = $db->table('avances')
->select('type_avance, product_name')
->where('is_order', 1)
->where('customer_name',
$db->table('orders')
->select('customer_name')
->where('id', $order_id)
->get()
->getRow()->customer_name ?? ''
)
->where('type_avance', 'mere')
->get()
->getRowArray();
return $avance !== null;
}
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les détails d'un item de commande
* Gère à la fois les produits normaux et ceux des avances "sur mer"
*/
private function getOrderItemDetails($item)
{
$Products = new Products();
$Brand = new Brands();
// Si l'item a un product_name (avance sur mer), on l'utilise directement
if (!empty($item['product_name']) & & empty($item['product_id'])) {
return [
'type' => 'mere',
'product_name' => $item['product_name'],
'marque' => $item['marque'] ?? $item['product_name'],
'numero_moteur' => $item['numero_moteur'] ?? '',
'numero_chassis' => $item['numero_chassis'] ?? '',
'puissance' => $item['puissance'] ?? '',
'commentaire' => $item['commentaire'] ?? '',
'prix' => (float)$item['amount']
];
}
// Sinon, récupérer depuis la table products (avance sur terre ou commande normale)
if (empty($item['product_id'])) {
return null;
}
$product = $Products->getProductData($item['product_id']);
if (!$product) {
return null;
}
// Récupérer le nom de la marque
$brandName = 'N/A';
if (!empty($product['marque'])) {
$brandData = $Brand->find($product['marque']);
if ($brandData & & isset($brandData['name'])) {
$brandName = $brandData['name'];
}
}
return [
'type' => 'terre',
'product_name' => $product['name'],
'marque' => $brandName,
'numero_moteur' => $product['numero_de_moteur'] ?? '',
'numero_chassis' => $product['chasis'] ?? $product['sku'] ?? '',
'puissance' => !empty($item['puissance']) ? $item['puissance'] : ($product['puissance'] ?? ''), // ✅ Priorité à la commande
'commentaire' => '',
'prix' => (float)$item['amount']
];
}
/**
* ✅ PRINT7 - Bon de commande adapté pour avances "sur mer" et "sur terre"
*/
public function print7(int $id)
{
$this->verifyRole('viewOrder');
if (!$id) {
throw new \CodeIgniter\Exceptions\PageNotFoundException();
}
// Modèles
$Orders = new Orders();
$Company = new Company();
$OrderItems = new OrderItems();
// Récupération des données
$order = $Orders->getOrdersData($id);
$items = $OrderItems->getOrdersItemData($id);
$company = $Company->getCompanyData(1);
$today = date('d/m/Y');
// ✅ VÉRIFIER SI C'EST UNE AVANCE "SUR MER"
$isAvanceMere = false;
foreach ($items as $item) {
if (!empty($item['product_name']) & & empty($item['product_id'])) {
$isAvanceMere = true;
break;
}
}
// ✅ LOGIQUE DE REMISE
$discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT;
$paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé';
// Démarrage du HTML
$html = '<!DOCTYPE html>
< html lang = "fr" >
< head >
< meta charset = "utf-8" >
< title > Bon de commande '.$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;" > Bon de commande 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) {
// --- TABLE SIMPLIFIÉE POUR AVANCE "SUR MER" (2-3 COLONNES) ---
$html .= '
< table >
< thead >
< tr >
< th > Produit< / th >
< th class = "right" > Prix Unitaire (Ar)< / th >
< / tr >
< / thead >
< tbody > ';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '
< tr >
< td > '.esc($details['product_name']);
// Afficher le commentaire s'il existe
if (!empty($details['commentaire'])) {
$html .= '< br > < em style = "font-size:12px; color:#666;" > Remarque : '.esc($details['commentaire']).'< / em > ';
}
$html .= '< / td >
< td class = "right" > '.number_format($prixAffiche, 0, '', ' ').'< / td >
< / tr > ';
}
$html .= '
< / tbody >
< / table > ';
} else {
// --- TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE (7 COLONNES) ---
$html .= '
< table >
< thead >
< tr >
< th > Nom< / th >
< th > Marque< / th >
< th > Catégorie< / th >
< th > N° Moteur< / th >
< th > Châssis< / th >
< th > Puissance (CC)< / th >
< th class = "right" > Prix Unitaire (Ar)< / th >
< / tr >
< / thead >
< tbody > ';
$Products = new Products();
$Brand = new Brands();
$Category = new Category();
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
// Récupérer la catégorie si c'est un produit terre
$categoryName = 'Non définie';
if ($details['type'] === 'terre' & & !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']);
if ($categoryData & & isset($categoryData['name'])) {
$categoryName = $categoryData['name'];
}
}
}
$html .= '
< tr >
< td > '.esc($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" > '.number_format($prixAffiche, 0, '', ' ').'< / td >
< / tr > ';
}
$html .= '
< / tbody >
< / table > ';
}
// ========================================
// TABLEAU RÉCAPITULATIF (IDENTIQUE POUR TOUS)
// ========================================
$html .= '
< table style = "width:calc(100% - 40px); margin:0 20px 20px;" >
< tr >
< td > < strong > Total HT :< / strong > < / td >
< 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 >
<!-- Conditions Générales avec saut de page -->
< 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);
// ✅ Vérifier si c'est une avance "sur mer"
$isAvanceMere = false;
foreach ($items as $item) {
if (!empty($item['product_name']) & & empty($item['product_id'])) {
$isAvanceMere = true;
break;
}
}
$paid_status = $order_data['paid_status'] === 1
? "< span style = 'color: green; font-weight: bold;' > Validé< / span > "
: "< span style = 'color: red; font-weight: bold;' > Refusé< / span > ";
// Calculs globaux
$discount = (float) $order_data['discount'];
$grossAmount = (float) $order_data['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
$totalHT = $totalTTC / 1.20;
$tva = $totalTTC - $totalHT;
// --- FACTURES : Une par produit ---
foreach ($items as $item) {
// ✅ Utiliser getOrderItemDetails au lieu de getProductData directement
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$unitPrice = $details['prix'];
$quantity = isset($item['qty']) ? (int) $item['qty'] : 1;
$subtotal = $unitPrice * $quantity;
// ✅ Pour avance sur mer avec remise, utiliser la remise comme prix
$prixAffiche = ($discount > 0 & & $isAvanceMere) ? $discount : $unitPrice;
echo '<!DOCTYPE html> ';
echo '< html lang = "fr" > ';
echo '< head > < meta charset = "utf-8" > ';
echo '< link rel = "stylesheet" href = "' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '" > ';
echo "< style >
body { font-family: Arial, sans-serif; font-size:14px; color:#000; margin:0; padding:0; }
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
.header .infos { line-height:1.4; }
.header img { max-height:80px; }
.client { margin-bottom:20px; }
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
th, td { border:1px solid #000; padding:6px; text-align:left; }
th { background:#f0f0f0; }
.right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin-top:50px; }
.signature div { text-align:center; }
.page-break { page-break-after: always; }
.to-fill { border-bottom: 1px dotted #000; min-width: 150px; display: inline-block; }
@media print {
body { font-size: 14px; }
@page { margin: 1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
< / style > ";
echo '< / head > < body onload = "window.print()" > ';
echo '< div class = "page-break" > ';
echo '< div class = "header" > ';
echo '< div class = "infos" > ';
echo '< h2 style = "margin:0;" > ' . esc($company_info['company_name']) . '< / h2 > ';
echo '< p style = "margin:2px 0;" > < strong > NIF :< / strong > ' . esc($company_info['NIF']) . '< / p > ';
echo '< p style = "margin:2px 0;" > < strong > STAT :< / strong > ' . esc($company_info['STAT']) . '< / p > ';
echo '< p style = "margin:2px 0;" > < strong > Contact :< / strong > ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '< / p > ';
echo '< p style = "margin:2px 0;" > < strong > Magasin :< / strong > ' . esc($this->returnStore($order_data['store_id'])) . '< / p > ';
echo '< / div > ';
echo '< div style = "text-align:center;" > ';
echo '< img src = "' . base_url('assets/images/company_logo.jpg') . '" alt = "Logo" > ';
echo '< p style = "margin:5px 0; font-weight:bold;" > Facture N° ' . esc($order_data['bill_no']) . '< / p > ';
echo '< p style = "margin:5px 0;" > < em > Antananarivo, le ' . date('d/m/Y') . '< / em > < / p > ';
echo '< / div > ';
echo '< / div > ';
echo '< div class = "client" > ';
echo '< p > < strong > DOIT :< / strong > ' . esc($order_data['customer_name']) . '< / p > ';
echo '< p > < strong > Adresse :< / strong > ' . esc($order_data['customer_address']) . '< / p > ';
echo '< p > < strong > Téléphone :< / strong > ' . esc($order_data['customer_phone']) . '< / p > ';
echo '< p > < strong > CIN :< / strong > ' . esc($order_data['customer_cin']) . '< / p > ';
echo '< / div > ';
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
if ($isAvanceMere) {
// TABLE SIMPLIFIÉE POUR AVANCE "SUR MER"
echo '< table > < thead > < tr > ';
echo '< th > Désignation< / th > ';
echo '< th > Produit à compléter< / th > ';
echo '< th class = "right" > Prix (Ar)< / th > ';
echo '< / tr > < / thead > < tbody > ';
echo '< tr > ';
echo '< td > ' . esc($details['product_name']) . '< / td > ';
echo '< td > < span class = "to-fill" > < / span > < / td > ';
echo '< td class = "right" > ' . number_format($prixAffiche, 0, '', ' ') . '< / td > ';
echo '< / tr > ';
// Afficher commentaire si existant
if (!empty($details['commentaire'])) {
echo '< tr > < td colspan = "3" > < em style = "font-size:12px; color:#666;" > Remarque : ' . esc($details['commentaire']) . '< / em > < / td > < / tr > ';
}
echo '< / tbody > < / table > ';
} else {
// TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE
// Récupérer catégorie
$categoryName = 'Non définie';
if ($details['type'] === 'terre' & & !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']);
if ($categoryData & & isset($categoryData['name'])) {
$categoryName = $categoryData['name'];
}
}
}
echo '< table > < thead > < tr > ';
echo '< th > Nom< / th > < th > Marque< / th > < th > Catégorie< / th > < th > N° Moteur< / th > < th > Châssis< / th > < th > Puissance (CC)< / th > < th class = "right" > Prix Unit. (Ar)< / th > ';
echo '< / tr > < / thead > < tbody > ';
echo '< tr > ';
echo '< td > ' . esc($details['product_name']) . '< / td > ';
echo '< td > ' . esc($details['marque']) . '< / td > ';
echo '< td > ' . esc($categoryName) . '< / td > ';
echo '< td > ' . esc($details['numero_moteur']) . '< / td > ';
echo '< td > ' . esc($details['numero_chassis']) . '< / td > ';
echo '< td > ' . esc($details['puissance']) . '< / td > ';
echo '< td class = "right" > ' . number_format($prixAffiche, 0, '', ' ') . '< / td > ';
echo '< / tr > ';
echo '< / tbody > < / table > ';
}
// Récapitulatif pour cette facture
$itemHT = $prixAffiche / 1.20;
$itemTVA = $prixAffiche - $itemHT;
echo '< table > ';
echo '< tr > < td > < strong > Total HT :< / strong > < / td > < td class = "right" > ' . number_format($itemHT, 0, '', ' ') . ' Ar< / td > < / tr > ';
echo '< tr > < td > < strong > TVA (20%) :< / strong > < / td > < td class = "right" > ' . number_format($itemTVA, 0, '', ' ') . ' Ar< / td > < / tr > ';
echo '< tr > < td > < strong > Total TTC :< / strong > < / td > < td class = "right" > ' . number_format($prixAffiche, 0, '', ' ') . ' Ar< / td > < / tr > ';
echo '< tr > < td > < strong > Statut :< / strong > < / td > < td class = "right" > ' . $paid_status . '< / td > < / tr > ';
if (!empty($order_data['order_payment_mode'])) {
echo '< tr > < td > < strong > Mode de paiement :< / strong > < / td > < td class = "right" > ' . esc($order_data['order_payment_mode']) . '< / td > < / tr > ';
}
if (!empty($order_data['tranche_1'])) {
echo '< tr > < td > < strong > Tranche 1 :< / strong > < / td > < td class = "right" > ' . number_format((float)$order_data['tranche_1'], 0, '', ' ') . ' Ar< / td > < / tr > ';
}
if (!empty($order_data['tranche_2'])) {
echo '< tr > < td > < strong > Tranche 2 :< / strong > < / td > < td class = "right" > ' . number_format((float)$order_data['tranche_2'], 0, '', ' ') . ' Ar< / td > < / tr > ';
}
echo '< / table > ';
echo '< div class = "signature" > ';
echo '< div > L\'Acheteur< br > < br > __________________< / div > ';
echo '< div > Le Vendeur< br > < br > __________________< / div > ';
echo '< / div > ';
echo '< / div > '; // fin page-break
echo '< / body > < / html > ';
}
// --- BON DE COMMANDE (UNE SEULE PAGE) ---
echo '<!DOCTYPE html> ';
echo '< html lang = "fr" > ';
echo '< head > < meta charset = "utf-8" > ';
echo '< link rel = "stylesheet" href = "' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '" > ';
echo "< style >
body { font-family: Arial, sans-serif; font-size:14px; color:#000; margin:0; padding:0; }
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
.header .infos { line-height:1.4; }
.header img { max-height:80px; }
.client { margin-bottom:20px; }
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
th, td { border:1px solid #000; padding:6px; text-align:left; }
th { background:#f0f0f0; }
.right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin-top:50px; }
.signature div { text-align:center; }
.to-fill { border-bottom: 1px dotted #000; min-width: 150px; display: inline-block; }
.conditions { page-break-before: always; padding:20px; line-height:1.5; }
@media print {
body { font-size: 14px; }
@page { margin: 1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
< / style > ";
echo '< / head > < body > ';
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;" > Bon de Commande 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 = "right" > Prix (Ar)< / th > ';
echo '< / tr > < / thead > < tbody > ';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
echo '< tr > ';
echo '< td > ' . esc($details['product_name']) . '< / td > ';
echo '< td > < span class = "to-fill" > < / span > < / td > ';
echo '< td class = "right" > ' . number_format($prixAffiche, 0, '', ' ') . '< / td > ';
echo '< / tr > ';
}
echo '< / tbody > < / table > ';
} else {
echo '< table > < thead > < tr > ';
echo '< th > Nom< / th > < th > Marque< / th > < th > Catégorie< / th > < th > N° Moteur< / th > < th > Châssis< / th > < th > Puissance< / th > < th class = "right" > Prix (Ar)< / th > ';
echo '< / tr > < / thead > < tbody > ';
foreach ($items as $item) {
$details = $this->getOrderItemDetails($item);
if (!$details) continue;
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
// Catégorie
$categoryName = 'Non définie';
if ($details['type'] === 'terre' & & !empty($item['product_id'])) {
$p = $Products->getProductData($item['product_id']);
if (!empty($p['categorie_id'])) {
$categoryData = $Category->find($p['categorie_id']);
if ($categoryData) $categoryName = $categoryData['name'];
}
}
echo '< tr > ';
echo '< td > ' . esc($details['product_name']) . '< / td > ';
echo '< td > ' . esc($details['marque']) . '< / td > ';
echo '< td > ' . esc($categoryName) . '< / td > ';
echo '< td > ' . esc($details['numero_moteur']) . '< / td > ';
echo '< td > ' . esc($details['numero_chassis']) . '< / td > ';
echo '< td > ' . esc($details['puissance']) . '< / td > ';
echo '< td class = "right" > ' . number_format($prixAffiche, 0, '', ' ') . '< / td > ';
echo '< / tr > ';
}
echo '< / tbody > < / table > ';
}
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 > ';
}
}