Sarobidy22 3 weeks ago
parent
commit
778e8332ee
  1. 29
      app/Config/Routes.php
  2. 106
      app/Controllers/AvanceController.php
  3. 486
      app/Controllers/OrderController.php
  4. 283
      app/Models/Avance.php
  5. 25
      app/Models/Orders.php
  6. 121
      app/Models/Products.php
  7. 27
      app/Models/Remise.php
  8. 153
      app/Views/orders/edit.php
  9. 203
      app/Views/orders/index.php

29
app/Config/Routes.php

@ -174,11 +174,11 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('update/(:num)', [ProductCOntroller::class, 'update']);
$routes->post('update/(:num)', [ProductCOntroller::class, 'update']);
$routes->post('remove', [ProductCOntroller::class, 'remove']);
// $routes->get('generateqrcode/(:num)', [QrCodeController::class, 'generate']);
$routes->post('assign_store', [ProductCOntroller::class, 'assign_store']);
$routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']);
$routes->post('checkProductAvailability', [ProductCOntroller::class, 'checkProductAvailability']);
});
/**
* route for the orders
@ -296,10 +296,12 @@ $routes->group('/sortieCaisse', function ($routes) {
});
// avance
// ✅ DANS app/Config/Routes.php
$routes->group('/avances', function ($routes) {
$routes->get('/', [AvanceController::class, 'index']);
// Routes pour récupérer les données (GET)
// Routes pour récupérer les données (GET)
$routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']);
$routes->get('fetchAvanceBecameOrder', [AvanceController::class, 'fetchAvanceBecameOrder']);
$routes->get('fetchExpiredAvance', [AvanceController::class, 'fetchExpiredAvance']);
@ -310,22 +312,27 @@ $routes->group('/avances', function ($routes) {
$routes->get('getFullInvoiceForPrint/(:num)', [AvanceController::class, 'getFullInvoiceForPrint/$1']);
$routes->get('printInvoice/(:num)', [AvanceController::class, 'printInvoice/$1']);
// Routes POST pour modifications
// Routes POST pour modifications
$routes->post('createAvance', [AvanceController::class, 'createAvance']);
$routes->post('updateAvance', [AvanceController::class, 'updateAvance']);
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
$routes->post('notifyPrintInvoice', [AvanceController::class, 'notifyPrintInvoice']);
// ✅ AJOUTER CETTE ROUTE MANQUANTE
$routes->post('processExpiredAvances', [AvanceController::class, 'processExpiredAvances']);
// ✅ Route CRON (optionnel)
// ✅ CORRECTION : Routes pour paiement et conversion
$routes->post('payAvance', [AvanceController::class, 'payAvance']);
// ✅ AJOUT : Routes GET ET POST pour la conversion manuelle
$routes->get('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']);
$routes->post('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']);
// Route pour forcer la conversion d'une avance spécifique
$routes->get('forceConvertToOrder/(:num)', [AvanceController::class, 'forceConvertToOrder/$1']);
// Route CRON (optionnel)
$routes->get('checkDeadlineAlerts', [AvanceController::class, 'checkDeadlineAlerts']);
$routes->post('payAvance', 'AvanceController::payAvance');
$routes->get('forceConvertToOrder/(:num)', 'AvanceController::forceConvertToOrder/$1');
$routes->post('checkAndConvertCompleted', 'AvanceController::checkAndConvertCompleted');
});
// historique
$routes->group('historique', ['filter' => 'auth'], static function ($routes) {
$routes->get('/', 'HistoriqueController::index');

106
app/Controllers/AvanceController.php

@ -2114,6 +2114,13 @@ public function payAvance()
$avance_id = $this->request->getPost('avance_id');
$montant_paye = (float)$this->request->getPost('montant_paye');
if (!$avance_id || $montant_paye <= 0) {
return $this->response->setJSON([
'success' => false,
'message' => 'Données invalides'
]);
}
$avanceModel = new \App\Models\Avance();
$avance = $avanceModel->find($avance_id);
@ -2124,52 +2131,62 @@ public function payAvance()
]);
}
// Calcul du nouveau montant dû
// ✅ Vérifier si déjà convertie
if ($avance['is_order'] == 1) {
return $this->response->setJSON([
'success' => false,
'message' => '⚠️ Cette avance a déjà été convertie en commande'
]);
}
// ✅ Calcul nouveau montant dû
$amount_due = max(0, (float)$avance['amount_due'] - $montant_paye);
// ✅ Mise à jour de l'avance
// ✅ Mise à jour avance
$avanceModel->update($avance_id, [
'avance_amount' => (float)$avance['avance_amount'] + $montant_paye,
'amount_due' => $amount_due,
]);
// ✅ NOUVEAU : Conversion automatique UNIQUEMENT pour avances TERRE
log_message('info', "💰 Paiement {$montant_paye} Ar sur avance {$avance_id} (Type: {$avance['type_avance']})");
// ✅ CONVERSION si paiement complet
if ($amount_due <= 0) {
if ($avance['type_avance'] === 'terre') {
// ✅ Avance TERRE complète → Conversion en commande
log_message('info', "💰 Avance TERRE {$avance_id} complétée ! Conversion en commande...");
log_message('info', "🔄 Avance TERRE {$avance_id} complétée → Conversion en commande...");
$order_id = $avanceModel->convertToOrder($avance_id);
if ($order_id) {
log_message('info', "✅ Conversion réussie → Commande {$order_id}");
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance TERRE a été convertie en commande.',
'message' => '✅ Paiement effectué ! L\'avance a été convertie en commande.',
'converted' => true,
'type' => 'terre',
'order_id' => $order_id,
'redirect_url' => site_url('orders/update/' . $order_id)
]);
} else {
log_message('error', "Échec conversion avance TERRE {$avance_id} en commande");
log_message('error', "❌ Échec conversion avance {$avance_id}");
return $this->response->setJSON([
'success' => true,
'message' => '⚠️ Paiement effectué mais erreur lors de la création de la commande.',
'converted' => false
'success' => false,
'message' => '⚠️ Paiement enregistré mais erreur lors de la conversion. Contactez l\'administrateur.'
]);
}
} else {
// ✅ Avance MER complète → Reste dans la liste
log_message('info', "💰 Avance MER {$avance_id} complétée ! Elle reste dans la liste des avances.");
// ✅ Avance MER complète
log_message('info', "✅ Avance MER {$avance_id} complétée (pas de conversion)");
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance MER est maintenant complète.',
'converted' => false,
'type' => 'mere',
'status' => 'completed'
'type' => 'mere'
]);
}
}
@ -2177,8 +2194,9 @@ public function payAvance()
// ✅ Paiement partiel
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement partiel enregistré avec succès',
'message' => '✅ Paiement partiel enregistré',
'amount_due_remaining' => $amount_due,
'amount_due_formatted' => number_format($amount_due, 0, ',', ' ') . ' Ar',
'type' => $avance['type_avance']
]);
}
@ -2187,16 +2205,33 @@ public function payAvance()
*/
public function forceConvertToOrder($avance_id)
{
$this->verifyRole('updateAvance'); // Adapter selon vos permissions
$this->verifyRole('updateAvance');
$avanceModel = new \App\Models\Avance();
$avance = $avanceModel->find($avance_id);
if (!$avance) {
session()->setFlashdata('errors', 'Avance introuvable');
return redirect()->back();
}
if ($avance['type_avance'] !== 'terre') {
session()->setFlashdata('errors', 'Seules les avances TERRE peuvent être converties');
return redirect()->back();
}
if ($avance['amount_due'] > 0) {
session()->setFlashdata('errors', 'L\'avance doit être complètement payée avant conversion');
return redirect()->back();
}
$order_id = $avanceModel->convertToOrder($avance_id);
if ($order_id) {
session()->setFlashdata('success', 'Avance convertie en commande avec succès !');
return redirect()->to('orders/update/' . $order_id);
} else {
session()->setFlashdata('errors', 'Erreur lors de la conversion de l\'avance.');
session()->setFlashdata('errors', 'Erreur lors de la conversion');
return redirect()->back();
}
}
@ -2208,16 +2243,33 @@ public function forceConvertToOrder($avance_id)
public function checkAndConvertCompleted()
{
try {
$Avance = new Avance();
log_message('info', "=== DÉBUT checkAndConvertCompleted (manuel) ===");
// ✅ Récupérer uniquement les avances TERRE complètes non converties
$completedTerreAvances = $Avance->getCompletedNotConverted();
$Avance = new \App\Models\Avance();
// ✅ Récupérer toutes les avances TERRE complètes non converties
$completedTerreAvances = $Avance
->where('type_avance', 'terre')
->where('amount_due <=', 0)
->where('is_order', 0)
->where('active', 1)
->findAll();
if (empty($completedTerreAvances)) {
return $this->response->setJSON([
'success' => true,
'message' => 'Aucune avance complète à convertir',
'converted' => 0
]);
}
$convertedCount = 0;
$errorCount = 0;
$details = [];
foreach ($completedTerreAvances as $avance) {
log_message('info', "🔍 Traitement avance {$avance['avance_id']}...");
$order_id = $Avance->convertToOrder($avance['avance_id']);
if ($order_id) {
@ -2225,32 +2277,35 @@ public function checkAndConvertCompleted()
$details[] = [
'avance_id' => $avance['avance_id'],
'customer' => $avance['customer_name'],
'type' => 'terre',
'order_id' => $order_id,
'status' => 'success'
];
log_message('info', "✅ Avance {$avance['avance_id']} → Commande {$order_id}");
} else {
$errorCount++;
$details[] = [
'avance_id' => $avance['avance_id'],
'customer' => $avance['customer_name'],
'type' => 'terre',
'status' => 'error'
'status' => 'error',
'reason' => 'Voir logs pour détails'
];
log_message('error', "❌ Échec conversion avance {$avance['avance_id']}");
}
}
log_message('info', "=== FIN checkAndConvertCompleted - Convertis: {$convertedCount}, Erreurs: {$errorCount} ===");
return $this->response->setJSON([
'success' => true,
'message' => 'Vérification terminée',
'converted' => $convertedCount,
'errors' => $errorCount,
'note' => 'Seules les avances TERRE sont converties. Les avances MER restent dans la liste.',
'total_checked' => count($completedTerreAvances),
'details' => $details
]);
} catch (\Exception $e) {
log_message('error', "Erreur checkAndConvertCompleted: " . $e->getMessage());
log_message('error', "Erreur checkAndConvertCompleted: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur: ' . $e->getMessage()
@ -2258,4 +2313,5 @@ public function checkAndConvertCompleted()
}
}
}

486
app/Controllers/OrderController.php

@ -41,26 +41,46 @@ class OrderController extends AdminController
* @param int $store_id
* @return string
*/
private function generateBillNo(int $store_id): string
private function generateSimpleSequentialBillNo(int $store_id): string
{
// Mapping des préfixes par magasin
$storePrefixes = [
1 => 'ANTS', // ANTSAKAVIRO
2 => 'BESA', // BESARETY
3 => 'BYPA', // BYPASS
4 => 'TOAM', // TOAMASINA
1 => 'ANTS',
2 => 'BESA',
3 => 'BYPA',
4 => 'TOAM',
];
// 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;
$prefix = $storePrefixes[$store_id] ?? 'STORE';
$db = \Config\Database::connect();
$lastBill = $db->table('orders')
->select('bill_no')
->like('bill_no', $prefix . '-', 'after')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if ($lastBill && !empty($lastBill['bill_no'])) {
// Extraire le numéro (ex: "BESA-001" -> 1)
preg_match('/-(\d+)$/', $lastBill['bill_no'], $matches);
if (isset($matches[1])) {
$lastNumber = (int)$matches[1];
$newNumber = $lastNumber + 1;
} else {
$newNumber = 1;
}
} else {
$newNumber = 1;
}
// Formater avec zéros (ex: 001, 002, 010, 100)
return $prefix . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT);
}
public function fetchOrdersData()
{
helper(['url', 'form']);
@ -75,53 +95,102 @@ class OrderController extends AdminController
// POUR CAISSIÈRE
// ========================================
if ($users['group_name'] == "Caissière") {
$Remise = new Remise();
foreach ($data as $key => $value) {
$date_time = date('d-m-Y h:i a', strtotime($value['date_time']));
$buttons = '';
// 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>';
$discount = (float)$value['discount'];
// ✅ VÉRIFICATION : Si la commande est refusée (paid_status = 0), aucun bouton
if ($value['paid_status'] == 0) {
$buttons = '<span class="label label-danger"><i class="fa fa-ban"></i> Accès bloqué</span>';
} else {
// ✅ Bouton imprimer
if (in_array('viewOrder', $this->permission)) {
// CAS 1 : Commande payée (1) ou livrée (3) → Toujours afficher imprimer
if (in_array($value['paid_status'], [1, 3])) {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 2 : Commande en attente (2) SANS remise → Afficher imprimer
elseif ($value['paid_status'] == 2 && $discount == 0) {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 3 : Commande en attente (2) AVEC remise validée → Afficher imprimer
elseif ($value['paid_status'] == 2 && $Remise->hasRemiseValidatedForOrder($value['id'])) {
$buttons .= '<a target="_blank" href="' . site_url('orders/printDiv/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-print"></i></a>';
}
// CAS 4 : Commande en attente (2) AVEC remise en attente → Indicateur
elseif ($value['paid_status'] == 2 && $Remise->hasRemisePendingForOrder($value['id'])) {
$buttons .= '<button class="btn btn-warning btn-sm" disabled title="En attente de validation"><i class="fa fa-clock-o"></i></button>';
}
}
// ✅ Bouton voir
if (in_array('viewOrder', $this->permission)) {
// Afficher pour toutes les commandes sauf celles refusées
// Et pour les commandes en attente : seulement si pas de remise OU remise validée
if ($value['paid_status'] == 2) {
// En attente : vérifier la remise
if ($discount == 0 || $Remise->hasRemiseValidatedForOrder($value['id'])) {
$buttons .= '
<a
href="#"
data-order-id="' . $value['id'] . '"
class="btn btn-default btn-view"
data-toggle="tooltip"
title="Voir">
<i class="fa fa-eye"></i>
</a>';
}
} else {
// Payé ou Livré : toujours afficher
$buttons .= '
<a
href="#"
data-order-id="' . $value['id'] . '"
class="btn btn-default btn-view"
data-toggle="tooltip"
title="Voir">
<i class="fa fa-eye"></i>
</a>';
}
}
// ✅ Bouton modifier (seulement si paid_status = 2)
if (in_array('updateOrder', $this->permission)
&& $users["store_id"] == $value['store_id']
&& $value['paid_status'] == 2) {
// CAS 1 : Pas de remise → Afficher le bouton modifier
if ($discount == 0) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>';
}
// CAS 2 : Remise validée → Afficher le bouton modifier
elseif ($Remise->hasRemiseValidatedForOrder($value['id'])) {
$buttons .= ' <a href="' . site_url('orders/update/' . $value['id']) . '" class="btn btn-primary"><i class="fa fa-pencil"></i></a>';
}
}
}
// Statut de paiement
if ($value['paid_status'] == 1) {
$paid_status = '<span class="label label-success">payé</span>';
$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>';
$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) {
@ -132,7 +201,7 @@ class OrderController extends AdminController
$statuDate = '<span class="label label-danger"> depuis ' . $daysPassed . ' Jours</span>';
}
}
$result['data'][$key] = [
$value['product_names'],
$value['user_name'],
@ -145,7 +214,7 @@ class OrderController extends AdminController
}
return $this->response->setJSON($result);
}
// ========================================
// POUR DIRECTION OU DAF
// ========================================
@ -213,7 +282,7 @@ class OrderController extends AdminController
}
// ========================================
// POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, etc.)
// POUR LES AUTRES UTILISATEURS (COMMERCIALE, SECURITE, Cheffe d'Agence)
// ========================================
else {
foreach ($data as $key => $value) {
@ -281,20 +350,58 @@ class OrderController extends AdminController
}
}
$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
];
// ✅ CONDITION SPÉCIALE POUR SECURITE : remplacer Prix demandé et Prix de vente par Marque et Désignation
if ($users['group_name'] == "SECURITE") {
// Récupérer les infos produit
$OrderItems = new OrderItems();
$Products = new Products();
$Brands = new Brands();
$order_items = $OrderItems->getOrdersItemData($value['id']);
$marque = 'N/A';
$numero_serie = 'N/A';
if (!empty($order_items[0])) {
$product = $Products->getProductData($order_items[0]['product_id']);
if ($product) {
$numero_serie = $product['sku'] ?? 'N/A'; // ✅ Numéro de série
if (!empty($product['marque'])) {
$brand = $Brands->find($product['marque']);
if ($brand) {
$marque = $brand['name'];
}
}
}
}
$result['data'][$key] = [
$value['product_names'],
$value['user_name'],
$date_time . " <br >" . $statuDate,
$marque, // ✅ Remplace Prix demandé
$numero_serie, // ✅ Remplace Prix de vente
$paid_status,
$buttons
];
} else {
// Pour les autres (COMMERCIALE, Cheffe d'Agence)
$result['data'][$key] = [
$value['product_names'],
$value['user_name'],
$date_time . " <br >" . $statuDate,
number_format((int) $value['discount'], 0, ',', ' '),
number_format((int) $value['gross_amount'], 0, ',', ' '),
$paid_status,
$buttons
];
}
}
return $this->response->setJSON($result);
}
}
/**
* Affiche le formulaire create avec les données d'une commande existante
* Pour le rôle COMMERCIALE
@ -364,19 +471,25 @@ public function create()
$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']);
// Générer le numéro séquentiel
$bill_no = $this->generateSimpleSequentialBillNo($users['store_id']);
// Récupérer le type de document
$document_type = $this->request->getPost('document_type') ?? 'facture';
$posts = $this->request->getPost('product[]');
$rates = $this->request->getPost('rate_value[]');
$amounts = $this->request->getPost('amount_value[]');
$puissances = $this->request->getPost('puissance[]');
$discount = (float)$this->request->getPost('discount') ?? 0;
$gross_amount = $this->calculGross($amounts);
$net_amount = $gross_amount - $discount;
// Vérification prix minimal SI rabais existe
if ($discount > 0) {
@ -405,25 +518,38 @@ public function create()
}
}
$montant_a_payer = ($discount > 0) ? $discount : $gross_amount;
$discount = (float)$this->request->getPost('discount') ?? 0;
$gross_amount = $this->calculGross($amounts);
$net_amount = $gross_amount - $discount;
$tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0;
$tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0;
// Si des tranches sont définies, vérifier la cohérence
if ($tranche_1 > 0 && $tranche_2 > 0) {
$net_amount = $tranche_1 + $tranche_2;
} else {
$net_amount = $montant_a_payer;
$total_tranches = $tranche_1 + $tranche_2;
// S'assurer que les tranches correspondent au net_amount
if (abs($total_tranches - $net_amount) > 0.01) {
return redirect()->back()
->withInput()
->with('errors', [
'Les tranches de paiement ne correspondent pas au montant total (' .
number_format($net_amount, 0, ',', ' ') . ' Ar)'
]);
}
}
$data = [
'bill_no' => $bill_no,
'document_type' => $document_type,
'customer_name' => $this->request->getPost('customer_name'),
'customer_address' => $this->request->getPost('customer_address'),
'customer_phone' => $this->request->getPost('customer_phone'),
'customer_cin' => $this->request->getPost('customer_cin'),
'customer_type' => $this->request->getPost('customer_type'),
'source' => $this->request->getPost('source'),
'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,
@ -446,7 +572,14 @@ public function create()
$order_id = $Orders->create($data, $posts);
if ($order_id) {
// ✅ NOUVEAU : Marquer immédiatement les produits comme réservés
$productModel = new Products();
foreach ($posts as $product_id) {
$productModel->update($product_id, ['product_sold' => 1]);
}
session()->setFlashdata('success', 'Créé avec succès');
$Notification = new NotificationController();
$Stores = new Stores();
@ -856,6 +989,7 @@ public function update(int $id)
}
$dataUpdate = [
'document_type' => $this->request->getPost('document_type'), // ✅ AJOUTER CETTE LIGNE
'customer_name' => $this->request->getPost('customer_name'),
'customer_address' => $this->request->getPost('customer_address'),
'customer_phone' => $this->request->getPost('customer_phone'),
@ -1507,21 +1641,35 @@ public function update(int $id)
$this->verifyRole('deleteOrder');
$order_id = $this->request->getPost('order_id');
$response = [];
if ($order_id) {
$Orders = new Orders();
$OrderItems = new OrderItems();
$Products = new Products();
// ✅ Récupérer tous les produits de la commande
$orderItems = $OrderItems->getOrdersItemData($order_id);
// ✅ Libérer chaque produit (remettre product_sold = 0)
foreach ($orderItems as $item) {
if (!empty($item['product_id'])) {
$Products->update($item['product_id'], ['product_sold' => 0]);
}
}
// Supprimer la commande
if ($Orders->remove($order_id)) {
$response['success'] = true;
$response['messages'] = "Successfully removed";
} else {
$response['success'] = false;
$response['messages'] = "Error in the database while removing the product information";
$response['messages'] = "Error in the database while removing the order";
}
} else {
$response['success'] = false;
$response['messages'] = "Refersh the page again!!";
}
return $this->response->setJSON($response);
}
@ -1550,6 +1698,39 @@ public function update(int $id)
return $this->render_template('orders/createbyid', $data);
}
public function printDiv(int $id)
{
$Orders = new Orders();
$order = $Orders->getOrdersData($id);
$docType = $order['document_type'] ?? 'facture';
// Rediriger vers la bonne méthode selon le type
switch($docType) {
case 'facture':
return $this->print31($id); // Factures individuelles
case 'bl':
return $this->print7($id); // Bon de livraison
case 'both':
return $this->print31($id); // Factures + Bon de livraison
default:
return $this->print31($id);
}
}
public function printDivBL(int $id)
{
// Force le bon de livraison
return $this->print7($id);
}
public function printDivBLF(int $id)
{
// Force facture + bon de livraison
return $this->print31($id);
}
// update caisse
public function update_caisse($data)
{
@ -1801,6 +1982,24 @@ public function print5(int $id)
$company = $Company->getCompanyData(1);
$today = date('d/m/Y');
// ✅ RÉCUPÉRER LE TYPE DE DOCUMENT
$documentType = $order['document_type'] ?? 'facture';
$documentTitle = '';
switch($documentType) {
case 'facture':
$documentTitle = 'FACTURE';
break;
case 'bl':
$documentTitle = 'BON DE LIVRAISON';
break;
case 'both':
$documentTitle = 'FACTURE & BON DE LIVRAISON';
break;
default:
$documentTitle = 'FACTURE';
}
// ✅ Vérifier si c'est une avance "sur mer"
$isAvanceMere = false;
foreach ($items as $item) {
@ -1823,7 +2022,7 @@ public function print5(int $id)
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Facture '.$order['bill_no'].'</title>
<title>'.$documentTitle.' '.$order['bill_no'].'</title>
<style>
/* ✅ FORMAT A4 PAYSAGE DIVISÉ EN 2 */
@page {
@ -2001,10 +2200,10 @@ public function print5(int $id)
</head>
<body onload="window.print()">
<!-- ✅ PAGE 1 : RECTO - 2 FACTURES CÔTE À CÔTE -->
<!-- ✅ PAGE 1 : RECTO - 2 DOCUMENTS CÔTE À CÔTE -->
<div class="page">';
// ✅ GÉNÉRER 2 FACTURES IDENTIQUES
// ✅ GÉNÉRER 2 DOCUMENTS IDENTIQUES
for ($i = 0; $i < 2; $i++) {
$html .= '
<div class="facture-box">
@ -2017,7 +2216,7 @@ public function print5(int $id)
</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>
<p class="facture-num">'.$documentTitle.' N° '.esc($order['bill_no']).'</p>
</div>
</div>
@ -2059,7 +2258,6 @@ public function print5(int $id)
</tr>';
}
// ✅ CORRECTION : Fermer le tableau pour avance
$html .= '
</tbody>
</table>';
@ -2097,7 +2295,6 @@ public function print5(int $id)
</tr>';
}
// ✅ Fermer le tableau pour produit normal
$html .= '
</tbody>
</table>';
@ -2322,17 +2519,33 @@ public function print7(int $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');
// ✅ RÉCUPÉRER LE TYPE DE DOCUMENT
$documentType = $order['document_type'] ?? 'bl';
$documentTitle = '';
switch($documentType) {
case 'facture':
$documentTitle = 'FACTURE';
break;
case 'bl':
$documentTitle = 'BON DE LIVRAISON';
break;
case 'both':
$documentTitle = 'FACTURE & BON DE LIVRAISON';
break;
default:
$documentTitle = 'BON DE LIVRAISON';
}
// ✅ VÉRIFIER SI C'EST UNE AVANCE "SUR MER"
$isAvanceMere = false;
foreach ($items as $item) {
@ -2342,7 +2555,6 @@ public function print7(int $id)
}
}
// ✅ LOGIQUE DE REMISE
$discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
@ -2351,12 +2563,11 @@ public function print7(int $id)
$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>
<title>'.$documentTitle.' '.$order['bill_no'].'</title>
<style>
body { font-family: Arial, sans-serif; font-size:14px; color:#000; margin:0; padding:0; }
.header { display:flex; justify-content:space-between; align-items:center; margin:20px; }
@ -2388,7 +2599,7 @@ public function print7(int $id)
</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>
<p style="margin:5px 0; font-weight:bold;">'.$documentTitle.' N° '.esc($order['bill_no']).'</p>
</div>
</div>
@ -2400,11 +2611,8 @@ public function print7(int $id)
<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>
@ -2426,7 +2634,6 @@ public function print7(int $id)
<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>';
}
@ -2441,7 +2648,6 @@ public function print7(int $id)
</table>';
} else {
// --- TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE (7 COLONNES) ---
$html .= '
<table>
<thead>
@ -2468,7 +2674,6 @@ public function print7(int $id)
$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']);
@ -2497,9 +2702,6 @@ public function print7(int $id)
</table>';
}
// ========================================
// TABLEAU RÉCAPITULATIF (IDENTIQUE POUR TOUS)
// ========================================
$html .= '
<table style="width:calc(100% - 40px); margin:0 20px 20px;">
<tr>
@ -2555,7 +2757,6 @@ public function print7(int $id)
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>
@ -2576,6 +2777,7 @@ public function print7(int $id)
return $this->response->setBody($html);
}
// ====================================
// PRINT31 - Facture + Bon de commande (pages séparées)
// ====================================
@ -2598,6 +2800,24 @@ public function print31(int $id)
$items = $OrderItems->getOrdersItemData($id);
$company_info = $Company->getCompanyData(1);
// ✅ RÉCUPÉRER LE TYPE DE DOCUMENT
$documentType = $order_data['document_type'] ?? 'facture';
$documentTitle = '';
switch($documentType) {
case 'facture':
$documentTitle = 'FACTURE';
break;
case 'bl':
$documentTitle = 'BON DE LIVRAISON';
break;
case 'both':
$documentTitle = 'FACTURE & BON DE LIVRAISON';
break;
default:
$documentTitle = 'FACTURE';
}
// ✅ Vérifier si c'est une avance "sur mer"
$isAvanceMere = false;
foreach ($items as $item) {
@ -2611,51 +2831,49 @@ public function print31(int $id)
? "<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;
$style = '
<style>
body { font-family: Arial, sans-serif; font-size:14px; color:#000; margin:0; padding:0; }
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
.header .infos { line-height:1.4; }
.header img { max-height:80px; }
.client { margin-bottom:20px; }
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
th, td { border:1px solid #000; padding:6px; text-align:left; }
th { background:#f0f0f0; }
.right { text-align:right; }
.signature { display:flex; justify-content:space-between; margin-top:50px; }
.signature div { text-align:center; }
.page-break { page-break-after: always; }
.to-fill { border-bottom: 1px dotted #000; min-width: 150px; display: inline-block; }
.conditions { page-break-before: always; padding:20px; line-height:1.5; }
@media print {
body { font-size: 14px; }
@page { margin: 1cm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
</style>';
// --- FACTURES : Une par produit ---
foreach ($items as $item) {
// ✅ 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 $style;
echo '</head><body onload="window.print()">';
echo '<div class="page-break">';
echo '<div class="header">';
@ -2668,7 +2886,7 @@ public function print31(int $id)
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; font-weight:bold;">' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '</p>';
echo '<p style="margin:5px 0;"><em>Antananarivo, le ' . date('d/m/Y') . '</em></p>';
echo '</div>';
echo '</div>';
@ -2694,7 +2912,6 @@ public function print31(int $id)
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>';
}
@ -2702,7 +2919,6 @@ public function print31(int $id)
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']);
@ -2762,31 +2978,12 @@ public function print31(int $id)
echo '</body></html>';
}
// --- BON DE COMMANDE (UNE SEULE PAGE) ---
// --- BON DE LIVRAISON (UNE SEULE PAGE) ---
echo '<!DOCTYPE html>';
echo '<html lang="fr">';
echo '<head><meta charset="utf-8">';
echo '<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '">';
echo "<style>
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 $style;
echo '</head><body>';
echo '<div class="header">';
@ -2798,7 +2995,7 @@ public function print31(int $id)
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 '<p style="margin:5px 0; font-weight:bold;">' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '</p>';
echo '</div>';
echo '</div>';
@ -2842,7 +3039,6 @@ public function print31(int $id)
$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']);

283
app/Models/Avance.php

@ -443,194 +443,178 @@ public function getProductWithBrand($product_id)
*/
public function convertToOrder(int $avance_id)
{
$db = \Config\Database::connect();
try {
$db->transStart();
$avance = $this->find($avance_id);
if (!$avance) {
log_message('error', "Avance introuvable : {$avance_id}");
log_message('error', "Avance {$avance_id} introuvable");
return false;
}
// ✅ Vérifier que c'est bien une avance sur TERRE
// ✅ VÉRIFICATION 1 : Type d'avance
if ($avance['type_avance'] !== 'terre') {
log_message('info', "Avance {$avance_id} de type '{$avance['type_avance']}' - Conversion ignorée (seules les avances TERRE sont converties)");
log_message('info', "⚠️ Avance {$avance_id} est MER, pas de conversion");
return false;
}
// ✅ Vérifier que l'avance est bien complète
if ((float)$avance['amount_due'] > 0) {
log_message('warning', "Avance TERRE {$avance_id} non complète (amount_due={$avance['amount_due']})");
// ✅ VÉRIFICATION 2 : Paiement complet
if ($avance['amount_due'] > 0) {
log_message('warning', "⚠️ Avance {$avance_id} non complète (reste: {$avance['amount_due']})");
return false;
}
// ✅ Vérifier qu'elle n'a pas déjà été convertie
if ((int)$avance['is_order'] === 1) {
log_message('info', "Avance TERRE {$avance_id} déjà convertie en commande");
return false;
// ✅ VÉRIFICATION 3 : Déjà convertie ?
if ($avance['is_order'] == 1) {
log_message('info', "⚠️ Avance {$avance_id} déjà convertie");
// ✅ Récupérer l'ID de la commande existante
$existingOrder = $db->table('orders')
->select('id')
->where('customer_name', $avance['customer_name'])
->where('customer_phone', $avance['customer_phone'])
->where('source', 'Avance convertie')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
return $existingOrder ? $existingOrder['id'] : false;
}
// ✅ Vérifier que le produit existe (obligatoire pour avance TERRE)
if (empty($avance['product_id'])) {
log_message('error', "Avance TERRE {$avance_id} sans product_id - Impossible de convertir");
// ✅ VÉRIFICATION 4 : Produit existe ?
$Products = new \App\Models\Products();
$product = $Products->find($avance['product_id']);
if (!$product) {
log_message('error', "❌ Produit {$avance['product_id']} introuvable");
return false;
}
$db = \Config\Database::connect();
$db->transStart();
// ✅ Récupérer l'utilisateur actuel
$session = session();
$user = $session->get('user');
// ✅ 1. Créer la commande
$orderModel = new \App\Models\Orders();
$bill_no = 'BILAV-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
// ✅ Générer le numéro de commande
$bill_no = $this->generateBillNo($avance['store_id']);
// ✅ Préparer les données de commande
$orderData = [
'bill_no' => $bill_no,
'customer_name' => $avance['customer_name'],
'customer_address' => $avance['customer_address'],
'customer_phone' => $avance['customer_phone'],
'customer_cin' => $avance['customer_cin'],
'date_time' => date('Y-m-d H:i:s'),
'gross_amount' => $avance['gross_amount'],
'net_amount' => $avance['gross_amount'],
'discount' => 0,
'paid_status' => 2, // En attente validation caissière
'user_id' => $avance['user_id'],
'store_id' => $avance['store_id'],
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
'tranche_1' => $avance['avance_amount'],
'tranche_2' => null,
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
'order_payment_mode_1' => null,
'bill_no' => $bill_no,
'document_type' => 'facture',
'customer_name' => $avance['customer_name'],
'customer_address' => $avance['customer_address'],
'customer_phone' => $avance['customer_phone'],
'customer_cin' => $avance['customer_cin'],
'customer_type' => 'Particulier',
'source' => 'Avance convertie', // ✅ MARQUEUR IMPORTANT
'date_time' => date('Y-m-d H:i:s'),
'gross_amount' => $avance['gross_amount'],
'net_amount' => $avance['gross_amount'],
'discount' => 0,
'paid_status' => 2, // En attente de validation
'user_id' => $user['id'] ?? $avance['user_id'],
'store_id' => $avance['store_id'],
'tranche_1' => $avance['avance_amount'],
'tranche_2' => 0,
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
];
$order_id = $orderModel->insert($orderData);
// ✅ Créer la commande
$db->table('orders')->insert($orderData);
$order_id = $db->insertID();
if (!$order_id) {
throw new \Exception("Échec création commande pour avance TERRE {$avance_id}");
throw new \Exception("Échec création commande pour avance {$avance_id}");
}
log_message('info', "✅ Commande {$bill_no} créée depuis avance TERRE {$avance_id}");
// ✅ 2. Créer les items de commande
$orderItemModel = new \App\Models\OrderItems();
$productModel = new \App\Models\Products();
$product = $productModel->find($avance['product_id']);
// ✅ CORRECTION CRITIQUE : Créer l'item de commande SANS 'qty'
$orderItemData = [
'order_id' => $order_id,
'product_id' => $avance['product_id'],
'rate' => $avance['gross_amount'],
'amount' => $avance['gross_amount'],
'puissance' => $product['puissance'] ?? '',
];
if ($product) {
$orderItemModel->insert([
'order_id' => $order_id,
'product_id' => $avance['product_id'],
'rate' => $avance['gross_amount'],
'qty' => 1,
'amount' => $avance['gross_amount'],
]);
log_message('info', "Item ajouté : produit {$avance['product_id']} (TERRE)");
} else {
log_message('warning', "Produit {$avance['product_id']} introuvable pour avance TERRE {$avance_id}");
}
$db->table('orders_item')->insert($orderItemData);
// ✅ 3. Marquer l'avance comme convertie
// ✅ MARQUER L'AVANCE COMME CONVERTIE
$this->update($avance_id, [
'is_order' => 1,
'active' => 0,
'active' => 0 // ✅ Désactiver pour ne plus apparaître dans les listes
]);
// ✅ Le produit RESTE product_sold = 1 (déjà marqué lors de la création de l'avance)
$db->transComplete();
if ($db->transStatus() === false) {
log_message('error', "Transaction échouée pour avance TERRE {$avance_id}");
log_message('error', "Transaction échouée pour avance {$avance_id}");
return false;
}
// ✅ 4. NOUVEAU : Envoyer notifications à TOUS les stores
$this->sendConversionNotifications($avance, $order_id, $bill_no);
log_message('info', "✅ Avance {$avance_id} convertie en commande {$order_id}");
// ✅ 5. Notification à la caissière du store concerné
$notificationController = new \App\Controllers\NotificationController();
$notificationController->createNotification(
"Nouvelle commande issue d'une avance TERRE complète - {$bill_no}",
"Caissière",
(int)$avance['store_id'],
"orders"
);
log_message('info', "✅ Avance TERRE {$avance_id} convertie en commande {$order_id} ({$bill_no})");
// ✅ Envoyer notification
$this->sendConversionNotification($avance, $order_id, $bill_no);
return $order_id;
} catch (\Exception $e) {
log_message('error', "Erreur conversion avance TERRE→commande : " . $e->getMessage());
$db->transRollback();
log_message('error', "❌ Erreur conversion avance {$avance_id}: " . $e->getMessage());
return false;
}
}
private function sendConversionNotifications($avance, $order_id, $bill_no)
private function sendConversionNotification($avance, $order_id, $bill_no)
{
try {
$Notification = new \App\Controllers\NotificationController();
$db = \Config\Database::connect();
// Récupérer tous les stores
$storesQuery = $db->table('stores')->select('id')->get();
$allStores = $storesQuery->getResultArray();
// Récupérer les infos de l'utilisateur
$userQuery = $db->table('users')
->select('firstname, lastname')
->where('id', $avance['user_id'])
->get();
$user = $userQuery->getRowArray();
$userName = $user ? "{$user['firstname']} {$user['lastname']}" : 'Utilisateur inconnu';
// Préparer le message
$customerName = $avance['customer_name'];
$avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT);
$avanceAmount = number_format((float)$avance['gross_amount'], 0, ',', ' ');
$typeAvance = strtoupper($avance['type_avance']);
$notificationMessage = "🎉 Avance {$typeAvance} N°{$avanceNumber} convertie en COMMANDE {$bill_no} - Client: {$customerName} - Montant: {$avanceAmount} Ar - Par: {$userName}";
// ✅ Envoyer notification à DAF, Direction et SuperAdmin de TOUS les stores
foreach ($allStores as $store) {
$storeId = (int)$store['id'];
// Notification pour DAF
$Notification->createNotification(
$notificationMessage,
"DAF",
$storeId,
'orders'
);
// Notification pour Direction
$Notification->createNotification(
$notificationMessage,
"Direction",
$storeId,
'orders'
);
// Notification pour SuperAdmin
$Notification->createNotification(
$notificationMessage,
"SuperAdmin",
$storeId,
'orders'
);
$Stores = new \App\Models\Stores();
$allStores = $Stores->getActiveStore();
$message = "🔄 Avance TERRE convertie en commande<br>" .
"N° Avance : #{$avance['avance_id']}<br>" .
"Client : {$avance['customer_name']}<br>" .
"Commande : {$bill_no}<br>" .
"Montant : " . number_format($avance['gross_amount'], 0, ',', ' ') . " Ar";
// ✅ Notifier tous les stores (Direction, DAF, SuperAdmin)
if (is_array($allStores) && count($allStores) > 0) {
foreach ($allStores as $store) {
foreach (['Direction', 'DAF', 'SuperAdmin'] as $role) {
$Notification->createNotification(
$message,
$role,
(int)$store['id'],
'orders'
);
}
}
}
log_message('info', "✅ Notifications conversion envoyées pour avance {$avance['avance_id']} → commande {$order_id} à tous les stores");
// ✅ Caissière du store concerné
$Notification->createNotification(
$message,
"Caissière",
(int)$avance['store_id'],
'orders'
);
} catch (\Exception $e) {
log_message('error', "Erreur envoi notifications conversion: " . $e->getMessage());
log_message('error', '❌ Erreur notification: ' . $e->getMessage());
}
}
/**
* ✅ Hook appelé automatiquement lors du paiement d'une avance
* Intégrer ceci dans votre fonction de paiement existante
@ -660,41 +644,48 @@ public function afterPayment(int $avance_id)
/**
* ✅ Générer un numéro de facture unique
*/
private function generateBillNumber($store_id)
private function generateBillNo(int $store_id): string
{
$db = \Config\Database::connect();
$storePrefixes = [
1 => 'ANTS',
2 => 'BESA',
3 => 'BYPA',
4 => 'TOAM',
];
// Récupérer le dernier numéro pour ce store
$query = $db->query(
"SELECT bill_no FROM orders WHERE store_id = ? ORDER BY id DESC LIMIT 1",
[$store_id]
);
$prefix = $storePrefixes[$store_id] ?? 'STORE';
$result = $query->getRow();
$db = \Config\Database::connect();
if ($result && preg_match('/(\d+)$/', $result->bill_no, $matches)) {
$lastNumber = intval($matches[1]);
$newNumber = $lastNumber + 1;
$lastBill = $db->table('orders')
->select('bill_no')
->like('bill_no', $prefix . '-', 'after')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if ($lastBill && !empty($lastBill['bill_no'])) {
preg_match('/-(\d+)$/', $lastBill['bill_no'], $matches);
$newNumber = isset($matches[1]) ? (int)$matches[1] + 1 : 1;
} else {
$newNumber = 1;
}
// Format: BILL-STORE{store_id}-{number}
return 'BILL-STORE' . $store_id . '-' . str_pad($newNumber, 5, '0', STR_PAD_LEFT);
return $prefix . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT);
}
/**
* ✅ Récupérer toutes les avances complètes non converties
*/
public function getCompletedNotConverted()
{
return $this->where('amount_due', 0)
return $this->where('type_avance', 'terre')
->where('amount_due <=', 0)
->where('is_order', 0)
->where('active', 1)
->where('type_avance', 'terre') // ✅ Uniquement TERRE à convertir
->findAll();
}
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les avances MER complètes (pour statistiques)

25
app/Models/Orders.php

@ -16,6 +16,7 @@ class Orders extends Model
protected $table = 'orders';
protected $allowedFields = [
'bill_no',
'document_type',
'customer_name',
'customer_address',
'customer_phone',
@ -202,34 +203,29 @@ class Orders extends Model
$orderItemModel = new OrderItems();
$productModel = new Products();
// ✅ RÉCUPÉRER LE TABLEAU DES PUISSANCES
$puissances = $data['puissance'] ?? [];
// Loop through products and insert order items
$count_product = count($post);
for ($x = 0; $x < $count_product; $x++) {
// ✅ AJOUT DE LA PUISSANCE
$items = [
'order_id' => $order_id,
'product_id' => $post[$x],
'rate' => $data['rate_value'][$x],
'qty' => 1,
'amount' => $data['amount_value'][$x],
'puissance' => $puissances[$x] ?? 1, // ✅ CORRECTION ICI
'puissance' => $puissances[$x] ?? 1,
];
$orderItemModel->insert($items);
// Decrease stock for the product
$product_data = $productModel->find($post[$x]);
if((int)$data['paid_status'] == 1){
if ($product_data) {
$product_sold = true;
$productModel->update($post[$x], ['product_sold' => $product_sold]);
} else {
$product_sold = false;
$productModel->update($post[$x], ['product_sold' => $product_sold]);
}
// ✅ CORRECTION : Marquer product_sold = 1 dès la création
// Peu importe le statut initial (En Attente, Payé, etc.)
if (in_array((int)$data['paid_status'], [1, 2, 3])) {
$productModel->update($post[$x], ['product_sold' => 1]);
} else {
// Si statut = 0 (Refusé), laisser disponible
$productModel->update($post[$x], ['product_sold' => 0]);
}
}
return $order_id;
@ -238,7 +234,6 @@ class Orders extends Model
return false;
}
}
/**
* count order item
* @param int $order_id
@ -467,7 +462,7 @@ class Orders extends Model
ELSE 0
END) AS total_virement_bancaire2
')
->whereIn('orders.paid_status', [1, 3]); // ← CHANGEZ CETTE LIGNE
->whereIn('orders.paid_status', [1, 2, 3]); // ← CHANGEZ CETTE LIGNE
if (!$isAdmin) {
$baseQuery->where('orders.store_id', $users['store_id']);

121
app/Models/Products.php

@ -11,34 +11,26 @@ class Products extends Model
protected $allowedFields = ['name', 'sku', 'price', 'product_sold', 'qty', 'image', 'description', 'numero_de_moteur', 'marque', 'chasis', 'store_id', 'availability', 'is_piece', 'prix_vente', 'date_arivage', 'puissance', 'cler', 'categorie_id', 'etats','type', 'infoManquekit', 'info', 'infoManque'];
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les produits selon le rôle et le store de l'utilisateur
* @param int|null $id
* @return array|object|null
* ✅ Récupérer les produits selon le rôle et le store de l'utilisateur
*/
public function getProductDataByRole(int $id = null)
{
$session = session();
$user = $session->get('user');
// Vérifier si l'utilisateur est admin (Conseil ou Direction)
$isAdmin = in_array($user['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$builder = $this->where('is_piece', 0)
->where('product_sold', 0);
// ✅ Si pas admin ET a un store_id valide, filtrer par son magasin
if (!$isAdmin) {
// ✅ Si l'utilisateur n'a pas de store_id (NULL ou 0), ne retourner aucun produit
if (empty($user['store_id']) || $user['store_id'] == 0) {
// Retourner une requête impossible pour avoir 0 résultats
$builder->where('id', -1);
} else {
// Filtrer par le store_id de l'utilisateur
$builder->where('store_id', $user['store_id']);
}
}
// Si un ID spécifique est demandé
if ($id) {
return $builder->where('id', $id)->first();
}
@ -46,11 +38,6 @@ class Products extends Model
return $builder->orderBy('id', 'DESC')->findAll();
}
/**
* get the brand data
* @param int $id
* @return array|object|null
*/
public function getProductData(int $id = null)
{
if ($id) {
@ -92,17 +79,29 @@ class Products extends Model
->where('availability', 1)
->where('store_id', $store_id);
$db = \Config\Database::connect();
// Sous-requête pour exclure les produits en avance
if ($excludeAvance) {
$db = \Config\Database::connect();
$subQuery = $db->table('avances')
$subQueryAvances = $db->table('avances')
->select('product_id')
->where('active', 1)
->where('is_order', 0)
->getCompiledSelect();
$builder->where("id NOT IN ($subQuery)", null, false);
$builder->where("id NOT IN ($subQueryAvances)", null, false);
}
// ✅ LISTE : Exclure TOUS les produits ayant une commande (statuts 1, 2, 3)
$subQueryOrders = $db->table('orders_item')
->select('orders_item.product_id')
->join('orders', 'orders.id = orders_item.order_id')
->whereIn('orders.paid_status', [1, 2, 3]) // ✅ Disparaît de la liste dès qu'il y a une commande
->getCompiledSelect();
$builder->where("id NOT IN ($subQueryOrders)", null, false);
// Exception pour le produit actuel lors de modification de commande
if ($currentProductId) {
$builder->orWhere('id', $currentProductId);
}
@ -143,19 +142,32 @@ class Products extends Model
return $this->delete($id) ? true : false;
}
/**
* ✅ NOUVELLE MÉTHODE : Compteur DASHBOARD (exclut uniquement statut 3)
* Utilisé pour afficher le nombre total de produits dans le dashboard
*/
public function countTotalProducts()
{
$db = \Config\Database::connect();
$subQuery = $db->table('avances')
// Exclure produits en avance
$subQueryAvances = $db->table('avances')
->select('product_id')
->where('active', 1)
->where('is_order', 0)
->getCompiledSelect();
// ✅ Exclure UNIQUEMENT les produits avec statut 3 (livré)
$subQueryOrders = $db->table('orders_item')
->select('orders_item.product_id')
->join('orders', 'orders.id = orders_item.order_id')
->where('orders.paid_status', 3) // ✅ Décompté UNIQUEMENT quand livré
->getCompiledSelect();
return $this->where('is_piece', 0)
->where('product_sold', 0)
->where("id NOT IN ($subQuery)", null, false)
->where("id NOT IN ($subQueryAvances)", null, false)
->where("id NOT IN ($subQueryOrders)", null, false)
->countAllResults();
}
@ -197,38 +209,43 @@ class Products extends Model
}
/**
* Compter les produits par store selon le rôle de l'utilisateur
* @return int
*/
public function countProductsByUserStore()
{
$session = session();
$user = $session->get('user');
// Vérifier si l'utilisateur est admin
$isAdmin = in_array($user['group_name'], ['DAF', 'Direction', 'SuperAdmin']);
$db = \Config\Database::connect();
// Sous-requête pour exclure les produits en avance
$subQuery = $db->table('avances')
->select('product_id')
->where('active', 1)
->where('is_order', 0)
->getCompiledSelect();
$builder = $this->where('is_piece', 0)
->where('product_sold', 0)
->where("id NOT IN ($subQuery)", null, false);
// Si pas admin ET a un store_id valide, filtrer par son magasin
if (!$isAdmin && !empty($user['store_id']) && $user['store_id'] != 0) {
$builder->where('store_id', $user['store_id']);
} elseif (!$isAdmin) {
// Utilisateur sans store = 0 produits
return 0;
* ✅ NOUVELLE MÉTHODE : Compteur DASHBOARD par store (exclut uniquement statut 3)
* Compter les produits par store selon le rôle de l'utilisateur
*/
public function countProductsByUserStore()
{
$session = session();
$user = $session->get('user');
$isAdmin = in_array($user['group_name'], ['DAF', 'Direction', 'SuperAdmin']);
$db = \Config\Database::connect();
// Exclure avances
$subQueryAvances = $db->table('avances')
->select('product_id')
->where('active', 1)
->where('is_order', 0)
->getCompiledSelect();
// ✅ Exclure UNIQUEMENT les produits livrés (statut 3)
$subQueryOrders = $db->table('orders_item')
->select('orders_item.product_id')
->join('orders', 'orders.id = orders_item.order_id')
->where('orders.paid_status', 3) // ✅ Décompté UNIQUEMENT quand livré
->getCompiledSelect();
$builder = $this->where('is_piece', 0)
->where('product_sold', 0)
->where("id NOT IN ($subQueryAvances)", null, false)
->where("id NOT IN ($subQueryOrders)", null, false);
if (!$isAdmin && !empty($user['store_id']) && $user['store_id'] != 0) {
$builder->where('store_id', $user['store_id']);
} elseif (!$isAdmin) {
return 0;
}
return $builder->countAllResults();
}
return $builder->countAllResults();
}
}

27
app/Models/Remise.php

@ -104,4 +104,31 @@ class Remise extends Model
return "Nouvelle remise créée avec succès.";
}
}
/**
* Vérifier si une commande a une demande de remise en attente
* @param int $order_id
* @return bool true si remise en attente, false sinon
*/
public function hasRemisePendingForOrder(int $order_id): bool
{
$result = $this->where('id_order', $order_id)
->where('demande_status', 'En attente')
->first();
return $result !== null;
}
/**
* Vérifier si une commande a une remise validée
* @param int $order_id
* @return bool
*/
public function hasRemiseValidatedForOrder(int $order_id): bool
{
$result = $this->where('id_order', $order_id)
->whereIn('demande_status', ['Accepté', 'Validé'])
->first();
return $result !== null;
}
}

153
app/Views/orders/edit.php

@ -64,10 +64,30 @@
<label for="time" class="col-sm-12 control-label">Heure: <?php echo date('h:i a') ?></label>
</div>
<div class="col-md-4 col-xs-12 pull pull-left">
<div class="col-md-4 col-xs-12 pull pull-left">
<!-- ✅ NOUVEAU : Type de document -->
<div class="form-group">
<label for="document_type" class="col-sm-5 control-label" style="text-align:left;">
Type de document <span class="text-danger">*</span>
</label>
<div class="col-sm-7">
<select name="document_type" id="document_type" class="form-control" required>
<option value="facture" <?php echo (isset($order_data['order']['document_type']) && $order_data['order']['document_type'] == 'facture') ? 'selected' : ''; ?>>
Facture
</option>
<option value="bl" <?php echo (isset($order_data['order']['document_type']) && $order_data['order']['document_type'] == 'bl') ? 'selected' : ''; ?>>
Bon de Livraison
</option>
<option value="both" <?php echo (isset($order_data['order']['document_type']) && $order_data['order']['document_type'] == 'both') ? 'selected' : ''; ?>>
Facture & Bon de Livraison
</option>
</select>
</div>
</div>
<!-- Types d'impression (ancien système - peut être supprimé si vous voulez) -->
<div class="form-group">
<label for="types" class="col-sm-5 control-label" style="text-align:left;">Types</label>
<label for="types" class="col-sm-5 control-label" style="text-align:left;">Types d'impression</label>
<div class="col-sm-7">
<select name="" id="typesCommande" class="form-control">
<option value="1">Facture</option>
@ -331,11 +351,69 @@
</div>
<script type="text/javascript">
var base_url = "<?php echo base_url(); ?>";
var idData = "<?php echo $order_data['order']['id']; ?>";
let Imprimente = document.getElementById('Imprimente');
let typesCommande = document.getElementById('typesCommande');
var base_url = "<?php echo base_url(); ?>";
var idData = "<?php echo $order_data['order']['id']; ?>";
let Imprimente = document.getElementById('Imprimente');
let typesCommande = document.getElementById('typesCommande');
let documentType = document.getElementById('document_type');
function updatePrintLink() {
let type = documentType ? documentType.value : 'facture';
// Synchroniser l'ancien select (si vous le gardez)
if (typesCommande) {
if (type === 'facture') {
typesCommande.value = 1;
} else if (type === 'bl') {
typesCommande.value = 3;
} else if (type === 'both') {
typesCommande.value = 2;
}
}
// Mettre à jour le lien d'impression
if (Imprimente) {
Imprimente.removeAttribute("href");
switch(type) {
case 'facture':
Imprimente.setAttribute("href", base_url + 'orders/printDiv/' + idData);
break;
case 'bl':
Imprimente.setAttribute("href", base_url + 'orders/printDivBL/' + idData);
break;
case 'both':
Imprimente.setAttribute("href", base_url + 'orders/printDivBLF/' + idData);
break;
default:
Imprimente.setAttribute("href", base_url + 'orders/printDiv/' + idData);
}
}
}
// ✅ Écouter les changements sur le nouveau select
if (documentType) {
documentType.addEventListener('change', updatePrintLink);
// Initialiser au chargement
updatePrintLink();
}
// ✅ Garder la compatibilité avec l'ancien système (optionnel)
if (typesCommande) {
typesCommande.addEventListener('change', function () {
// Synchroniser avec le nouveau select
if (documentType) {
if (typesCommande.value == 1) {
documentType.value = 'facture';
} else if (typesCommande.value == 3) {
documentType.value = 'bl';
} else {
documentType.value = 'both';
}
}
updatePrintLink();
});
}
typesCommande.addEventListener('change', function () {
if (typesCommande.value == 1) {
Imprimente.removeAttribute("href");
@ -501,10 +579,10 @@
var tableProductLength = $("#product_info_table tbody tr").length;
var totalSubAmount = 0;
for (x = 0; x < tableProductLength; x++) {
var tr = $("#product_info_table tbody tr")[x];
var count = $(tr).attr('id');
count = count.substring(4);
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
var tr = $("#product_info_table tbody tr")[x];
var count = $(tr).attr('id');
count = count.substring(4);
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
}
totalSubAmount = totalSubAmount.toFixed(2);
@ -521,30 +599,25 @@
$("#service_charge").val(service);
$("#service_charge_value").val(service);
var totalAmount = (Number(totalSubAmount));
totalAmount = totalAmount.toFixed(2);
var discount = $("#discount").val();
if (discount) {
var grandTotal = Number(totalAmount) - Number(discount);
grandTotal = grandTotal.toFixed(2);
$("#net_amount").val(grandTotal);
$("#net_amount_value").val(grandTotal);
} else {
$("#net_amount").val(totalAmount);
$("#net_amount_value").val(totalAmount);
}
// ✅ CORRECTION : net_amount = gross_amount - discount
var discount = Number($("#discount").val()) || 0;
var grossAmount = Number(totalSubAmount);
var netAmount = grossAmount - discount;
netAmount = netAmount.toFixed(2);
$("#net_amount").val(netAmount);
$("#net_amount_value").val(netAmount);
var paid_amount = Number($("#paid_amount").val());
if (paid_amount) {
var net_amount_value = Number($("#net_amount_value").val());
var remaning = net_amount_value - paid_amount;
$("#remaining").val(remaning.toFixed(2));
$("#remaining_value").val(remaning.toFixed(2));
var net_amount_value = Number($("#net_amount_value").val());
var remaning = net_amount_value - paid_amount;
$("#remaining").val(remaning.toFixed(2));
$("#remaining_value").val(remaning.toFixed(2));
}
updateMontantTranches();
}
}
function paidAmount() {
var grandTotal = $("#net_amount_value").val();
@ -564,10 +637,13 @@
function getMontantPourTranches() {
var discount = parseFloat($("#discount").val()) || 0;
var grossAmount = parseFloat($("#gross_amount_value").val()) || 0;
return discount > 0 ? discount : grossAmount;
}
// Si discount existe, on utilise gross_amount - discount
// Sinon on utilise gross_amount
return discount > 0 ? (grossAmount - discount) : grossAmount;
}
function updateMontantTranches() {
function updateMontantTranches() {
var montant = getMontantPourTranches();
var discount = parseFloat($("#discount").val()) || 0;
@ -575,26 +651,25 @@
$("#montant_total_tranches_value").val(montant);
if (discount > 0) {
$("#montant_source_label").text("(Prix avec remise acceptée)");
$("#montant_source_label").text("(Prix avec remise)");
} else {
$("#montant_source_label").text("(Montant brut)");
$("#montant_source_label").text("(Montant brut)");
}
var paymentMode = $("#payment_mode").val();
if (parseInt(paymentMode) === 1) {
$("#payment_amount_1").val(montant.toFixed(2));
$("#payment_amount_1").val(montant.toFixed(2));
} else {
calculerTranche2();
calculerTranche2();
}
}
function calculerTranche2() {
}
function calculerTranche2() {
var montantTotal = getMontantPourTranches();
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
var tranche2 = montantTotal - tranche1;
if (tranche2 < 0) tranche2 = 0;
$("#payment_amount_2").val(tranche2.toFixed(2));
}
}
$("#discount").on('keyup', function() {
subAmount();

203
app/Views/orders/index.php

@ -72,7 +72,23 @@
<?php
$session = session();
$users = $session->get('user');
if ($users['group_name'] === 'COMMERCIALE' || $users['group_name'] === 'Caissière' || $users['group_name'] === 'SECURITE' || $users['group_name'] === "Cheffe d'Agence") {
// Interface spécifique pour SECURITE
if ($users['group_name'] === 'SECURITE') {
?>
<th>Nom du produit</th>
<th>Commerciale</th>
<th>Date et Heure</th>
<th>Marque</th>
<th>Numéro de Série</th>
<th>Status</th>
<?php if (
in_array('viewOrder', $user_permission)
|| in_array('updateOrder', $user_permission)
) { ?>
<th>Action</th>
<?php } ?>
<?php } elseif ($users['group_name'] === 'COMMERCIALE' || $users['group_name'] === 'Caissière' || $users['group_name'] === "Cheffe d'Agence") {
// Interface pour les autres rôles (COMMERCIALE, Caissière, Cheffe d'Agence)
?>
<th>Nom du produit</th>
<th>Commerciale</th>
@ -184,20 +200,14 @@
<!-- Tableau produits avec toutes les infos sur une ligne -->
<h4>Informations de la commande</h4>
<table class="table table-bordered table-striped" id="view_products_table">
<thead>
<tr>
<th>Marque</th>
<th>Désignation</th>
<th>Numéro de Série</th>
<th>N° de Moteur</th>
<th>Châssis</th>
<th>N° Facture</th>
<th>Statut</th>
</tr>
</thead>
<tbody></tbody>
</table>
<table class="table table-bordered table-striped" id="view_products_table">
<thead>
<tr id="table_headers">
<!-- Les en-têtes seront générés dynamiquement par JavaScript -->
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="modal-footer">
@ -371,65 +381,92 @@
// ✅ FONCTION MODIFIÉE POUR AFFICHER LES NOUVELLES COLONNES
$(document).on('click', '.btn-view', function(e) {
e.preventDefault();
var orderId = $(this).data('order-id');
currentOrderId = orderId; // Stocker l'ID de la commande
var url = base_url + 'orders/lookOrder/' + orderId;
// Réinitialiser les messages
$('#modal-messages').empty();
// Requête AJAX pour récupérer les données JSON
$.getJSON(url, function(response) {
var d = response.order_data.order;
// Remplir les champs de la modal
$('#order_date').text(d.date_time || '');
$('#customer_name').text(d.customer_name);
$('#customer_address').text(d.customer_address);
$('#customer_phone').text(d.customer_phone);
$('#customer_cin').text(d.customer_cin);
// Déterminer le statut une seule fois
var statutHtml = '';
if (d.paid_status == 1) {
statutHtml = '<span class="label label-success">Payé</span>';
<?php if ($users['group_name'] === 'SECURITE'): ?>
$('#btn-mark-delivered').show();
<?php endif; ?>
} else if (d.paid_status == 2) {
statutHtml = '<span class="label label-warning">En Attente</span>';
$('#btn-mark-delivered').hide();
} else if (d.paid_status == 3) {
statutHtml = '<span class="label label-info">Payé et Livré</span>';
$('#btn-mark-delivered').hide();
} else {
statutHtml = '<span class="label label-danger">Refusé</span>';
$('#btn-mark-delivered').hide();
}
e.preventDefault();
var orderId = $(this).data('order-id');
currentOrderId = orderId;
var url = base_url + 'orders/lookOrder/' + orderId;
$('#modal-messages').empty();
$.getJSON(url, function(response) {
var d = response.order_data.order;
// Remplir les champs de la modal
$('#order_date').text(d.date_time || '');
$('#customer_name').text(d.customer_name);
$('#customer_address').text(d.customer_address);
$('#customer_phone').text(d.customer_phone);
$('#customer_cin').text(d.customer_cin);
// Déterminer le statut une seule fois
var statutHtml = '';
if (d.paid_status == 1) {
statutHtml = '<span class="label label-success">Payé</span>';
<?php if ($users['group_name'] === 'SECURITE'): ?>
$('#btn-mark-delivered').show();
<?php endif; ?>
} else if (d.paid_status == 2) {
statutHtml = '<span class="label label-warning">En Attente</span>';
$('#btn-mark-delivered').hide();
} else if (d.paid_status == 3) {
statutHtml = '<span class="label label-info">Payé et Livré</span>';
$('#btn-mark-delivered').hide();
} else {
statutHtml = '<span class="label label-danger">Refusé</span>';
$('#btn-mark-delivered').hide();
}
// ✅ GÉNÉRER LES EN-TÊTES SELON LE RÔLE
var $headers = $('#table_headers');
$headers.empty();
<?php if ($users['group_name'] === 'SECURITE'): ?>
// En-têtes pour SECURITE
$headers.append(
'<th>Marque</th>' +
'<th>Désignation</th>' +
'<th>Numéro de Série</th>' +
'<th>N° de Moteur</th>' +
'<th>Châssis</th>' +
'<th>N° Facture</th>' +
'<th>Statut</th>'
);
<?php else: ?>
// En-têtes pour les autres rôles
$headers.append(
'<th>Marque</th>' +
'<th>Désignation</th>' +
'<th>Prix de vente</th>' +
'<th>Prix demandé</th>' +
'<th>Remise</th>' +
'<th>Statut</th>'
);
<?php endif; ?>
// ✅ GÉNÉRER LES LIGNES DU TABLEAU SELON LE RÔLE
var $tb = $('#view_products_table tbody');
$tb.empty();
$.each(response.order_data.order_item, function(_, item) {
var product = null;
$.each(response.products, function(_, p) {
if (p.id == item.product_id) {
product = p;
return false;
}
});
// Produits avec les nouvelles colonnes
var $tb = $('#view_products_table tbody');
$tb.empty();
$.each(response.order_data.order_item, function(_, item) {
var product = null;
$.each(response.products, function(_, p) {
if (p.id == item.product_id) {
product = p;
if (product) {
var brandName = '';
$.each(response.brands, function(_, brand) {
if (brand.id == product.marque) {
brandName = brand.name;
return false;
}
});
if (product) {
var brandName = '';
$.each(response.brands, function(_, brand) {
if (brand.id == product.marque) {
brandName = brand.name;
return false;
}
});
// ✅ AJOUT DES NOUVELLES COLONNES : N° Moteur, Châssis, N° Facture, Statut
<?php if ($users['group_name'] === 'SECURITE'): ?>
// ✅ AFFICHAGE POUR SECURITE
$tb.append(
'<tr>' +
'<td>' + (brandName || 'Aucune marque') + '</td>' +
@ -441,12 +478,28 @@
'<td>' + statutHtml + '</td>' +
'</tr>'
);
}
});
// Afficher la modal
$('#viewOrderModal').modal('show');
<?php else: ?>
// ✅ AFFICHAGE POUR LES AUTRES RÔLES
var prixVente = parseFloat(product.prix_vente || 0);
var prixDemande = parseFloat(d.discount || 0); // Prix demandé = discount
var netAmount = parseFloat(d.net_amount || 0); // Remise = net_amount
$tb.append(
'<tr>' +
'<td>' + (brandName || 'Aucune marque') + '</td>' +
'<td>' + (product.name || '') + '</td>' +
'<td>' + prixVente.toLocaleString('fr-FR') + ' Ar</td>' +
'<td>' + prixDemande.toLocaleString('fr-FR') + ' Ar</td>' +
'<td>' + netAmount.toLocaleString('fr-FR') + ' Ar</td>' +
'<td>' + statutHtml + '</td>' +
'</tr>'
);
<?php endif; ?>
}
});
});
// Afficher la modal
$('#viewOrderModal').modal('show');
});
});
</script>
Loading…
Cancel
Save