motorbike/app/Controllers/OrderController.php
2025-11-01 08:41:24 +03:00

2777 lines
109 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 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;
// 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();
}
// 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">&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="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">&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="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>';
}
}