You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2900 lines
114 KiB
2900 lines
114 KiB
<?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">payé</span>';
|
|
} elseif ($value['paid_status'] == 2) {
|
|
$paid_status = '<span class="label label-warning">En Attente</span>';
|
|
} elseif ($value['paid_status'] == 3) {
|
|
$paid_status = '<span class="label label-info">payé et Livré</span>';
|
|
} else {
|
|
$paid_status = '<span class="label label-danger">Refusé</span>';
|
|
}
|
|
|
|
// Calcul délai
|
|
$date1 = new DateTime($date_time);
|
|
$date2 = new DateTime();
|
|
$interval = $date1->diff($date2);
|
|
$daysPassed = $interval->days;
|
|
|
|
$statuDate = '';
|
|
if ($value['paid_status'] == 2) {
|
|
if ($daysPassed < 8) {
|
|
$statuDate = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
|
|
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} else {
|
|
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
|
|
}
|
|
}
|
|
|
|
$result['data'][$key] = [
|
|
$value['product_names'],
|
|
$value['user_name'],
|
|
$date_time . " <br >" . $statuDate,
|
|
number_format((int) $value['discount'], 0, ',', ' '),
|
|
number_format((int) $value['gross_amount'], 0, ',', ' '),
|
|
$paid_status,
|
|
$buttons
|
|
];
|
|
}
|
|
return $this->response->setJSON($result);
|
|
}
|
|
|
|
// ========================================
|
|
// POUR DIRECTION OU DAF
|
|
// ========================================
|
|
elseif($users['group_name'] == "Direction" || $users['group_name'] == "DAF" || $users['group_name'] == "SuperAdmin" ){
|
|
foreach ($data as $key => $value) {
|
|
$date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
|
|
|
|
$buttons = '';
|
|
|
|
// Bouton imprimer
|
|
if (in_array('viewOrder', $this->permission) && $users['group_name'] != "SECURITE" && $users['group_name'] != "COMMERCIALE") {
|
|
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
|
|
}
|
|
|
|
// ✅ Bouton modifier pour statuts 0 (Refusé) et 2 (En Attente)
|
|
if (in_array('updateOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) {
|
|
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>';
|
|
}
|
|
|
|
// ✅ Bouton supprimer pour statuts 0 et 2
|
|
if (in_array('deleteOrder', $this->permission) && in_array($value['paid_status'], [0, 2])) {
|
|
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
|
|
}
|
|
|
|
// Statut de paiement
|
|
if ($value['paid_status'] == 1) {
|
|
$paid_status = '<span class="label label-success">payé</span>';
|
|
} elseif ($value['paid_status'] == 2) {
|
|
$paid_status = '<span class="label label-warning">En Attente</span>';
|
|
} elseif ($value['paid_status'] == 3) {
|
|
$paid_status = '<span class="label label-info">Payé et Livré</span>';
|
|
} else {
|
|
$paid_status = '<span class="label label-danger">Refusé</span>';
|
|
}
|
|
|
|
// Calcul délai
|
|
$date1 = new DateTime($date_time);
|
|
$date2 = new DateTime();
|
|
$interval = $date1->diff($date2);
|
|
$daysPassed = $interval->days;
|
|
|
|
$statuDate = '';
|
|
if ($value['paid_status'] == 2) {
|
|
if ($daysPassed < 8) {
|
|
$statuDate = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
|
|
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} else {
|
|
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
|
|
}
|
|
}
|
|
|
|
$result['data'][$key] = [
|
|
$value['bill_no'],
|
|
$value['customer_name'],
|
|
$value['customer_phone'],
|
|
$date_time . " <br >" . $statuDate,
|
|
number_format((int) $value['discount'], 0, ',', ' '),
|
|
number_format((int) $value['gross_amount'], 0, ',', ' '),
|
|
$paid_status,
|
|
$buttons
|
|
];
|
|
}
|
|
return $this->response->setJSON($result);
|
|
}
|
|
|
|
// ========================================
|
|
// POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, 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">Payé</span>';
|
|
} elseif ($value['paid_status'] == 2) {
|
|
$paid_status = '<span class="label label-warning">En Attente</span>';
|
|
} elseif ($value['paid_status'] == 3) {
|
|
$paid_status = '<span class="label label-info">Payé et Livré</span>';
|
|
} else {
|
|
$paid_status = '<span class="label label-danger">Refusé</span>';
|
|
}
|
|
|
|
// Calcul délai
|
|
$date1 = new DateTime($date_time);
|
|
$date2 = new DateTime();
|
|
$interval = $date1->diff($date2);
|
|
$daysPassed = $interval->days;
|
|
|
|
$statuDate = '';
|
|
if ($value['paid_status'] == 2) {
|
|
if ($daysPassed < 8) {
|
|
$statuDate = '<span class="label label-success"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} elseif ($daysPassed >= 8 && $daysPassed < 15) {
|
|
$statuDate = '<span class="label label-warning"> depuis ' . $daysPassed . ' Jours</span>';
|
|
} else {
|
|
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
|
|
}
|
|
}
|
|
|
|
$result['data'][$key] = [
|
|
$value['product_names'],
|
|
$value['user_name'],
|
|
$date_time . " <br >" . $statuDate,
|
|
number_format((int) $value['discount'], 0, ',', ' '),
|
|
number_format((int) $value['gross_amount'], 0, ',', ' '),
|
|
$paid_status,
|
|
$buttons
|
|
];
|
|
}
|
|
return $this->response->setJSON($result);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche le formulaire create avec les données d'une commande existante
|
|
* Pour le rôle COMMERCIALE
|
|
*/
|
|
|
|
/**
|
|
* function who check if the product is null
|
|
* and create notification about it
|
|
* @param array $product_id id of the product
|
|
* @param int $store_id id of the store
|
|
* @return void
|
|
*/
|
|
private function checkProductisNull(array $product_id, $store_id)
|
|
{
|
|
$notification = new NotificationController();
|
|
$product = new Products();
|
|
for ($i = 0; $i < count($product_id); $i++) {
|
|
$singleProduct = $product->getProductData($product_id[$i]);
|
|
if ($singleProduct['product_sold'] == true) {
|
|
$notification->createNotification("Produit en rupture de stock", "Direction", $store_id, "products");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function calculGross($request)
|
|
{
|
|
$amount = $request;
|
|
$montant = 0;
|
|
for ($i = 0; $i < \count($amount); $i++) {
|
|
$montant += $amount[$i];
|
|
}
|
|
return $montant;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* ✅ AMÉLIORATION : Notifications centralisées pour Direction/DAF/SuperAdmin (tous stores)
|
|
*/
|
|
|
|
public function create()
|
|
{
|
|
$this->verifyRole('createOrder');
|
|
$data['page_title'] = $this->pageTitle;
|
|
|
|
$validation = \Config\Services::validation();
|
|
$products = $this->request->getPost('product[]');
|
|
|
|
if ($products !== null && (count($products) !== count(array_unique($products)))) {
|
|
return redirect()->back()->withInput()->with('errors', ['product' => 'Chaque produit sélectionné doit être unique.']);
|
|
}
|
|
|
|
$validation->setRules([
|
|
'product[]' => 'required',
|
|
'customer_type' => 'required',
|
|
'source' => 'required'
|
|
]);
|
|
|
|
$validationData = [
|
|
'product[]' => $this->request->getPost('product[]'),
|
|
'customer_type' => $this->request->getPost('customer_type'),
|
|
'source' => $this->request->getPost('source')
|
|
];
|
|
|
|
$Orders = new Orders();
|
|
$Company = new Company();
|
|
$Products = new Products();
|
|
|
|
if ($this->request->getMethod() === 'post' && $validation->run($validationData)) {
|
|
|
|
$session = session();
|
|
$users = $session->get('user');
|
|
$user_id = $users['id'];
|
|
|
|
$bill_no = $this->generateBillNo($users['store_id']);
|
|
|
|
$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é."
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
$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'),
|
|
'source' => $this->request->getPost('source'),
|
|
'date_time' => date('Y-m-d H:i:s'),
|
|
'service_charge_rate' => 0,
|
|
'vat_charge_rate' => 0,
|
|
'vat_charge' => 0,
|
|
'net_amount' => $net_amount,
|
|
'discount' => $discount,
|
|
'paid_status' => 2,
|
|
'user_id' => $user_id,
|
|
'amount_value' => $amounts,
|
|
'gross_amount' => $gross_amount,
|
|
'rate_value' => $rates,
|
|
'puissance' => $puissances,
|
|
'store_id' => $users['store_id'],
|
|
'tranche_1' => $tranche_1,
|
|
'tranche_2' => $tranche_2,
|
|
'order_payment_mode' => $this->request->getPost('order_payment_mode_1'),
|
|
'order_payment_mode_1' => $this->request->getPost('order_payment_mode_2')
|
|
];
|
|
|
|
$order_id = $Orders->create($data, $posts);
|
|
|
|
if ($order_id) {
|
|
session()->setFlashdata('success', 'Créé avec succès');
|
|
|
|
$Notification = new NotificationController();
|
|
$Stores = new Stores();
|
|
|
|
if ($discount > 0) {
|
|
// ✅ DEMANDE DE REMISE : NOTIFIER TOUS LES STORES
|
|
$Order_item1 = new OrderItems();
|
|
$order_item_data = $Order_item1->getOrdersItemData($order_id);
|
|
$product_ids = array_column($order_item_data, 'product_id');
|
|
|
|
$productData = new Products();
|
|
$product_data_results = [];
|
|
|
|
foreach ($product_ids as $prod_id) {
|
|
$id = (int) $prod_id;
|
|
$product_data_results[] = $productData->getProductData($id);
|
|
}
|
|
|
|
$product_lines = [];
|
|
foreach ($product_data_results as $product) {
|
|
if (isset($product['sku'], $product['price'])) {
|
|
$sku = $product['sku'];
|
|
$price = $product['price'];
|
|
$product_lines[] = "{$sku}:{$price}";
|
|
}
|
|
}
|
|
|
|
$product_output = implode("\n", $product_lines);
|
|
|
|
$data1 = [
|
|
'date_demande' => date('Y-m-d H:i:s'),
|
|
'montant_demande' => $discount,
|
|
'total_price' => $amounts,
|
|
'id_store' => $users['store_id'],
|
|
'id_order' => $order_id,
|
|
'product' => $product_output,
|
|
'demande_status' => 'En attente'
|
|
];
|
|
|
|
$Remise = new Remise();
|
|
$id_remise = $Remise->addDemande($data1);
|
|
|
|
// ✅ RÉCUPÉRER TOUS LES STORES
|
|
$allStores = $Stores->getActiveStore();
|
|
|
|
$montantFormatted = number_format($discount, 0, ',', ' ');
|
|
$message = "💰 Nouvelle demande de remise : {$montantFormatted} Ar<br>" .
|
|
"Commande : {$bill_no}<br>" .
|
|
"Store : " . $this->returnStore($users['store_id']) . "<br>" .
|
|
"Demandeur : {$users['firstname']} {$users['lastname']}";
|
|
|
|
// ✅ NOTIFIER SUPERADMIN, DIRECTION, DAF DE TOUS LES STORES
|
|
if (is_array($allStores) && count($allStores) > 0) {
|
|
foreach ($allStores as $store) {
|
|
// SuperAdmin (validation)
|
|
$Notification->createNotification(
|
|
$message,
|
|
"SuperAdmin",
|
|
(int)$store['id'],
|
|
'remise/'
|
|
);
|
|
|
|
// Direction (consultation)
|
|
$Notification->createNotification(
|
|
$message . "<br><em>Pour information</em>",
|
|
"Direction",
|
|
(int)$store['id'],
|
|
'remise/'
|
|
);
|
|
|
|
// DAF (consultation)
|
|
$Notification->createNotification(
|
|
$message . "<br><em>Pour information</em>",
|
|
"DAF",
|
|
(int)$store['id'],
|
|
'remise/'
|
|
);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// ✅ COMMANDE SANS REMISE : NOTIFIER CAISSIÈRE DU STORE + TOUS LES CENTRAUX
|
|
|
|
// Caissière du store concerné
|
|
$Notification->createNotification(
|
|
"📦 Nouvelle commande à valider : {$bill_no}<br>" .
|
|
"Client : {$data['customer_name']}<br>" .
|
|
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar",
|
|
"Caissière",
|
|
(int)$users['store_id'],
|
|
"orders"
|
|
);
|
|
|
|
// ✅ RÉCUPÉRER TOUS LES STORES
|
|
$allStores = $Stores->getActiveStore();
|
|
|
|
$messageGlobal = "📋 Nouvelle commande créée : {$bill_no}<br>" .
|
|
"Store : " . $this->returnStore($users['store_id']) . "<br>" .
|
|
"Client : {$data['customer_name']}<br>" .
|
|
"Montant : " . number_format($gross_amount, 0, ',', ' ') . " Ar<br>" .
|
|
"Créée par : {$users['firstname']} {$users['lastname']}";
|
|
|
|
// ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES
|
|
if (is_array($allStores) && count($allStores) > 0) {
|
|
foreach ($allStores as $store) {
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"Direction",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"DAF",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"SuperAdmin",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($users["group_name"] != "COMMERCIALE") {
|
|
$this->checkProductisNull($posts, $users['store_id']);
|
|
}
|
|
return redirect()->to('orders/');
|
|
|
|
} else {
|
|
session()->setFlashdata('errors', 'Error occurred!!');
|
|
return redirect()->to('orders/create/');
|
|
}
|
|
|
|
} else {
|
|
// Affichage du formulaire
|
|
$company = $Company->getCompanyData(1);
|
|
$session = session();
|
|
$users = $session->get('user');
|
|
$store_id = $users['store_id'];
|
|
|
|
$data = [
|
|
'paid_status' => 2,
|
|
'company_data' => $company,
|
|
'is_vat_enabled' => ($company['vat_charge_value'] > 0),
|
|
'is_service_enabled' => ($company['service_charge_value'] > 0),
|
|
'products' => $Products->getProductData2($store_id),
|
|
'validation' => $validation,
|
|
'page_title' => $this->pageTitle,
|
|
];
|
|
|
|
return $this->render_template('orders/create', $data);
|
|
}
|
|
}
|
|
/**
|
|
* Marquer une commande comme livrée
|
|
* Accessible uniquement par le rôle SECURITE
|
|
*/
|
|
public function markAsDelivered()
|
|
{
|
|
// Activer le retour JSON même en cas d'erreur
|
|
ini_set('display_errors', 0);
|
|
|
|
$order_id = $this->request->getPost('order_id');
|
|
$response = ['success' => false, 'messages' => ''];
|
|
|
|
try {
|
|
$session = session();
|
|
$users = $session->get('user');
|
|
|
|
// Vérifier que l'utilisateur est SECURITE
|
|
if (!isset($users['group_name']) || $users['group_name'] !== 'SECURITE') {
|
|
$response['messages'] = "Accès refusé : seule la sécurité peut marquer une commande comme livrée.";
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
if (!$order_id) {
|
|
$response['messages'] = "ID de commande manquant.";
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
$Orders = new Orders();
|
|
|
|
// Récupérer la commande actuelle
|
|
$current_order = $Orders->getOrdersData((int)$order_id);
|
|
|
|
if (!$current_order) {
|
|
$response['messages'] = "Commande introuvable.";
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
// Vérifier que la commande est validée (paid_status = 1)
|
|
if ($current_order['paid_status'] != 1) {
|
|
$response['messages'] = "Cette commande doit être validée avant d'être marquée comme livrée.";
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
// Mettre à jour UNIQUEMENT le statut à 3 (Livré)
|
|
$updated = $Orders->update((int)$order_id, ['paid_status' => 3]);
|
|
|
|
if ($updated) {
|
|
// ✅ Créer une notification centralisée pour tous les stores
|
|
try {
|
|
$Notification = new NotificationController();
|
|
$Stores = new Stores();
|
|
$allStores = $Stores->getActiveStore();
|
|
|
|
$messageGlobal = "📦 Commande livrée : {$current_order['bill_no']}<br>" .
|
|
"Store : " . $this->returnStore($current_order['store_id']) . "<br>" .
|
|
"Client : {$current_order['customer_name']}<br>" .
|
|
"Livrée par : {$users['firstname']} {$users['lastname']}";
|
|
|
|
// ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES
|
|
if (is_array($allStores) && count($allStores) > 0) {
|
|
foreach ($allStores as $store) {
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"Direction",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"DAF",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"SuperAdmin",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Si la notification échoue, on continue quand même
|
|
log_message('error', 'Erreur notification: ' . $e->getMessage());
|
|
}
|
|
|
|
$response['success'] = true;
|
|
$response['messages'] = "La commande a été marquée comme livrée avec succès.";
|
|
} else {
|
|
$response['messages'] = "Erreur lors de la mise à jour du statut.";
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
log_message('error', 'Erreur markAsDelivered: ' . $e->getMessage());
|
|
$response['messages'] = "Erreur serveur : " . $e->getMessage();
|
|
}
|
|
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
public function getProductValueById()
|
|
{
|
|
$product_id = $this->request->getPost('product_id');
|
|
if ($product_id) {
|
|
$Products = new \App\Models\Products();
|
|
$product_data = $Products->getProductData($product_id);
|
|
|
|
$FourchettePrix = new \App\Models\FourchettePrix();
|
|
$fourchette = $FourchettePrix->where('product_id', $product_id)->first();
|
|
|
|
// ✅ Extraire la puissance - Plusieurs méthodes
|
|
$puissance_extracted = '';
|
|
|
|
// Méthode 1: Chercher dans le champ puissance de la BD
|
|
if (!empty($product_data['puissance'])) {
|
|
$puissance_extracted = $product_data['puissance'];
|
|
}
|
|
|
|
// Méthode 2: Chercher à la fin du nom (format: |150)
|
|
if (empty($puissance_extracted) && !empty($product_data['name'])) {
|
|
if (preg_match('/\|(\d+)(?:cc)?$/i', $product_data['name'], $matches)) {
|
|
$puissance_extracted = $matches[1];
|
|
}
|
|
}
|
|
|
|
// Méthode 3: Chercher n'importe où dans le nom (format: 150cc ou 150 CC)
|
|
if (empty($puissance_extracted) && !empty($product_data['name'])) {
|
|
if (preg_match('/(\d+)\s*cc/i', $product_data['name'], $matches)) {
|
|
$puissance_extracted = $matches[1];
|
|
}
|
|
}
|
|
|
|
// Log pour debug (à retirer en production)
|
|
log_message('info', 'Product ID: ' . $product_id . ' - Puissance: ' . $puissance_extracted);
|
|
|
|
$response = [
|
|
'id' => $product_data['id'],
|
|
'sku' => $product_data['sku'],
|
|
'name' => $product_data['name'],
|
|
'prix_vente' => $product_data['prix_vente'],
|
|
'prix_minimal' => $fourchette ? $fourchette['prix_minimal'] : 0,
|
|
'puissance' => $puissance_extracted,
|
|
'numero_de_moteur' => $product_data['numero_de_moteur'] ?? '',
|
|
];
|
|
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
return $this->response->setJSON(['error' => 'Product ID missing']);
|
|
}
|
|
public function getTableProductRow()
|
|
{
|
|
$Products = new Products();
|
|
$session = session();
|
|
$users = $session->get('user');
|
|
$store_id = $users['store_id'];
|
|
|
|
$product_data = $Products->getProductData2($store_id);
|
|
|
|
// ✅ Nettoyer les données pour ne pas afficher la puissance dans le select
|
|
foreach ($product_data as &$product) {
|
|
// Extraire la puissance si elle est dans le nom
|
|
if (!empty($product['name']) && preg_match('/\|(\d+)(?:cc)?$/i', $product['name'], $matches)) {
|
|
// Stocker la puissance séparément
|
|
if (empty($product['puissance'])) {
|
|
$product['puissance'] = $matches[1];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return $this->response->setJSON($product_data);
|
|
}
|
|
|
|
public function update(int $id)
|
|
{
|
|
$this->verifyRole('updateOrder');
|
|
|
|
$data['page_title'] = $this->pageTitle;
|
|
$validation = \Config\Services::validation();
|
|
|
|
$Orders = new Orders();
|
|
$current_order = $Orders->getOrdersData($id);
|
|
|
|
// ✅ AJOUT : Vérification plus détaillée
|
|
if (!$current_order || !isset($current_order['id'])) {
|
|
log_message('error', 'Commande introuvable pour ID: ' . $id);
|
|
session()->setFlashData('errors', 'Commande introuvable.');
|
|
return redirect()->to('orders/');
|
|
}
|
|
|
|
if (in_array($current_order['paid_status'], [1, 3])) {
|
|
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
|
|
return redirect()->to('orders/');
|
|
}
|
|
|
|
$validation->setRules([
|
|
'product' => 'required'
|
|
]);
|
|
|
|
$validationData = [
|
|
'product' => $this->request->getPost('product')
|
|
];
|
|
|
|
$Company = new Company();
|
|
$Products = new Products();
|
|
$OrderItems = new OrderItems();
|
|
|
|
$session = session();
|
|
$user = $session->get('user');
|
|
$role = $user['group_name'] ?? null;
|
|
|
|
if ($this->request->getMethod() === 'post' && $validation->run($validationData)) {
|
|
|
|
$current_order_check = $Orders->getOrdersData($id);
|
|
if (in_array($current_order_check['paid_status'], [1, 3])) {
|
|
session()->setFlashData('errors', 'Cette commande ne peut plus être modifiée car elle est déjà validée ou livrée.');
|
|
return redirect()->to('orders/');
|
|
}
|
|
|
|
$old_paid_status = $current_order['paid_status'];
|
|
|
|
if ($role === 'COMMERCIALE') {
|
|
$paid_status = 2;
|
|
} else {
|
|
$paid_status = $this->request->getPost('paid_status');
|
|
}
|
|
|
|
$validated_by = $current_order['validated_by'] ?? null;
|
|
$validated_at = $current_order['validated_at'] ?? null;
|
|
|
|
if ($old_paid_status != 1 && $paid_status == 1 && $role === 'Caissière') {
|
|
$validated_by = $user['id'];
|
|
$validated_at = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
if (in_array($paid_status, [0, 2])) {
|
|
$validated_by = null;
|
|
$validated_at = null;
|
|
}
|
|
|
|
$discount = $this->request->getPost('discount');
|
|
$original_discount = $this->request->getPost('original_discount');
|
|
if ($discount === '' || $discount === null) {
|
|
$discount = $original_discount;
|
|
}
|
|
|
|
$dataUpdate = [
|
|
'customer_name' => $this->request->getPost('customer_name'),
|
|
'customer_address' => $this->request->getPost('customer_address'),
|
|
'customer_phone' => $this->request->getPost('customer_phone'),
|
|
'customer_cin' => $this->request->getPost('customer_cin'),
|
|
'customer_type' => $this->request->getPost('customer_type'),
|
|
'source' => $this->request->getPost('source'),
|
|
'gross_amount' => $this->request->getPost('gross_amount_value'),
|
|
'service_charge_rate' => $this->request->getPost('service_charge_rate'),
|
|
'service_charge' => max(0, (float)$this->request->getPost('service_charge_value')),
|
|
'vat_charge_rate' => $this->request->getPost('vat_charge_rate'),
|
|
'vat_charge' => max(0, (float)$this->request->getPost('vat_charge_value')),
|
|
'net_amount' => $this->request->getPost('net_amount_value'),
|
|
'discount' => (float)$discount,
|
|
'paid_status' => $paid_status,
|
|
'product' => $this->request->getPost('product'),
|
|
'product_sold' => true,
|
|
'rate_value' => $this->request->getPost('rate_value'),
|
|
'amount_value' => $this->request->getPost('amount_value'),
|
|
'puissance' => $this->request->getPost('puissance'),
|
|
'tranche_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_1') : null,
|
|
'tranche_2' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_2') : null,
|
|
'order_payment_mode' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_1') : null,
|
|
'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null,
|
|
'validated_by' => $validated_by,
|
|
'validated_at' => $validated_at
|
|
];
|
|
|
|
if ($Orders->updates($id, $dataUpdate)) {
|
|
$order_item_data = $OrderItems->getOrdersItemData($id);
|
|
$product_ids = array_column($order_item_data, 'product_id');
|
|
|
|
$Notification = new NotificationController();
|
|
|
|
// ✅ NOTIFICATION CENTRALISÉE POUR VALIDATION
|
|
if ($old_paid_status == 2 && $paid_status == 1) {
|
|
$customer_name = $this->request->getPost('customer_name');
|
|
$bill_no = $current_order['bill_no'];
|
|
|
|
// ✅ Notification SECURITE du store concerné
|
|
$Notification->createNotification(
|
|
"Commande validée: {$bill_no} - Client: {$customer_name}",
|
|
"SECURITE",
|
|
(int)$user['store_id'],
|
|
'orders'
|
|
);
|
|
|
|
// ✅ RÉCUPÉRER TOUS LES STORES
|
|
$Stores = new Stores();
|
|
$allStores = $Stores->getActiveStore();
|
|
|
|
$messageGlobal = "✅ Commande validée : {$bill_no}<br>" .
|
|
"Store : " . $this->returnStore($user['store_id']) . "<br>" .
|
|
"Client : {$customer_name}<br>" .
|
|
"Validée par : {$user['firstname']} {$user['lastname']}";
|
|
|
|
// ✅ NOTIFIER DIRECTION, DAF, SUPERADMIN DE TOUS LES STORES
|
|
if (is_array($allStores) && count($allStores) > 0) {
|
|
foreach ($allStores as $store) {
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"Direction",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"DAF",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
|
|
$Notification->createNotification(
|
|
$messageGlobal,
|
|
"SuperAdmin",
|
|
(int)$store['id'],
|
|
'orders'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((float)$discount > 0) {
|
|
$productData = new Products();
|
|
$product_data_results = [];
|
|
|
|
foreach ($product_ids as $product_id) {
|
|
$product_data_results[] = $productData->getProductData((int) $product_id);
|
|
}
|
|
|
|
$product_lines = [];
|
|
foreach ($product_data_results as $product) {
|
|
if (isset($product['sku'], $product['price'])) {
|
|
$product_lines[] = "{$product['sku']}:{$product['price']}";
|
|
}
|
|
}
|
|
|
|
$product_output = implode("\n", $product_lines);
|
|
|
|
$data1 = [
|
|
'date_demande' => date('Y-m-d H:i:s'),
|
|
'montant_demande' => $this->request->getPost('discount'),
|
|
'total_price' => $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;'>Payé</span>";
|
|
} elseif ($order_data['paid_status'] == 2) {
|
|
$paid_status = "<span style='color: orange; font-weight: bold;'>En Attente</span>";
|
|
} else {
|
|
$paid_status = "<span style='color: red; font-weight: bold;'>Refusé</span>";
|
|
}
|
|
|
|
// Génération du HTML
|
|
$html .= '<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
|
|
<style>
|
|
body { font-size: 14px; font-family: Arial, sans-serif; }
|
|
.invoice-container {
|
|
max-width: 750px; /* Réduire la largeur du cadre */
|
|
margin: 20px auto;
|
|
padding: 20px;
|
|
border: 2px solid #007bff; /* Bordure plus visible */
|
|
border-radius: 10px;
|
|
background: #f0f8ff; /* Couleur de fond plus douce */
|
|
}
|
|
.invoice-header {
|
|
background: #007bff;
|
|
color: white;
|
|
text-align: center;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
padding: 10px;
|
|
border-radius: 10px 10px 0 0;
|
|
}
|
|
.invoice-footer {
|
|
background: #007bff;
|
|
color: white;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
border-radius: 0 0 10px 10px;
|
|
margin-top: 12px;
|
|
}
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
th { background: #e9ecef; }
|
|
p, strong { color: #333; }
|
|
@media print {
|
|
body { font-size: 14px; font-family: Arial, sans-serif, margin: 1cm; }
|
|
|
|
@page { margin: 0; }
|
|
|
|
.invoice-container {
|
|
max-width: 750px;
|
|
margin: 20px auto;
|
|
padding: 20px;
|
|
border: 2px solid #007bff;
|
|
border-radius: 10px;
|
|
background: #f0f8ff;
|
|
}
|
|
|
|
.invoice-header {
|
|
background: #007bff !important;
|
|
color: white !important;
|
|
text-align: center;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
padding: 10px;
|
|
border-radius: 10px 10px 0 0;
|
|
}
|
|
|
|
input {
|
|
border: none;
|
|
outline: none;
|
|
width: 100%;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.invoice-footer {
|
|
background: #007bff !important;
|
|
color: white !important;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
border-radius: 0 0 10px 10px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
th { background: #e9ecef; }
|
|
p, strong { color: #333; } a
|
|
|
|
/* Supprimer l\'ombre et les bordures non nécessaires */
|
|
.invoice-container { box-shadow: none !important; border: none !important; }
|
|
|
|
/* Éviter que les couleurs soient supprimées à l\'impression */
|
|
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body onload="window.print();">
|
|
<div class="invoice-container">
|
|
<div class="invoice-header" style="background: #007bff;color: white;text-align: center;font-size: 18px;font-weight: bold;padding: 10px;border-radius: 10px 10px 0 0; ">
|
|
' . esc($company_info['company_name']) . '
|
|
</div>
|
|
<div style="display: flex;justify-content: space-around;margin-top: 3%;">
|
|
<div>
|
|
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
|
|
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
|
|
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
|
|
<p style="display: flex; gap: 10px;">
|
|
<strong>Contact :</strong>
|
|
<span>' . esc($company_info['phone']) . '</span>
|
|
<span>' . esc($company_info['phone2']) . '</span>
|
|
</p>
|
|
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
|
|
</div>
|
|
<div>
|
|
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
|
|
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
|
|
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
|
|
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
|
|
<br >
|
|
<br >
|
|
<p><strong>Antananarivo le</strong> ' . esc(date('d/m/Y')) . '</p>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Marque</th>
|
|
<th>Moteur</th>
|
|
<th>Puissance</th>
|
|
<th>Prix</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>';
|
|
|
|
foreach ($orders_items as $item) {
|
|
$product_data = $Products->getProductData($item['product_id']);
|
|
|
|
// ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE
|
|
$puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : ($product_data['puissance'] ?? '');
|
|
|
|
$html .= '<tr>
|
|
<td>' . esc($product_data['sku']) . '</td>
|
|
<td>' . esc($product_data['numero_de_moteur']) . '</td>
|
|
<td><input type="text" value="' . esc($puissance_affichee) . '"></td> ✅
|
|
<td style="text-align:right;">' . number_format((float) $item['amount'], 2, '.', ' ') . '</td>
|
|
</tr>';
|
|
}
|
|
|
|
$html .= ' </tbody>
|
|
</table>
|
|
<table class="table">
|
|
<tr>
|
|
<th>Total:</th>
|
|
<td>' . number_format(((float) $order_data['gross_amount'] - ((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '</td>
|
|
</tr>
|
|
<tr>
|
|
<th>TVA:</th>
|
|
<td>' . number_format((((float) $order_data['gross_amount'] * 0.2)), 2, '.', ' ') . '</td>
|
|
</tr>';
|
|
|
|
$html .= '<tr>
|
|
<th>Réduction:</th>
|
|
<td>' . number_format((float) $order_data['discount'], 2, '.', ' ') . '</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Total à payer:</th>
|
|
<td><strong>' . number_format((float) ($order_data['net_amount']), 2, '.', ' ') . '</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Statut:</th>
|
|
<td>' . $paid_status . '</td>
|
|
</tr>';
|
|
|
|
// Vérification et ajout des informations de paiement
|
|
if (!empty($order_data['order_payment_mode'])) {
|
|
$html .= '<tr>
|
|
<th>Mode de paiement:</th>
|
|
<td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td>
|
|
</tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_1'])) {
|
|
$html .= '<tr>
|
|
<th>Tranche 1:</th>
|
|
<td><strong>' . number_format((float) $order_data['tranche_1'], 2, '.', ' ') . '</strong></td>
|
|
</tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_2'])) {
|
|
$html .= '<tr>
|
|
<th>Tranche 2:</th>
|
|
<td><strong>' . number_format((float) $order_data['tranche_2'], 2, '.', ' ') . '</strong></td>
|
|
</tr>';
|
|
}
|
|
|
|
$html .= '</table>
|
|
<div style="display: flex;align-items: center;justify-content: space-around;margin-bottom: 5%;">
|
|
<div>
|
|
<p style="font-weight:bold;">L\'acheteur</p>
|
|
</div>
|
|
<div>
|
|
<p style="font-weight:bold;">Le vendeur</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="invoice-footer">
|
|
Merci pour votre achat !<br>
|
|
<strong style="color:white;">Original</strong>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
return $this->response->setBody($html);
|
|
}
|
|
}
|
|
|
|
public function print(int $id)
|
|
{
|
|
$this->verifyRole('viewOrder');
|
|
|
|
if ($id) {
|
|
$Orders = new Orders();
|
|
$Company = new Company();
|
|
$Products = new Products();
|
|
$OrderItems = new OrderItems();
|
|
|
|
$order_data = $Orders->getOrdersData($id);
|
|
$orders_items = $OrderItems->getOrdersItemData($id);
|
|
$company_info = $Company->getCompanyData(1);
|
|
|
|
$paid_status = ($order_data['paid_status'] == 1)
|
|
? "<span style='color: green; font-weight: bold;'>Payé</span>"
|
|
: "<span style='color: red; font-weight: bold;'>Non payé</span>";
|
|
|
|
foreach ($orders_items as $index => $item) {
|
|
$product_data = $Products->getProductData($item['product_id']);
|
|
|
|
// ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE (si modifiée), sinon celle du produit
|
|
$puissance_affichee = !empty($item['puissance']) ? $item['puissance'] : ($product_data['puissance'] ?? '');
|
|
|
|
echo '<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
|
|
<style>
|
|
body { font-size: 14px; font-family: Arial, sans-serif; margin: 0; padding: 0; }
|
|
.invoice-container {
|
|
max-width: 750px;
|
|
margin: 20px auto;
|
|
padding: 20px;
|
|
border: 2px solid #007bff;
|
|
border-radius: 10px;
|
|
background: #f0f8ff;
|
|
page-break-after: always;
|
|
}
|
|
.invoice-header {
|
|
background: #007bff !important;
|
|
color: white;
|
|
text-align: center;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
padding: 10px;
|
|
border-radius: 10px 10px 0 0;
|
|
}
|
|
.invoice-footer {
|
|
background: #007bff !important;
|
|
color: white;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
padding: 10px;
|
|
border-radius: 0 0 10px 10px;
|
|
margin-top: 12px;
|
|
}
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
th { background: #e9ecef; }
|
|
input {
|
|
border: none;
|
|
outline: none;
|
|
width: 100%;
|
|
font-size: 14px;
|
|
}
|
|
@media print {
|
|
body { font-size: 14px; font-family: Arial, sans-serif, margin: 1cm; }
|
|
|
|
@page { margin: 0; }
|
|
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
body, .invoice-container {
|
|
margin: 0;
|
|
padding: 0;
|
|
border: none;
|
|
box-shadow: none;
|
|
}
|
|
.invoice-container {
|
|
page-break-after: always;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body onload="window.print()">
|
|
<div class="invoice-container">
|
|
<div class="invoice-header">' . esc($company_info['company_name']) . '</div>
|
|
|
|
<div style="display: flex; justify-content: space-around; margin-top: 3%;">
|
|
<div>
|
|
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
|
|
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
|
|
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
|
|
<p style="display: flex; gap: 10px;">
|
|
<strong>Contact :</strong>
|
|
<span>' . esc($company_info['phone']) . '</span>
|
|
<span>' . esc($company_info['phone2']) . '</span>
|
|
</p>
|
|
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
|
|
</div>
|
|
<div>
|
|
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
|
|
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
|
|
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
|
|
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
|
|
<br><br>
|
|
<p><strong>Antananarivo le</strong> ' . esc(date('d/m/Y')) . '</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Marque</th>
|
|
<th>Moteur</th>
|
|
<th>Puissance</th>
|
|
<th>Prix</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>' . esc($product_data['sku']) . '</td>
|
|
<td>' . esc($product_data['numero_de_moteur']) . '</td>
|
|
<td><input type="text" value="' . esc($puissance_affichee) . '" placeholder="Remplir ici"></td>
|
|
<td style="text-align:right;">' . number_format((float)$item['amount'], 2, '.', ' ') . '</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<table class="table">
|
|
<tr>
|
|
<th>Total:</th>
|
|
<td>' . number_format($item['amount'] - ($item['amount'] * 0.2), 2, '.', ' ') . '</td>
|
|
</tr>
|
|
<tr>
|
|
<th>TVA:</th>
|
|
<td>' . number_format($item['amount'] * 0.2, 2, '.', ' ') . '</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Réduction:</th>
|
|
<td>' . number_format($order_data['discount'], 2, '.', ' ') . '</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Total à payer:</th>
|
|
<td><strong>' . number_format($item['amount'] - $order_data['discount'], 2, '.', ' ') . '</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Statut:</th>
|
|
<td>' . $paid_status . '</td>
|
|
</tr>';
|
|
|
|
if (!empty($order_data['order_payment_mode'])) {
|
|
echo '<tr><th>Mode de paiement:</th><td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td></tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_1'])) {
|
|
echo '<tr><th>Tranche 1:</th><td><strong>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . '</strong></td></tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_2'])) {
|
|
echo '<tr><th>Tranche 2:</th><td><strong>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . '</strong></td></tr>';
|
|
}
|
|
|
|
echo '</table>
|
|
|
|
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
|
|
<div><p style="font-weight:bold;">L\'acheteur</p></div>
|
|
<div><p style="font-weight:bold;">Le vendeur</p></div>
|
|
</div>
|
|
|
|
<div class="invoice-footer">
|
|
Merci pour votre achat !<br>
|
|
<strong style="color:white;">Original</strong>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
}
|
|
}
|
|
}
|
|
|
|
public function remove()
|
|
{
|
|
$this->verifyRole('deleteOrder');
|
|
$order_id = $this->request->getPost('order_id');
|
|
$response = [];
|
|
|
|
if ($order_id) {
|
|
$Orders = new Orders();
|
|
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;'>Payé</span>"
|
|
: "<span style='color: red; font-weight: bold;'>Refusé</span>";
|
|
|
|
// STYLE COMMUN
|
|
$style = '
|
|
<style>
|
|
body { font-family: Arial, sans-serif; font-size:14px; margin:0; padding:0; }
|
|
.invoice-container { max-width:750px; margin:20px auto; padding:20px;
|
|
border:2px solid #007bff; border-radius:10px; page-break-after:always; }
|
|
.invoice-header { background:#007bff !important; color:#fff; text-align:center;
|
|
font-size:18px; padding:10px; border-radius:10px 10px 0 0; }
|
|
.invoice-footer { background:#007bff !important; color:#fff; text-align:center;
|
|
font-size:14px; padding:10px; border-radius:0 0 10px 10px; margin-top:12px; }
|
|
.info { display:flex; justify-content:space-between; margin-top:15px; }
|
|
table { width:100%; border-collapse:collapse; margin-top:15px; }
|
|
th, td { border:1px solid #ddd; padding:8px; text-align:left; }
|
|
th { background:#e9ecef; }
|
|
input { border:none; outline:none; width:100%; font-size:14px; background:transparent; }
|
|
.summary { width: 80%; margin: 20px 0; margin-right: 0; }
|
|
.summary th, .summary td { border:none; padding:4px; text-align:right; }
|
|
.summary th { text-align:left; }
|
|
@media print {
|
|
body { font-size: 14px; font-family: Arial, sans-serif; margin: 1cm; }
|
|
@page { margin: 0; }
|
|
* { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
|
}
|
|
</style>
|
|
';
|
|
|
|
// --- FACTURES : Une par produit ---
|
|
foreach ($items as $item) {
|
|
$product_data = $Products->getProductData($item['product_id']);
|
|
|
|
// ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE
|
|
$puissance_affichee = !empty($item['puissance'])
|
|
? $item['puissance']
|
|
: (!empty($product_data['puissance']) ? $product_data['puissance'] : '');
|
|
|
|
$unitPrice = (float)$item['amount'];
|
|
$quantity = isset($item['qty']) ? (int)$item['qty'] : 1;
|
|
$subtotal = $unitPrice * $quantity;
|
|
$vatAmount = $subtotal * 0.2;
|
|
$discount = (float)$order_data['discount'];
|
|
$totalNet = $subtotal + $vatAmount - $discount;
|
|
|
|
echo '<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
|
|
' . $style . '
|
|
</head>
|
|
<body onload="window.print()">
|
|
<div class="invoice-container">
|
|
<div class="invoice-header">FACTURE</div>
|
|
<div class="info">
|
|
<div>
|
|
<p><strong>Facture ID :</strong> ' . esc($order_data['bill_no']) . '</p>
|
|
<p><strong>NIF :</strong> ' . esc($company_info['NIF']) . '</p>
|
|
<p><strong>STAT :</strong> ' . esc($company_info['STAT']) . '</p>
|
|
<p><strong>Contact :</strong> ' . esc($company_info['phone']) . ' / ' . esc($company_info['phone2']) . '</p>
|
|
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
|
|
</div>
|
|
<div>
|
|
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
|
|
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
|
|
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
|
|
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table>
|
|
<thead><tr><th>Marque</th><th>Moteur</th><th>Puissance (CC)</th><th>Prix unitaire</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>' . esc($product_data['sku']) . '</td>
|
|
<td>' . esc($product_data['numero_de_moteur']) . '</td>
|
|
<td><input type="text" value="' . esc($puissance_affichee) . '" readonly></td>
|
|
<td>' . number_format($unitPrice, 2, '.', ' ') . '</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<table class="table summary">
|
|
<tr><th>Total HT:</th><td>' . number_format($subtotal, 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>TVA (20%):</th><td>' . number_format($vatAmount, 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>Réduction:</th><td>' . number_format($discount, 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>Total à payer:</th><td><strong>' . number_format($totalNet, 2, '.', ' ') . ' Ar</strong></td></tr>
|
|
<tr><th>Statut:</th><td>' . $paid_status . '</td></tr>';
|
|
|
|
if (!empty($order_data['order_payment_mode'])) {
|
|
echo '<tr><th>Mode de paiement:</th><td>' . esc($order_data['order_payment_mode']) . '</td></tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_1'])) {
|
|
echo '<tr><th>Tranche 1:</th><td>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . ' Ar</td></tr>';
|
|
}
|
|
|
|
if (!empty($order_data['tranche_2'])) {
|
|
echo '<tr><th>Tranche 2:</th><td>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . ' Ar</td></tr>';
|
|
}
|
|
|
|
echo '</table>
|
|
|
|
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
|
|
<div><p style="font-weight:bold;">L\'acheteur</p></div>
|
|
<div><p style="font-weight:bold;">Le vendeur</p></div>
|
|
</div>
|
|
|
|
<div class="invoice-footer">
|
|
Merci pour votre achat !<br>
|
|
<strong style="color:white;">Original</strong>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
}
|
|
|
|
// --- BON DE COMMANDE : une seule fois après la boucle ---
|
|
echo '<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">
|
|
' . $style . '
|
|
</head>
|
|
<body onload="window.print()">
|
|
<div class="invoice-container">
|
|
<div class="invoice-header">BON DE COMMANDE</div>
|
|
<div class="info">
|
|
<div>
|
|
<p><strong>Commande ID :</strong> ' . esc($order_data['order_no'] ?? $order_data['bill_no']) . '</p>
|
|
<p><strong>Magasin :</strong> ' . esc($this->returnStore($order_data['store_id'])) . '</p>
|
|
</div>
|
|
<div>
|
|
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p>
|
|
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p>
|
|
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p>
|
|
<p><strong>CIN:</strong> ' . esc($order_data['customer_cin']) . '</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Marque</th>
|
|
<th>Moteur</th>
|
|
<th>Puissance (CC)</th>
|
|
<th>Prix</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>';
|
|
|
|
$total_ht = 0;
|
|
foreach ($items as $item) {
|
|
$product_data = $Products->getProductData($item['product_id']);
|
|
|
|
// ✅ PRIORITÉ À LA PUISSANCE DE LA COMMANDE
|
|
$puissance_affichee = !empty($item['puissance'])
|
|
? $item['puissance']
|
|
: (!empty($product_data['puissance']) ? $product_data['puissance'] : '');
|
|
|
|
$total_ht += (float)$item['amount'];
|
|
|
|
echo '<tr>
|
|
<td>' . esc($product_data['sku']) . '</td>
|
|
<td>' . esc($product_data['numero_de_moteur']) . '</td>
|
|
<td><input type="text" value="' . esc($puissance_affichee) . '" style="border:none;width:100%;outline:none;"></td>
|
|
<td style="text-align:right;">' . number_format((float)$item['amount'], 2, '.', ' ') . '</td>
|
|
</tr>';
|
|
}
|
|
|
|
$tva = $total_ht * 0.2;
|
|
$total_ttc = $total_ht + $tva - $order_data['discount'];
|
|
|
|
echo '</tbody>
|
|
</table>
|
|
|
|
<table class="table summary">
|
|
<tr><th>Total HT:</th><td>' . number_format($total_ht, 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>TVA (20%):</th><td>' . number_format($tva, 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>Réduction:</th><td>' . number_format($order_data['discount'], 2, '.', ' ') . ' Ar</td></tr>
|
|
<tr><th>Total à payer:</th><td><strong>' . number_format($total_ttc, 2, '.', ' ') . ' Ar</strong></td></tr>
|
|
</table>
|
|
|
|
<div style="display: flex; justify-content: space-around; margin-top: 5%;">
|
|
<div><p style="font-weight:bold;">L\'acheteur</p></div>
|
|
<div><p style="font-weight:bold;">Le vendeur</p></div>
|
|
</div>
|
|
|
|
<div class="invoice-footer">
|
|
Merci pour votre commande !<br>
|
|
<strong style="color:white;">Original</strong>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
}
|
|
// PRINT5 - Facture détaillée avec conditions générales
|
|
// ====================================
|
|
public function print5(int $id)
|
|
{
|
|
$this->verifyRole('viewOrder');
|
|
|
|
if (!$id) {
|
|
throw new \CodeIgniter\Exceptions\PageNotFoundException();
|
|
}
|
|
|
|
$Orders = new Orders();
|
|
$Company = new Company();
|
|
$OrderItems = new OrderItems();
|
|
|
|
$order = $Orders->getOrdersData($id);
|
|
$items = $OrderItems->getOrdersItemData($id);
|
|
$company = $Company->getCompanyData(1);
|
|
$today = date('d/m/Y');
|
|
|
|
// ✅ 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;'>Payé</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>';
|
|
}
|
|
|
|
}
|