Sarobidy22 1 month ago
parent
commit
6a2fd61385
  1. 6
      app/Config/Routes.php
  2. 27
      app/Controllers/Dashboard.php
  3. 337
      app/Controllers/OrderController.php
  4. 124
      app/Controllers/RecouvrementController.php
  5. 57
      app/Controllers/ReportController.php
  6. 54
      app/Controllers/SortieCaisseController.php
  7. 42
      app/Models/Orders.php
  8. 25
      app/Models/Recouvrement.php
  9. 70
      app/Models/SortieCaisse.php
  10. 200
      app/Views/dashboard.php
  11. 90
      app/Views/orders/edit.php
  12. 172
      app/Views/recouvrement/index.php
  13. 219
      app/Views/sortieCaisse/index.php

6
app/Config/Routes.php

@ -197,12 +197,14 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('printDivBL/(:num)', [OrderController::class, 'print7']); $routes->get('printDivBL/(:num)', [OrderController::class, 'print7']);
$routes->get('printDivBLF/(:num)', [OrderController::class, 'print31']); $routes->get('printDivBLF/(:num)', [OrderController::class, 'print31']);
$routes->post('remove', [OrderController::class, 'remove']); $routes->post('remove', [OrderController::class, 'remove']);
$routes->post('markAsDelivered', [OrderController::class, 'markAsDelivered']); // ← AJOUTEZ CETTE LIGNE ICI $routes->post('markAsDelivered', [OrderController::class, 'markAsDelivered']);
$routes->get('lookOrder/(:num)', [OrderController::class, 'lookOrder']); $routes->get('lookOrder/(:num)', [OrderController::class, 'lookOrder']);
$routes->get('createFromEspace/(:num)', [OrderController::class, 'createById']); $routes->get('createFromEspace/(:num)', [OrderController::class, 'createById']);
$routes->get('resrevation', [ReservationController::class, 'index']); $routes->get('resrevation', [ReservationController::class, 'index']);
}); });
/** /**
* route for the reports * route for the reports
*/ */
@ -215,8 +217,10 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('detail/fetctDataStock2/(:num)', [ReportController::class, 'fetchProductStock2']); $routes->get('detail/fetctDataStock2/(:num)', [ReportController::class, 'fetchProductStock2']);
$routes->get('detail/performance', [ReportController::class, 'performancedetail']); $routes->get('detail/performance', [ReportController::class, 'performancedetail']);
$routes->get('detail/fetchPerformances', [ReportController::class, 'fetchPerformances']); $routes->get('detail/fetchPerformances', [ReportController::class, 'fetchPerformances']);
$routes->get('detail/fetchCaissierPerformances', [ReportController::class, 'fetchCaissierPerformances']);
}); });
/** /**
* route for the company * route for the company
*/ */

27
app/Controllers/Dashboard.php

@ -93,14 +93,14 @@ class Dashboard extends AdminController
'total_virement_bancaire' => $total_virement_bancaire_final, 'total_virement_bancaire' => $total_virement_bancaire_final,
// === POUR CAISSIÈRE (MÊME CALCUL QUE DIRECTION) === // === POUR CAISSIÈRE (MÊME CALCUL QUE DIRECTION) ===
'total_caisse' => $total_final, // ← Identique à 'total' 'total_caisse' => $total_final,
'total_mvola_caisse' => $total_mvola_final, // ← Identique à 'total_mvola' 'total_mvola_caisse' => $total_mvola_final,
'total_espece_caisse' => $total_espece_final, // ← Identique à 'total_espece' 'total_espece_caisse' => $total_espece_final,
'total_vb_caisse' => $total_virement_bancaire_final, // ← Identique à 'total_virement_bancaire' 'total_vb_caisse' => $total_virement_bancaire_final,
// === DÉTAIL POUR LA CAISSIÈRE === // === DÉTAIL POUR LA CAISSIÈRE ===
'total_orders_only' => $total_orders, // Ventes complètes uniquement 'total_orders_only' => $total_orders,
'total_avances' => $total_avances, // Avances uniquement 'total_avances' => $total_avances,
// ✅ Détails des sorties // ✅ Détails des sorties
'total_sorties' => $total_sortie_global, 'total_sorties' => $total_sortie_global,
@ -109,10 +109,10 @@ class Dashboard extends AdminController
'total_sortie_virement' => $total_sortie_virement, 'total_sortie_virement' => $total_sortie_virement,
// ✅ Détails des recouvrements // ✅ Détails des recouvrements
'recouvrement_me' => $me, // Mvola → Espèce 'recouvrement_me' => $me,
'recouvrement_be' => $be, // Banque → Espèce 'recouvrement_be' => $be,
'recouvrement_bm' => $bm, // Banque → Mvola 'recouvrement_bm' => $bm,
'recouvrement_mb' => $mb, // Mvola → Banque 'recouvrement_mb' => $mb,
'total_recouvrements' => $me + $be + $bm + $mb, 'total_recouvrements' => $me + $be + $bm + $mb,
// Détail avances par mode de paiement // Détail avances par mode de paiement
@ -125,7 +125,7 @@ class Dashboard extends AdminController
'total_espece_orders' => $es1_orders + $es2_orders, 'total_espece_orders' => $es1_orders + $es2_orders,
'total_vb_orders' => $vb1_orders + $vb2_orders, 'total_vb_orders' => $vb1_orders + $vb2_orders,
// ✅ Montants bruts (avant recouvrements et sorties) // ✅ Montants bruts
'total_brut' => $total_brut, 'total_brut' => $total_brut,
'total_mvola_brut' => $total_mvola_brut, 'total_mvola_brut' => $total_mvola_brut,
'total_espece_brut' => $total_espece_brut, 'total_espece_brut' => $total_espece_brut,
@ -232,9 +232,14 @@ class Dashboard extends AdminController
if ($user_id['group_name'] == "Cheffe d'Agence") { if ($user_id['group_name'] == "Cheffe d'Agence") {
$data['isChef'] = true; $data['isChef'] = true;
} }
// ✅ AJOUT POUR CAISSIER : Passer les données de performance
if ($user_id['group_name'] == "Caissière") { if ($user_id['group_name'] == "Caissière") {
$data['isCaissier'] = true; $data['isCaissier'] = true;
// Pas besoin de données supplémentaires car fetchCaissierPerformances
// récupère déjà les données via AJAX
} }
if ($user_id['group_name'] == "MECANICIEN") { if ($user_id['group_name'] == "MECANICIEN") {
$data['isMecanicien'] = true; $data['isMecanicien'] = true;
} }

337
app/Controllers/OrderController.php

@ -36,6 +36,31 @@ class OrderController extends AdminController
return $this->render_template('orders/index', $data); 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() public function fetchOrdersData()
{ {
helper(['url', 'form']); helper(['url', 'form']);
@ -335,13 +360,15 @@ class OrderController extends AdminController
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$user_id = $users['id']; $user_id = $users['id'];
$bill_no = 'BILPR-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
// ✅ UTILISER LA NOUVELLE MÉTHODE
$bill_no = $this->generateBillNo($users['store_id']);
// Récupération des produits // Récupération des produits
$posts = $this->request->getPost('product[]'); $posts = $this->request->getPost('product[]');
$rates = $this->request->getPost('rate_value[]'); $rates = $this->request->getPost('rate_value[]');
$amounts = $this->request->getPost('amount_value[]'); $amounts = $this->request->getPost('amount_value[]');
$puissances = $this->request->getPost('puissance[]'); // ✅ AJOUTER CETTE LIGNE $puissances = $this->request->getPost('puissance[]');
$discount = (float)$this->request->getPost('discount') ?? 0; $discount = (float)$this->request->getPost('discount') ?? 0;
$gross_amount = $this->calculGross($amounts); $gross_amount = $this->calculGross($amounts);
@ -352,14 +379,12 @@ class OrderController extends AdminController
foreach ($posts as $index => $productId) { foreach ($posts as $index => $productId) {
$productId = (int)$productId; $productId = (int)$productId;
// Récupérer données produit + prix minimal
$productData = $Products->getProductData($productId); $productData = $Products->getProductData($productId);
$fourchette = $FourchettePrix->getFourchettePrixByProductId($productId); $fourchette = $FourchettePrix->getFourchettePrixByProductId($productId);
if ($fourchette) { if ($fourchette) {
$prixMinimal = (float)$fourchette['prix_minimal']; $prixMinimal = (float)$fourchette['prix_minimal'];
// ✅ Le rabais devient le prix de vente
if ($discount < $prixMinimal) { if ($discount < $prixMinimal) {
$prixMinimalFormatted = number_format($prixMinimal, 0, ',', ' '); $prixMinimalFormatted = number_format($prixMinimal, 0, ',', ' ');
$discountFormatted = number_format($discount, 0, ',', ' '); $discountFormatted = number_format($discount, 0, ',', ' ');
@ -374,23 +399,19 @@ class OrderController extends AdminController
} }
} }
// ✅ NOUVELLE LOGIQUE : Calculer le montant à payer et net_amount // ✅ Calculer le montant à payer et net_amount
$montant_a_payer = ($discount > 0) ? $discount : $gross_amount; $montant_a_payer = ($discount > 0) ? $discount : $gross_amount;
// Récupérer les tranches
$tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0; $tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0;
$tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0; $tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0;
// Calculer net_amount selon les tranches
if ($tranche_1 > 0 && $tranche_2 > 0) { if ($tranche_1 > 0 && $tranche_2 > 0) {
// Paiement en 2 tranches
$net_amount = $tranche_1 + $tranche_2; $net_amount = $tranche_1 + $tranche_2;
} else { } else {
// Paiement en 1 tranche ou pas de tranches
$net_amount = $montant_a_payer; $net_amount = $montant_a_payer;
} }
// ✅ Création de la commande avec la nouvelle logique // ✅ Création de la commande
$data = [ $data = [
'bill_no' => $bill_no, 'bill_no' => $bill_no,
'customer_name' => $this->request->getPost('customer_name'), 'customer_name' => $this->request->getPost('customer_name'),
@ -408,7 +429,7 @@ class OrderController extends AdminController
'amount_value' => $amounts, 'amount_value' => $amounts,
'gross_amount' => $gross_amount, 'gross_amount' => $gross_amount,
'rate_value' => $rates, 'rate_value' => $rates,
'puissance' => $puissances, // ✅ AJOUTER CETTE LIGNE 'puissance' => $puissances,
'store_id' => $users['store_id'], 'store_id' => $users['store_id'],
'tranche_1' => $tranche_1, 'tranche_1' => $tranche_1,
'tranche_2' => $tranche_2, 'tranche_2' => $tranche_2,
@ -423,9 +444,8 @@ class OrderController extends AdminController
$Notification = new NotificationController(); $Notification = new NotificationController();
// ✅ WORKFLOW SELON LA REMISE
if ($discount > 0) { if ($discount > 0) {
// AVEC REMISE : Créer demande + Notifier Conseil // Logique demande de remise...
$Order_item1 = new OrderItems(); $Order_item1 = new OrderItems();
$order_item_data = $Order_item1->getOrdersItemData($order_id); $order_item_data = $Order_item1->getOrdersItemData($order_id);
$product_ids = array_column($order_item_data, 'product_id'); $product_ids = array_column($order_item_data, 'product_id');
@ -462,7 +482,6 @@ class OrderController extends AdminController
$Remise = new Remise(); $Remise = new Remise();
$id_remise = $Remise->addDemande($data1); $id_remise = $Remise->addDemande($data1);
// Notification au CONSEIL
$Notification->createNotification( $Notification->createNotification(
"Nouvelle demande de remise à valider - Commande " . $bill_no, "Nouvelle demande de remise à valider - Commande " . $bill_no,
"Direction", "Direction",
@ -471,7 +490,6 @@ class OrderController extends AdminController
); );
} else { } else {
// SANS REMISE : Notifier directement la Caissière
$Notification->createNotification( $Notification->createNotification(
"Nouvelle commande à valider - " . $bill_no, "Nouvelle commande à valider - " . $bill_no,
"Caissière", "Caissière",
@ -480,7 +498,6 @@ class OrderController extends AdminController
); );
} }
// Redirection selon le rôle
if ($users["group_name"] != "COMMERCIALE") { if ($users["group_name"] != "COMMERCIALE") {
$this->checkProductisNull($posts, $users['store_id']); $this->checkProductisNull($posts, $users['store_id']);
} }
@ -714,6 +731,22 @@ public function getTableProductRow()
$paid_status = $this->request->getPost('paid_status'); $paid_status = $this->request->getPost('paid_status');
} }
// ✅ AJOUT : TRACER LA VALIDATION PAR LE CAISSIER
$validated_by = $current_order['validated_by'] ?? null; // Garder l'ancienne valeur si existe
$validated_at = $current_order['validated_at'] ?? null;
// Si le statut passe à "Validé" (1) et que l'utilisateur est un caissier
if ($old_paid_status != 1 && $paid_status == 1 && $role === 'Caissière') {
$validated_by = $user['id'];
$validated_at = date('Y-m-d H:i:s');
}
// Si le statut repasse à "En attente" ou "Refusé", effacer la validation
if (in_array($paid_status, [0, 2])) {
$validated_by = null;
$validated_at = null;
}
$discount = $this->request->getPost('discount'); $discount = $this->request->getPost('discount');
$original_discount = $this->request->getPost('original_discount'); $original_discount = $this->request->getPost('original_discount');
if ($discount === '' || $discount === null) { if ($discount === '' || $discount === null) {
@ -737,11 +770,14 @@ public function getTableProductRow()
'product_sold' => true, 'product_sold' => true,
'rate_value' => $this->request->getPost('rate_value'), 'rate_value' => $this->request->getPost('rate_value'),
'amount_value' => $this->request->getPost('amount_value'), 'amount_value' => $this->request->getPost('amount_value'),
'puissance' => $this->request->getPost('puissance'), // ✅ AJOUT PUISSANCE 'puissance' => $this->request->getPost('puissance'),
'tranche_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_1') : null, 'tranche_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_1') : null,
'tranche_2' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_2') : 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' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_1') : null,
'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null 'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null,
// ✅ AJOUT DES CHAMPS DE TRACABILITÉ
'validated_by' => $validated_by,
'validated_at' => $validated_at
]; ];
if ($Orders->updates($id, $dataUpdate)) { if ($Orders->updates($id, $dataUpdate)) {
@ -760,6 +796,16 @@ public function getTableProductRow()
(int)$user['store_id'], (int)$user['store_id'],
'orders' 'orders'
); );
// ✅ AJOUT : Notification pour la Direction quand un caissier valide
if ($role === 'Caissière') {
$Notification->createNotification(
"Commande validée par la caisse: {$bill_no}",
"Direction",
(int)$user['store_id'],
'orders'
);
}
} }
if ((float)$discount > 0) { if ((float)$discount > 0) {
@ -1608,12 +1654,10 @@ public function print5(int $id)
throw new \CodeIgniter\Exceptions\PageNotFoundException(); throw new \CodeIgniter\Exceptions\PageNotFoundException();
} }
// Modèles
$Orders = new Orders(); $Orders = new Orders();
$Company = new Company(); $Company = new Company();
$OrderItems = new OrderItems(); $OrderItems = new OrderItems();
// Récupération des données
$order = $Orders->getOrdersData($id); $order = $Orders->getOrdersData($id);
$items = $OrderItems->getOrdersItemData($id); $items = $OrderItems->getOrdersItemData($id);
$company = $Company->getCompanyData(1); $company = $Company->getCompanyData(1);
@ -1628,7 +1672,6 @@ public function print5(int $id)
} }
} }
// Calculs
$discount = (float) $order['discount']; $discount = (float) $order['discount'];
$grossAmount = (float) $order['gross_amount']; $grossAmount = (float) $order['gross_amount'];
$totalTTC = ($discount > 0) ? $discount : $grossAmount; $totalTTC = ($discount > 0) ? $discount : $grossAmount;
@ -1638,39 +1681,205 @@ public function print5(int $id)
$paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé';
// Début du HTML
$html = '<!DOCTYPE html> $html = '<!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Facture '.$order['bill_no'].'</title> <title>Facture '.$order['bill_no'].'</title>
<style> <style>
body { font-family: Arial, sans-serif; font-size:14px; color:#000;margin:0; padding:0; } /* ✅ FORMAT A4 PAYSAGE DIVISÉ EN 2 */
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; } @page {
.header .infos { line-height:1.4; } size: A4 landscape;
.header img { max-height:80px; } margin: 0;
.client { margin-bottom:20px; } }
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
th, td { border:1px solid #000; padding:6px; } body {
th { background:#f0f0f0; } font-family: Arial, sans-serif;
.right { text-align:right; } font-size: 10px;
.signature { display:flex; justify-content:space-between; margin-top:50px; } color: #000;
.signature div { text-align:center; } margin: 0;
.conditions { page-break-before: always; padding:20px; line-height:1.5; } 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> </style>
</head> </head>
<body onload="window.print()"> <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="header">
<div class="infos"> <div class="infos">
<h2 style="margin:0;">'.esc($company['company_name']).'</h2> <h2>'.esc($company['company_name']).'</h2>
<p style="margin:2px 0;"><strong>NIF :</strong> '.esc($company['NIF']).'</p> <p><strong>NIF :</strong> '.esc($company['NIF']).'</p>
<p style="margin:2px 0;"><strong>STAT :</strong> '.esc($company['STAT']).'</p> <p><strong>STAT :</strong> '.esc($company['STAT']).'</p>
<p style="margin:2px 0;"><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p> <p><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p>
</div> </div>
<div style="text-align:center;"> <div style="text-align:center;">
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo"> <img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
<p style="margin:5px 0; font-weight:bold;">Facture N° '.esc($order['bill_no']).'</p> <p class="facture-num">Facture N° '.esc($order['bill_no']).'</p>
</div> </div>
</div> </div>
@ -1684,9 +1893,6 @@ public function print5(int $id)
// ✅ TABLEAU ADAPTÉ SELON LE TYPE // ✅ TABLEAU ADAPTÉ SELON LE TYPE
if ($isAvanceMere) { if ($isAvanceMere) {
// ========================================
// TABLE SIMPLIFIÉE POUR AVANCE "SUR MER"
// ========================================
$html .= ' $html .= '
<table> <table>
<thead> <thead>
@ -1704,10 +1910,10 @@ public function print5(int $id)
$prixAffiche = ($discount > 0) ? $discount : $details['prix']; $prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= '<tr><td>'.esc($details['product_name']);
// Afficher le commentaire s'il existe
if (!empty($details['commentaire'])) { if (!empty($details['commentaire'])) {
$html .= '<br><em style="font-size:12px; color:#666;">'.esc($details['commentaire']).'</em>'; $html .= '<br><em style="font-size:8px; color:#666;">'.esc($details['commentaire']).'</em>';
} }
$html .= '</td> $html .= '</td>
@ -1715,10 +1921,12 @@ public function print5(int $id)
</tr>'; </tr>';
} }
// ✅ CORRECTION : Fermer le tableau pour avance
$html .= '
</tbody>
</table>';
} else { } else {
// ========================================
// TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE
// ========================================
$html .= ' $html .= '
<table> <table>
<thead> <thead>
@ -1740,23 +1948,24 @@ public function print5(int $id)
$prixAffiche = ($discount > 0) ? $discount : $details['prix']; $prixAffiche = ($discount > 0) ? $discount : $details['prix'];
$html .= ' $html .= '
<tr> <tr>
<td>'.esc($details['marque']).'</td> <td>'.esc($details['marque']).'</td>
<td>'.esc($details['product_name']).'</td> <td>'.esc($details['product_name']).'</td>
<td>'.esc($details['numero_moteur']).'</td> <td>'.esc($details['numero_moteur']).'</td>
<td>'.esc($details['numero_chassis']).'</td> <td>'.esc($details['numero_chassis']).'</td>
<td>'.esc($details['puissance']).'</td> <!-- ✅ ICI --> <td>'.esc($details['puissance']).'</td>
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td> <td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
</tr>'; </tr>';
} }
}
// ✅ Fermer le tableau pour produit normal
$html .= ' $html .= '
</tbody> </tbody>
</table> </table>';
}
$html .= '
<table> <table>
<tr> <tr>
<td><strong>Prix (HT) :</strong></td> <td><strong>Prix (HT) :</strong></td>
@ -1772,7 +1981,7 @@ public function print5(int $id)
</tr> </tr>
</table> </table>
<div style="border:1px solid #000; padding:10px; margin-bottom:30px;"> <div class="words-box">
<strong>Arrêté à la somme de :</strong><br> <strong>Arrêté à la somme de :</strong><br>
'.$inWords.' '.$inWords.'
</div> </div>
@ -1781,12 +1990,22 @@ public function print5(int $id)
<div>L\'Acheteur<br><br>__________________</div> <div>L\'Acheteur<br><br>__________________</div>
<div>Le Vendeur<br><br>__________________</div> <div>Le Vendeur<br><br>__________________</div>
</div> </div>
</div>';
}
<!-- Conditions Générales avec saut de page --> $html .= '
<div class="conditions"> </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;"> <div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0;">Conditions Générales</h3> <h3>Conditions Générales</h3>
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo" style="height:60px;"> <img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
</div> </div>
<ul> <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>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>
@ -1795,7 +2014,11 @@ public function print5(int $id)
<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</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> <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> </ul>
<div style="text-align:center; margin-top:50px;">L\'Acheteur</div> <div class="buyer-signature">L\'Acheteur</div>
</div>';
}
$html .= '
</div> </div>
</body> </body>

124
app/Controllers/RecouvrementController.php

@ -222,9 +222,6 @@ class RecouvrementController extends AdminController
$data['page_title'] = $this->pageTitle; $data['page_title'] = $this->pageTitle;
// echo "<pre>";
// die(var_dump($this->request->getPost()));
// Load validation service // Load validation service
$validation = \Config\Services::validation(); $validation = \Config\Services::validation();
@ -242,59 +239,36 @@ class RecouvrementController extends AdminController
'recouvrement_date' => $this->request->getPost('recouvrement_date'), 'recouvrement_date' => $this->request->getPost('recouvrement_date'),
]; ];
// Set validation rules
$Notification = new NotificationController(); $Notification = new NotificationController();
$Recouvrement = new Recouvrement(); $Recouvrement = new Recouvrement();
// $recouvrement_id = $this->request->getPost('recouvrement_id');
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
if ($users && isset($users['firstname'], $users['lastname'])) { if ($users && isset($users['firstname'], $users['lastname'])) {
$fullname = $users['firstname'] . ' ' . $users['lastname']; $fullname = $users['firstname'] . ' ' . $users['lastname'];
} }
// $orders = new Orders(); if ($validation->run($validationData)) {
// $Recouvrement = new Recouvrement(); $send_mode = $this->request->getPost('send_mode');
// $paymentData = $orders->getPaymentModes(); $get_mode = $this->request->getPost('get_mode');
// $totalRecouvrement = $Recouvrement->getTotalRecouvrements(); $amount = (float) $this->request->getPost('recouvrement_montant');
// $total_recouvrement = $totalRecouvrement->total_recouvrement;
// Initialisation des totaux avec 0 au cas où il n'y aurait pas de données
// $total_mvola1 = isset($paymentData->total_mvola1) ? $paymentData->total_mvola1 : 0;
// $total_mvola2 = isset($paymentData->total_mvola2) ? $paymentData->total_mvola2 : 0;
// $total_mvola = $total_mvola1 + $total_mvola2;
// $total_mvola1 = $total_mvola - $total_recouvrement;
// die(var_dump($data['recouvrement']))
// if ($data['recouvrement_montant'] <= $total_mvola1) {
// if ($Recouvrement->addRecouvrement($data)) {
// session()->setFlashdata('success', 'Créé avec succès');
// $Notification->createNotification("Un nouveau recouvrement crée", "TOUS", 0, 'recouvrement/'); // Vérifier si le recouvrement est possible
// return redirect()->to('recouvrement/'); if (!$this->canMakeRecouvrement($send_mode, $get_mode, $amount)) {
// } else { $response['success'] = false;
// session()->setFlashdata('errors', 'Error occurred while creating the product'); $response['messages'] = 'Recouvrement impossible : solde insuffisant pour ce type de transaction';
// return redirect()->to('recouvrement/'); return $this->response->setJSON($response);
// } }
// } else {
// session()->setFlashdata('errors', 'Solde MVOLA insuffisant');
// return redirect()->to('recouvrement/');
// }
if ($validation->run($validationData)) { // Préparer les données
// // Prepare data
$session = session();
$users = $session->get('user');
$data = [ $data = [
'recouvrement_montant' => $this->request->getPost('recouvrement_montant'), 'recouvrement_montant' => $amount,
'recouvrement_date' => $this->request->getPost('recouvrement_date'), 'recouvrement_date' => $this->request->getPost('recouvrement_date'),
'recouvrement_personnel' => $fullname, 'recouvrement_personnel' => $fullname,
'get_money' => $this->request->getPost('get_mode'), 'get_money' => $get_mode,
'send_money' => $this->request->getPost('send_mode'), 'send_money' => $send_mode,
'user_id' => $users['id'], 'user_id' => $users['id'],
'store_id' => $users['store_id'], 'store_id' => $users['store_id'],
]; ];
if ($Recouvrement->addRecouvrement($data)) { if ($Recouvrement->addRecouvrement($data)) {
@ -315,7 +289,6 @@ class RecouvrementController extends AdminController
} }
public function updateRecouvrement($recouvrement_id) public function updateRecouvrement($recouvrement_id)
{ {
$this->verifyRole('updateRecouvrement'); $this->verifyRole('updateRecouvrement');
@ -367,6 +340,73 @@ class RecouvrementController extends AdminController
echo json_encode($data); echo json_encode($data);
} }
} }
private function canMakeRecouvrement($send_mode, $get_mode, $amount): bool
{
$orders = new Orders();
$recouvrement = new Recouvrement();
$sortieCaisse = new SortieCaisse();
$avance = new Avance();
// Récupérer les données actuelles
$paymentDataOrders = $orders->getPaymentModes();
$paymentDataAvance = $avance->getPaymentModesAvance();
$totalRecouvrement = $recouvrement->getTotalRecouvrements();
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
// === EXTRACTION DES SORTIES PAR MODE DE PAIEMENT ===
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
// === TOTAUX PAIEMENTS ORDERS ===
$mv1_orders = isset($paymentDataOrders->total_mvola1) ? (float) $paymentDataOrders->total_mvola1 : 0;
$mv2_orders = isset($paymentDataOrders->total_mvola2) ? (float) $paymentDataOrders->total_mvola2 : 0;
$es1_orders = isset($paymentDataOrders->total_espece1) ? (float) $paymentDataOrders->total_espece1 : 0;
$es2_orders = isset($paymentDataOrders->total_espece2) ? (float) $paymentDataOrders->total_espece2 : 0;
$vb1_orders = isset($paymentDataOrders->total_virement_bancaire1) ? (float) $paymentDataOrders->total_virement_bancaire1 : 0;
$vb2_orders = isset($paymentDataOrders->total_virement_bancaire2) ? (float) $paymentDataOrders->total_virement_bancaire2 : 0;
// === TOTAUX PAIEMENTS AVANCES ===
$mv_avances = isset($paymentDataAvance->total_mvola) ? (float) $paymentDataAvance->total_mvola : 0;
$es_avances = isset($paymentDataAvance->total_espece) ? (float) $paymentDataAvance->total_espece : 0;
$vb_avances = isset($paymentDataAvance->total_virement_bancaire) ? (float) $paymentDataAvance->total_virement_bancaire : 0;
// === TOTAUX RECOUVREMENT ===
$me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
$bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
$be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
$mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
// === CALCUL DES SOLDES ACTUELS ===
$solde_mvola = ($mv1_orders + $mv2_orders + $mv_avances) - $me - $mb + $bm - $total_sortie_mvola;
$solde_espece = ($es1_orders + $es2_orders + $es_avances) + $me + $be - $total_sortie_espece;
$solde_banque = ($vb1_orders + $vb2_orders + $vb_avances) - $be - $bm + $mb - $total_sortie_virement;
// === VÉRIFICATION EN FONCTION DU TYPE DE RECOUVREMENT ===
switch ($send_mode) {
case 'MVOLA':
if ($get_mode === 'En espèce') {
// MVOLA → Espèce : vérifier solde MVOLA
return $solde_mvola >= $amount;
} elseif ($get_mode === 'Virement Bancaire') {
// MVOLA → Banque : vérifier solde MVOLA
return $solde_mvola >= $amount;
}
break;
case 'Virement Bancaire':
if ($get_mode === 'MVOLA') {
// Banque → MVOLA : vérifier solde banque
return $solde_banque >= $amount;
} elseif ($get_mode === 'En espèce') {
// Banque → Espèce : vérifier solde banque
return $solde_banque >= $amount;
}
break;
}
return false;
}
public function fetchTotalRecouvrementData() { public function fetchTotalRecouvrementData() {
helper(['url', 'form']); helper(['url', 'form']);

57
app/Controllers/ReportController.php

@ -305,7 +305,26 @@ class ReportController extends AdminController
} }
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }
if ($users['group_name'] === "Caissière") {
$orderPaid = $Orders->getPerformanceByCaissier($users['id']);
foreach ($orderPaid as $key => $value) {
// Calculer le prix de vente réel
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
? $value['discount']
: $value['prix_vente'];
// Colonnes à afficher : Caissier, Moto vendue, Date de vente, Prix de vente
$result['data'][$key] = [
$value['caissier_name'] ?? 'N/A', // Nom du caissier
($value['sku'] == "" ? $value['motoname'] : $value['sku']), // Moto
(new DateTime($value['datevente']))->format('d/m/Y'), // Date
number_format($prix_vente_reel, 0, '.', ' ') // Prix
];
}
return $this->response->setJSON($result);
}
// Pour COMMERCIALE : uniquement ses propres ventes // Pour COMMERCIALE : uniquement ses propres ventes
if ($users['group_name'] === "COMMERCIALE") { if ($users['group_name'] === "COMMERCIALE") {
$orderPaid = $Orders->getPerformanceByOrders2(); $orderPaid = $Orders->getPerformanceByOrders2();
@ -334,4 +353,42 @@ class ReportController extends AdminController
$result = ['data'=> []]; $result = ['data'=> []];
} }
public function fetchCaissierPerformances()
{
$result = ['data' => []];
$Orders = new Orders();
$session = session();
$users = $session->get('user');
if ($users['group_name'] !== "Caissière") {
return $this->response->setJSON($result);
}
$startDate = $this->request->getGet('startDate');
$endDate = $this->request->getGet('endDate');
// ✅ SI PAS DE DATES, PRENDRE AUJOURD'HUI PAR DÉFAUT
if (empty($startDate) && empty($endDate)) {
$startDate = date('Y-m-d');
$endDate = date('Y-m-d');
}
$orderPaid = $Orders->getPerformanceByCaissier($users['id'], $startDate, $endDate);
foreach ($orderPaid as $key => $value) {
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
? $value['discount']
: $value['prix_vente'];
$result['data'][$key] = [
$value['caissier_name'] ?? 'N/A',
($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('d/m/Y H:i'),
number_format($prix_vente_reel, 0, '.', ' ') . ' Ar'
];
}
return $this->response->setJSON($result);
}
} }

54
app/Controllers/SortieCaisseController.php

@ -371,28 +371,29 @@ class SortieCaisseController extends AdminController
$result = $SortieCaisse->updateSortieCaisse($id_sortie, $data); $result = $SortieCaisse->updateSortieCaisse($id_sortie, $data);
if ($result) { if ($result) {
// Créer une notification pour DAF et Direction // Créer une notification pour DAF et Direction DU MÊME STORE
if (class_exists('App\Controllers\NotificationController')) { if (class_exists('App\Controllers\NotificationController')) {
$Notification = new NotificationController(); $Notification = new NotificationController();
$montant = number_format($decaissement['montant_retire'], 0, ',', ' '); $montant = number_format($decaissement['montant_retire'], 0, ',', ' ');
$message = "💰 Décaissement payé - " . $montant . " Ar<br>" . $message = "💰 Décaissement payé - " . $montant . " Ar<br>" .
"Motif: " . $decaissement['motif'] . "<br>" . "Motif: " . $decaissement['motif'] . "<br>" .
"Caissière: " . $users['firstname'] . ' ' . $users['lastname']; "Caissière: " . $users['firstname'] . ' ' . $users['lastname'] . "<br>" .
"Store: " . $this->returnStoreName($users['store_id']);
// Notifier la Direction // Notifier la Direction DU MÊME STORE
$Notification->createNotification( $Notification->createNotification(
$message, $message,
"Direction", "Direction",
(int)$users['store_id'], (int)$users['store_id'], // ✅ Store ID de la caissière
'sortieCaisse' 'sortieCaisse'
); );
// Notifier le DAF // Notifier le DAF DU MÊME STORE
$Notification->createNotification( $Notification->createNotification(
$message, $message,
"DAF", "DAF",
(int)$users['store_id'], (int)$users['store_id'], // ✅ Store ID de la caissière
'sortieCaisse' 'sortieCaisse'
); );
} }
@ -400,9 +401,10 @@ class SortieCaisseController extends AdminController
return $this->response->setJSON([ return $this->response->setJSON([
'success' => true, 'success' => true,
'messages' => '✅ Décaissement marqué comme <strong>PAYÉ</strong><br>' . 'messages' => '✅ Décaissement marqué comme <strong>PAYÉ</strong><br>' .
'Direction et DAF ont été notifiés.<br>' . 'Direction et DAF de ' . $this->returnStoreName($users['store_id']) . ' ont été notifiés.<br>' .
'Montant: ' . number_format($decaissement['montant_retire'], 0, ',', ' ') . ' Ar' 'Montant: ' . number_format($decaissement['montant_retire'], 0, ',', ' ') . ' Ar'
]); ]);
} else { } else {
return $this->response->setJSON([ return $this->response->setJSON([
'success' => false, 'success' => false,
@ -719,7 +721,7 @@ class SortieCaisseController extends AdminController
} }
// Champs supplémentaires pour montant > 1,000,000 // Champs supplémentaires pour montant > 1,000,000
if ($montant_retire > 1000000) { if ($montant_retire >= 1000000) {
$data['numero_fiche'] = $this->request->getPost('numero_fiche') ?? ''; $data['numero_fiche'] = $this->request->getPost('numero_fiche') ?? '';
$data['date_fiche'] = $this->request->getPost('date_fiche') ?? null; $data['date_fiche'] = $this->request->getPost('date_fiche') ?? null;
$data['service_demandeur'] = $this->request->getPost('service_demandeur') ?? ''; $data['service_demandeur'] = $this->request->getPost('service_demandeur') ?? '';
@ -765,13 +767,23 @@ class SortieCaisseController extends AdminController
$result = $model->addSortieCaisse($data); $result = $model->addSortieCaisse($data);
if ($result) { if ($result) {
// Notification // Notification UNIQUEMENT pour la Direction du même store
if (class_exists('App\Controllers\NotificationController')) { if (class_exists('App\Controllers\NotificationController')) {
$Notification = new NotificationController(); $Notification = new NotificationController();
// ✅ Notifier UNIQUEMENT la Direction du store concerné
$Notification->createNotification( $Notification->createNotification(
"Nouvelle demande de décaissement de " . number_format($montant_retire, 0, ',', ' ') . " Ar (" . $mode_paiement . ")", "Nouvelle demande de décaissement de " . number_format($montant_retire, 0, ',', ' ') . " Ar (" . $mode_paiement . ")",
"Direction", "Direction",
(int)$user['store_id'], (int)$user['store_id'], // ✅ Store ID du créateur
'sortieCaisse'
);
// ✅ Notifier aussi le DAF du même store (si vous avez des DAF par store)
$Notification->createNotification(
"Nouvelle demande de décaissement de " . number_format($montant_retire, 0, ',', ' ') . " Ar (" . $mode_paiement . ")",
"DAF",
(int)$user['store_id'], // ✅ Store ID du créateur
'sortieCaisse' 'sortieCaisse'
); );
} }
@ -780,8 +792,10 @@ class SortieCaisseController extends AdminController
'success' => true, 'success' => true,
'messages' => 'Décaissement de ' . number_format($montant_retire, 0, ',', ' ') . ' Ar créé avec succès<br>' . 'messages' => 'Décaissement de ' . number_format($montant_retire, 0, ',', ' ') . ' Ar créé avec succès<br>' .
'Mode de paiement: ' . $mode_paiement . '<br>' . 'Mode de paiement: ' . $mode_paiement . '<br>' .
'Nouveau solde ' . $mode_paiement_label . ': ' . number_format($fonds_disponible - $montant_retire, 0, ',', ' ') . ' Ar' 'Nouveau solde ' . $mode_paiement_label . ': ' . number_format($fonds_disponible - $montant_retire, 0, ',', ' ') . ' Ar<br>' .
'<em>Notification envoyée à la Direction de ' . $this->returnStoreName($user['store_id']) . '</em>'
]); ]);
} else { } else {
return $this->response->setJSON([ return $this->response->setJSON([
'success' => false, 'success' => false,
@ -1076,24 +1090,28 @@ class SortieCaisseController extends AdminController
$statut = $this->request->getPost('statut'); $statut = $this->request->getPost('statut');
$message = ''; $message = '';
// ✅ Récupérer le décaissement pour avoir son store_id
$decaissement = $SortieCaisse->getSortieCaisseSingle($id_sortie);
$store_id = $decaissement['store_id'];
switch ($statut) { switch ($statut) {
case "Valider": case "Valider":
$message = "Décaissement validé avec succès"; $message = "✅ Votre décaissement a été validé par la Direction de " . $this->returnStoreName($store_id);
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse'); $Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
break; break;
case "Refuser": case "Refuser":
$message = "Un décaissement a été refusé"; $message = "❌ Votre décaissement a été refusé par la Direction de " . $this->returnStoreName($store_id) . "<br>Raison: " . $this->request->getPost('admin_raison');
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse'); $Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
break; break;
case "En attente": case "En attente":
$message = "Décaissement mis en attente"; $message = "⏳ Votre décaissement a été mis en attente par la Direction de " . $this->returnStoreName($store_id);
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse'); $Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
break; break;
} }
return $this->response->setJSON([ return $this->response->setJSON([
'success' => true, 'success' => true,
'messages' => 'Décaissement modifié avec succès !' 'messages' => 'Décaissement modifié avec succès ! Notification envoyée à la caissière de ' . $this->returnStoreName($store_id)
]); ]);
} else { } else {
return $this->response->setJSON([ return $this->response->setJSON([

42
app/Models/Orders.php

@ -29,6 +29,8 @@ class Orders extends Model
'discount', 'discount',
'paid_status', 'paid_status',
'user_id', 'user_id',
'validated_by',
'validated_at',
'store_id', 'store_id',
'tranche_1', 'tranche_1',
'tranche_2', 'tranche_2',
@ -825,4 +827,44 @@ public function getUserPerformanceByMonth(string $month)
return $results; return $results;
} }
public function getPerformanceByCaissier(int $caissierId, $startDate = null, $endDate = null)
{
$builder = $this->db->table('orders')
->select('
orders.id as orderid,
orders.net_amount,
orders.date_time as datevente,
orders.discount,
orders.validated_at,
products.price,
products.sku,
products.prix_vente,
products.name as motoname,
stores.id as store_id,
stores.name as store_name,
CONCAT(validator.firstname, " ", validator.lastname) as caissier_name
')
->join('stores', 'orders.store_id = stores.id', 'left')
->join('orders_item', 'orders.id = orders_item.order_id', 'left')
->join('products', 'products.id = orders_item.product_id', 'left')
->join('users as validator', 'validator.id = orders.validated_by', 'left')
->whereIn('orders.paid_status', [1, 3])
->where('orders.validated_by', $caissierId)
->where('orders.validated_by IS NOT NULL', null, false);
// ✅ AJOUT : Filtrage par dates
if ($startDate && $endDate) {
$builder->where('DATE(orders.validated_at) >=', $startDate)
->where('DATE(orders.validated_at) <=', $endDate);
} elseif ($startDate) {
$builder->where('DATE(orders.validated_at) >=', $startDate);
} elseif ($endDate) {
$builder->where('DATE(orders.validated_at) <=', $endDate);
}
return $builder->orderBy('orders.validated_at', 'DESC')
->get()
->getResultArray();
}
} }

25
app/Models/Recouvrement.php

@ -186,4 +186,29 @@ class Recouvrement extends Model{
return $reparation; return $reparation;
} }
public function getTotalByPaymentMode($start_date = null, $end_date = null)
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['DAF', 'Direction']);
$builder = $this->db->table('recouvrement');
// Conditions de base selon le rôle
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
// Filtre par date si fourni
if ($start_date && $end_date) {
$builder->where("recouvrement_date BETWEEN '$start_date' AND '$end_date'");
}
return $builder->select('
SUM(CASE WHEN send_money = "MVOLA" AND get_money = "En espèce" THEN recouvrement_montant ELSE 0 END) AS me,
SUM(CASE WHEN send_money = "Virement Bancaire" AND get_money = "MVOLA" THEN recouvrement_montant ELSE 0 END) AS bm,
SUM(CASE WHEN send_money = "Virement Bancaire" AND get_money = "En espèce" THEN recouvrement_montant ELSE 0 END) AS be,
SUM(CASE WHEN send_money = "MVOLA" AND get_money = "Virement Bancaire" THEN recouvrement_montant ELSE 0 END) AS mb
')->get()->getRowObject();
}
} }

70
app/Models/SortieCaisse.php

@ -67,20 +67,34 @@ protected $allowedFields = [
try { try {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
// ✅ DIRECTION : Voir uniquement les décaissements de SON store
if ($users['group_name'] === 'Direction') { if ($users['group_name'] === 'Direction') {
return $this return $this
->select('*') ->select('*')
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
if ($users['group_name'] === 'Conseil') { // ✅ DAF : Voir uniquement les décaissements de SON store
if ($users['group_name'] === 'DAF') {
return $this return $this
->select('*') ->select('*')
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
// ✅ CONSEIL : Voir TOUS les décaissements (multi-stores)
// if ($users['group_name'] === 'Conseil') {
// return $this
// ->select('*')
// ->orderBy('date_retrait', 'DESC')
// ->findAll();
// }
// ✅ CAISSIÈRE : Voir uniquement SES décaissements
if($users["group_name"]==="Caissière"){ if($users["group_name"]==="Caissière"){
return $this return $this
->select('*') ->select('*')
@ -88,6 +102,8 @@ protected $allowedFields = [
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
// ✅ AUTRES : Par défaut, voir uniquement leurs décaissements
return $this return $this
->select('*') ->select('*')
->where('user_id', $users['id']) ->where('user_id', $users['id'])
@ -104,24 +120,40 @@ protected $allowedFields = [
try { try {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
// ✅ DIRECTION : Voir uniquement les décaissements de SON store
if ($users['group_name'] === 'Direction') { if ($users['group_name'] === 'Direction') {
return $this return $this
->select('*') ->select('*')
->join('user_group', 'user_group.user_id = sortie_caisse.user_id') ->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
->where('user_group.group_id', 7) ->where('user_group.group_id', 7)
->where('sortie_caisse.store_id', $users['store_id']) // ✅ FILTRE PAR STORE
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
if ($users['group_name'] === 'Conseil') { // ✅ DAF : Voir uniquement les décaissements de SON store
if ($users['group_name'] === 'DAF') {
return $this return $this
->select('*') ->select('*')
->join('user_group', 'user_group.user_id = sortie_caisse.user_id') ->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
->where('user_group.group_id', 6) ->where('user_group.group_id', 7)
->where('sortie_caisse.store_id', $users['store_id']) // ✅ FILTRE PAR STORE
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
// ✅ CONSEIL : Voir TOUS les stores
// if ($users['group_name'] === 'Conseil') {
// return $this
// ->select('*')
// ->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
// ->where('user_group.group_id', 6)
// ->orderBy('date_retrait', 'DESC')
// ->findAll();
// }
// ✅ CAISSIÈRE : Voir uniquement SES décaissements
if($users["group_name"]==="Caissière"){ if($users["group_name"]==="Caissière"){
return $this return $this
->select('*') ->select('*')
@ -129,6 +161,7 @@ protected $allowedFields = [
->orderBy('date_retrait', 'DESC') ->orderBy('date_retrait', 'DESC')
->findAll(); ->findAll();
} }
return $this return $this
->select('*') ->select('*')
->where('user_id', $users['id']) ->where('user_id', $users['id'])
@ -139,7 +172,6 @@ protected $allowedFields = [
return []; return [];
} }
} }
public function addSortieCaisse(array $data) { public function addSortieCaisse(array $data) {
try { try {
return $this->insert($data); return $this->insert($data);
@ -174,7 +206,12 @@ protected $allowedFields = [
public function getTotalSortieCaisse() { public function getTotalSortieCaisse() {
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
// ✅ DIRECTION et DAF : Voir uniquement leur store
$isAdmin = in_array($users['group_name'], ['Direction', 'DAF']);
// ✅ CONSEIL : Voir TOUS les stores
$isConseil = $users['group_name'] === 'Conseil';
if ($isAdmin) { if ($isAdmin) {
try { try {
@ -184,6 +221,7 @@ protected $allowedFields = [
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement, SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(montant_retire) AS mr SUM(montant_retire) AS mr
') ')
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
->whereIn('statut', ['Valider', 'Payé']) ->whereIn('statut', ['Valider', 'Payé'])
->get() ->get()
->getRowObject(); ->getRowObject();
@ -196,7 +234,29 @@ protected $allowedFields = [
'mr' => 0 'mr' => 0
]; ];
} }
} elseif ($isConseil) {
// ✅ CONSEIL voit TOUS les stores
try {
return $this->select('
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
SUM(CASE WHEN mode_paiement = "MVOLA" THEN montant_retire ELSE 0 END) AS total_mvola,
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(montant_retire) AS mr
')
->whereIn('statut', ['Valider', 'Payé'])
->get()
->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur getTotalSortieCaisse (Conseil) : ' . $e->getMessage());
return (object)[
'total_espece' => 0,
'total_mvola' => 0,
'total_virement' => 0,
'mr' => 0
];
}
} else { } else {
// ✅ CAISSIÈRE : Uniquement son store
try { try {
return $this->select(' return $this->select('
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece, SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,

200
app/Views/dashboard.php

@ -712,10 +712,11 @@
</section> </section>
<!-- ✅ SECTION CAISSIER - SIMPLIFIÉ SANS GRAPHIQUE -->
<?php if ($isCaissier === true): ?> <?php if ($isCaissier === true): ?>
<!-- Section des totaux caisse --> <!-- Section des totaux caisse -->
<div class="container-fluid row"> <div class="container-fluid row">
<!-- ✅ MODIFIÉ : Utiliser total_caisse au lieu de total -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
@ -728,7 +729,6 @@
</div> </div>
</div> </div>
<!-- ✅ MODIFIÉ : Utiliser total_mvola_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
@ -741,7 +741,6 @@
</div> </div>
</div> </div>
<!-- ✅ MODIFIÉ : Utiliser total_espece_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
@ -754,7 +753,6 @@
</div> </div>
</div> </div>
<!-- ✅ MODIFIÉ : Utiliser total_vb_caisse -->
<div class="col-lg-3 col-xs-6"> <div class="col-lg-3 col-xs-6">
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
@ -768,7 +766,7 @@
</div> </div>
</div> </div>
<!-- ✅ NOUVEAU : Afficher le détail Orders vs Avances --> <!-- Détail Orders vs Avances -->
<div class="container-fluid row" style="margin-top: 10px;"> <div class="container-fluid row" style="margin-top: 10px;">
<div class="col-lg-6 col-xs-12"> <div class="col-lg-6 col-xs-12">
<div class="info-box bg-aqua"> <div class="info-box bg-aqua">
@ -791,7 +789,7 @@
</div> </div>
</div> </div>
<!-- ✅ NOUVEAU : Détail des avances par mode de paiement --> <!-- Détail des avances par mode de paiement -->
<div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;"> <div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="box box-info"> <div class="box box-info">
@ -824,11 +822,12 @@
</div> </div>
</div> </div>
<!-- Section Rapport de Performance -->
<section class="content-header"> <section class="content-header">
<h1>Rapport de Performance du Caissier</h1> <h1>📊 Mes Performances de Vente</h1>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><a href="#"><i class="fa fa-home"></i> Accueil</a></li> <li><a href="#"><i class="fa fa-home"></i> Accueil</a></li>
<li class="active" onclick="window.history.back()" style="cursor: pointer;">Rapports</li> <li class="active">Rapports</li>
</ol> </ol>
</section> </section>
@ -841,32 +840,52 @@
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-12 col-lg-12"> <div class="col-md-12 col-lg-12">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-header bg-success text-white">
<h3 class="card-title m-0">
<i class="fa fa-list"></i> Liste de mes ventes validées
</h3>
</div>
<div class="card-body"> <div class="card-body">
<!-- Filtres -->
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;"> <div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
<div class="col-md-3"> <div class="col-md-3">
<label for="startDate" class="form-label">Date de début</label> <label for="startDateCaissier" class="form-label">Date de début</label>
<input type="date" id="startDate" class="form-control"> <input type="date" id="startDateCaissier" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label for="endDate" class="form-label">Date de fin</label> <label for="endDateCaissier" class="form-label">Date de fin</label>
<input type="date" id="endDate" class="form-control"> <input type="date" id="endDateCaissier" class="form-control">
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-3 d-flex align-items-end">
<br> <br>
<button id="filteredB1" class="btn btn-primary w-100">Filtrer 🔍</button> <button id="filteredBtnCaissier" class="btn btn-primary w-100">
<button id="ExportBTN1" class="btn btn-success w-100">Exporter 📤</button> <i class="fa fa-filter"></i> Filtrer
</button>
<button id="ExportBTNCaissier" class="btn btn-success w-100" style="margin-left: 5px;">
<i class="fa fa-file-excel-o"></i> Exporter
</button>
</div> </div>
</div> </div>
<table id="caissierperf" class="table table-hover table-striped">
<!-- Dans la section caissière, assurez-vous que le tableau a la bonne structure -->
<table id="caissierperf" class="table table-hover table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th id="me">MVOLA & Espèce</th> <th>Caissier</th>
<th id="bm">Banque & MVOLA</th> <th>Moto vendue</th>
<th id="be">Banque & Espèce</th> <th>Date de vente</th>
<th id="mb">MVOLA & Banque</th> <th>Prix de vente</th>
<th id="mr">Montant total</th>
</tr> </tr>
</thead> </thead>
<tbody>
<!-- Les données seront chargées automatiquement par DataTables -->
</tbody>
<tfoot>
<tr>
<th colspan="3" style="text-align:right; font-weight: bold;">Total :</th>
<th style="font-weight: bold;"></th>
</tr>
</tfoot>
</table> </table>
</div> </div>
</div> </div>
@ -879,86 +898,101 @@
</div> </div>
</section> </section>
<div style="width: 80%; margin: auto;"> <!-- ✅ SCRIPT POUR LE TABLEAU CAISSIER UNIQUEMENT -->
<canvas id="salesChart"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script> <script>
var manageTable; (function() {
var caissierTable;
$(document).ready(function () { $(document).ready(function () {
console.log('🔍 Initialisation du tableau caissier...');
// Initialize the datatable // Configuration DataTable pour caissier
manageTable = $('#caissierperf').DataTable({ caissierTable = $('#caissierperf').DataTable({
'ajax': 'recouvrement/fetchTotalRecouvrementData', 'ajax': {
'order': [], 'url': '<?= base_url('reports/detail/fetchCaissierPerformances') ?>',
'pageLength': 5, 'data': function(d) {
'lengthMenu': [ // ✅ AJOUT : Envoyer les dates au serveur
[5, 10, 25, 50, -1], d.startDate = $('#startDateCaissier').val();
[5, 10, 25, 50, "All"] d.endDate = $('#endDateCaissier').val();
},
'dataSrc': function(json) {
console.log('📊 Données reçues:', json);
return json.data;
},
'error': function(xhr, error, thrown) {
console.error('❌ Erreur AJAX:', error, thrown);
console.error('Réponse:', xhr.responseText);
}
},
'columns': [
{ 'data': 0 }, // Caissier
{ 'data': 1 }, // Moto vendue
{ 'data': 2 }, // Date
{ 'data': 3 } // Prix
], ],
"columnDefs": [{ 'order': [[2, 'desc']],
"targets": "_all", 'pageLength': 10,
"className": "text-left" 'lengthMenu': [[5, 10, 25, 50, -1], [5, 10, 25, 50, "Tout"]],
}], 'language': {
'processing': "Traitement en cours...",
"footerCallback": function (row, data, start, end, display) { 'search': "Rechercher&nbsp;:",
'lengthMenu': "Afficher _MENU_ éléments",
'info': "Affichage de l'élément _START_ à _END_ sur _TOTAL_ éléments",
'infoEmpty': "Affichage de l'élément 0 à 0 sur 0 élément",
'infoFiltered': "(filtré de _MAX_ éléments au total)",
'loadingRecords': "Chargement en cours...",
'zeroRecords': "Aucune vente enregistrée",
'emptyTable': "Aucune donnée disponible",
'paginate': {
'first': "Premier",
'previous': "Précédent",
'next': "Suivant",
'last': "Dernier"
}
},
'footerCallback': function (row, data, start, end, display) {
const api = this.api(); const api = this.api();
// Helper function to parse string to float
const parseNumber = function (i) { const parseNumber = function (i) {
return typeof i === 'string' ? return typeof i === 'string'
parseFloat(i.replace(/[^\d.-]/g, '')) : // remove currency symbols, commas ? parseFloat(i.replace(/[^\d.-]/g, ''))
typeof i === 'number' ? : typeof i === 'number' ? i : 0;
i : 0;
}; };
} const totalPrixVente = api
}); .column(3, { page: 'current' })
.data()
.reduce((a, b) => parseNumber(a) + parseNumber(b), 0);
$('#filteredB1').on('click', function () { const totalFormate = totalPrixVente.toLocaleString('fr-FR');
const startDate = $('#startDate').val();
const endDate = $('#endDate').val();
const pvente = $('#pvente').val(); // id de l'utilisateur ici
$.ajax({ $(api.column(3).footer()).html('<strong>' + totalFormate + ' Ar</strong>');
url: '<?= base_url("recouvrement/fetchTotalRecouvrementData") ?>',
type: 'GET',
data: {
start_date: startDate,
end_date: endDate
},
dataType: 'json',
success: function (response) {
if (response) {
$('#mr').text(response.tr); // par exemple
$('#me').text(response.me);
$('#bm').text(response.bm);
$('#be').text(response.be);
$('#mb').text(response.mb);
}
} }
}); });
});
});
// ✅ CORRECTION : Filtrage par dates
$('#filteredBtnCaissier').on('click', function () {
const startDate = $('#startDateCaissier').val();
const endDate = $('#endDateCaissier').val();
document.getElementById('ExportBTN1').addEventListener('click', function () { console.log('🔍 Filtrage:', startDate, endDate);
// Select your table
var table = document.getElementById('caissierperf');
// Convert it to a workbook // Recharger les données avec les nouveaux paramètres
var wb = XLSX.utils.table_to_book(table, { caissierTable.ajax.reload();
sheet: "Feuille1"
}); });
// Export it // Export Excel
XLSX.writeFile(wb, 'export-caissier-performance.xlsx'); $('#ExportBTNCaissier').on('click', function () {
const table = document.getElementById('caissierperf');
const wb = XLSX.utils.table_to_book(table, { sheet: "Performances Caissier" });
const today = new Date().toISOString().split('T')[0];
const fileName = 'performances_caissier_' + today + '.xlsx';
XLSX.writeFile(wb, fileName);
}); });
});
})();
</script> </script>
<!-- </div> -->
<?php endif; ?> <?php endif; ?>
<!-- securite --> <!-- securite -->
@ -1152,12 +1186,12 @@
<div class="card-body"> <div class="card-body">
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;"> <div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
<div class="col-md-3"> <div class="col-md-3">
<label for="startDate" class="form-label">Date de début</label> <label for="startDateCaissier" class="form-label">Date de début</label>
<input type="date" id="startDate" class="form-control"> <input type="date" id="startDateCaissier" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label for="endDate" class="form-label">Date de fin</label> <label for="endDateCaissier" class="form-label">Date de fin</label>
<input type="date" id="endDate" class="form-control"> <input type="date" id="endDateCaissier" class="form-control">
</div> </div>
<div class="col-md-3 d-flex align-items-end"> <div class="col-md-3 d-flex align-items-end">
<br> <br>

90
app/Views/orders/edit.php

@ -327,22 +327,38 @@
$("#mainOrdersNav").addClass('active'); $("#mainOrdersNav").addClass('active');
$("#manageOrdersNav").addClass('active'); $("#manageOrdersNav").addClass('active');
// ✅ CORRECTION : Gestion des tranches de paiement
var paymentTranche = 1; var paymentTranche = 1;
var netAmount = parseFloat($('#net_amount_value').val()) || 0;
// ✅ Fonction pour obtenir le montant à répartir (discount ou gross_amount)
function getMontantTotal() {
var discount = parseFloat($('#discount').val()) || 0;
var grossAmount = parseFloat($('#gross_amount_value').val()) || 0;
return discount > 0 ? discount : grossAmount;
}
var netAmount = getMontantTotal();
$('#payment_amount_1').val(netAmount); $('#payment_amount_1').val(netAmount);
function addPaymentTranche(paymentTranche) { function addPaymentTranche(paymentTranche) {
if (parseInt(paymentTranche) === 2) { if (parseInt(paymentTranche) === 2) {
$("#paid_status_1").show(); $("#paid_status_1").show();
$("#paid_status_2").show(); $("#paid_status_2").show();
$("#montant_reference").show(); // ✅ Afficher le montant de référence
var amount1 = parseFloat($('#payment_amount_1').val()) || 0; var amount1 = parseFloat($('#payment_amount_1').val()) || 0;
var amount2 = netAmount - amount1; var montantTotal = getMontantTotal();
$('#payment_amount_2').val(amount2); var amount2 = montantTotal - amount1;
$('#payment_amount_2').val(amount2.toFixed(2));
} else { } else {
$("#paid_status_1").show(); $("#paid_status_1").show();
$("#paid_status_2").hide(); $("#paid_status_2").hide();
$("#montant_reference").hide(); // ✅ Cacher le montant de référence
$('#payment_mode_2').val(''); $('#payment_mode_2').val('');
// ✅ Remplir tranche 1 avec le montant total
var montantTotal = getMontantTotal();
$('#payment_amount_1').val(montantTotal.toFixed(2));
} }
} }
@ -351,10 +367,15 @@
updateMontantTranches(); updateMontantTranches();
}); });
// ✅ CORRECTION : Recalculer tranche 2 quand tranche 1 change
$('#payment_amount_1').on("input", function() { $('#payment_amount_1').on("input", function() {
var amount1 = parseFloat($(this).val()) || 0; var amount1 = parseFloat($(this).val()) || 0;
var amount2 = netAmount - amount1; var montantTotal = getMontantTotal();
$('#payment_amount_2').val(amount2); var amount2 = montantTotal - amount1;
if (amount2 < 0) amount2 = 0;
$('#payment_amount_2').val(amount2.toFixed(2));
}); });
addPaymentTranche(paymentTranche); addPaymentTranche(paymentTranche);
@ -379,16 +400,17 @@
if (value.numero_de_moteur) { if (value.numero_de_moteur) {
displayText += ' | ' + value.numero_de_moteur; displayText += ' | ' + value.numero_de_moteur;
} }
// ✅ Ne plus afficher la puissance if (value.puissance) {
displayText += ' | ' + value.puissance;
}
html += '<option value="' + value.id + '">' + displayText + '</option>'; html += '<option value="' + value.id + '">' + displayText + '</option>';
}); });
html += '</select>' + html += '</select>' +
'</td>' + '</td>' +
// ✅ Colonne puissance visible et modifiable '<td><input type="text" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="Puissance" autocomplete="off"></td>' +
'<td><input type="text" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="Puissance" autocomplete="off" value="1"></td>' + '<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control" disabled><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"></td>' +
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' + '<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control" disabled><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
'<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
'<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' + '<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
'</tr>'; '</tr>';
@ -404,6 +426,7 @@
return false; return false;
}); });
});
function getTotal(row = null) { function getTotal(row = null) {
if (row) { if (row) {
@ -419,12 +442,10 @@
function getProductData(row_id) { function getProductData(row_id) {
var product_id = $("#product_" + row_id).val(); var product_id = $("#product_" + row_id).val();
if (product_id == "") { if (product_id == "") {
$("#rate_" + row_id).val(""); $("#rate_" + row_id).val("");
$("#rate_value_" + row_id).val(""); $("#rate_value_" + row_id).val("");
$("#min_price_" + row_id).val(""); $("#puissance_" + row_id).val("");
$("#puissance_" + row_id).val("1"); // ✅ Réinitialiser à 1
$("#amount_" + row_id).val(""); $("#amount_" + row_id).val("");
$("#amount_value_" + row_id).val(""); $("#amount_value_" + row_id).val("");
} else { } else {
@ -434,33 +455,16 @@
data: { product_id: product_id }, data: { product_id: product_id },
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
console.log('✅ Response:', response); // Debug $("#rate_" + row_id).val(response.prix_vente);
$("#rate_value_" + row_id).val(response.prix_vente);
var prixVente = parseFloat(response.prix_vente) || 0; $("#puissance_" + row_id).val(response.puissance || '');
var prixMinimal = parseFloat(response.prix_minimal) || 0;
if (prixVente < 0) prixVente = 0; var total = Number(response.prix_vente);
if (prixMinimal < 0) prixMinimal = 0;
$("#rate_" + row_id).val(prixVente);
$("#rate_value_" + row_id).val(prixVente);
$("#min_price_" + row_id).val(prixMinimal);
// ✅ CORRECTION : Remplir la puissance
var puissanceValue = response.puissance || '1';
console.log('✅ Puissance extraite:', puissanceValue); // Debug
$("#puissance_" + row_id).val(puissanceValue);
// ✅ Calculer le montant (prix * puissance)
var total = prixVente * 1; // Pour l'instant on garde qty=1
total = total.toFixed(2); total = total.toFixed(2);
$("#amount_" + row_id).val(total); $("#amount_" + row_id).val(total);
$("#amount_value_" + row_id).val(total); $("#amount_value_" + row_id).val(total);
subAmount(); subAmount();
},
error: function(xhr, status, error) {
console.error('❌ Erreur AJAX:', error);
} }
}); });
} }
@ -515,6 +519,7 @@
$("#remaining_value").val(remaning.toFixed(2)); $("#remaining_value").val(remaning.toFixed(2));
} }
// ✅ CORRECTION : Mettre à jour les tranches après chaque calcul
updateMontantTranches(); updateMontantTranches();
} }
@ -533,12 +538,14 @@
subAmount(); subAmount();
} }
// ✅ CORRECTION : Fonction pour obtenir le montant pour les tranches
function getMontantPourTranches() { function getMontantPourTranches() {
var discount = parseFloat($("#discount").val()) || 0; var discount = parseFloat($("#discount").val()) || 0;
var grossAmount = parseFloat($("#gross_amount_value").val()) || 0; var grossAmount = parseFloat($("#gross_amount_value").val()) || 0;
return discount > 0 ? discount : grossAmount; return discount > 0 ? discount : grossAmount;
} }
// ✅ CORRECTION : Mettre à jour l'affichage du montant de référence
function updateMontantTranches() { function updateMontantTranches() {
var montant = getMontantPourTranches(); var montant = getMontantPourTranches();
var discount = parseFloat($("#discount").val()) || 0; var discount = parseFloat($("#discount").val()) || 0;
@ -552,11 +559,17 @@
$("#montant_source_label").text("(Montant brut)"); $("#montant_source_label").text("(Montant brut)");
} }
if ($("#payment_amount_1").val()) { // ✅ CORRECTION : Mettre à jour tranche 1 si en mode 1 tranche
var paymentMode = $("#payment_mode").val();
if (parseInt(paymentMode) === 1) {
$("#payment_amount_1").val(montant.toFixed(2));
} else {
// ✅ En mode 2 tranches, recalculer tranche 2
calculerTranche2(); calculerTranche2();
} }
} }
// ✅ CORRECTION : Calculer tranche 2 basé sur le montant total actuel
function calculerTranche2() { function calculerTranche2() {
var montantTotal = getMontantPourTranches(); var montantTotal = getMontantPourTranches();
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0; var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
@ -565,12 +578,17 @@
$("#payment_amount_2").val(tranche2.toFixed(2)); $("#payment_amount_2").val(tranche2.toFixed(2));
} }
// ✅ CORRECTION : Écouter les changements de discount
$("#discount").on('keyup', function() { $("#discount").on('keyup', function() {
updateMontantTranches(); subAmount();
}); });
// ✅ Initialisation au chargement
const net_amount_value = document.getElementById('net_amount_value'); const net_amount_value = document.getElementById('net_amount_value');
const net_amount = document.getElementById('net_amount'); const net_amount = document.getElementById('net_amount');
const payment_amount_1 = document.getElementById('payment_amount_1'); const payment_amount_1 = document.getElementById('payment_amount_1');
if (payment_amount_1 && net_amount) {
payment_amount_1.value = net_amount.value; payment_amount_1.value = net_amount.value;
}
</script> </script>

172
app/Views/recouvrement/index.php

@ -106,7 +106,7 @@
<div class="small-box" style="background-color:#A9A9A9;"> <div class="small-box" style="background-color:#A9A9A9;">
<div class="inner"> <div class="inner">
<h3 id="total_espece"><?php echo number_format($total_espece, 0, '.', ' '); ?></h3> <h3 id="total_espece"><?php echo number_format($total_espece, 0, '.', ' '); ?></h3>
<p>Total en Caisse</p> <p>Total en Espèce</p>
</div> </div>
<div class="icon"> <div class="icon">
<i class="ion ion-cash"></i> <i class="ion ion-cash"></i>
@ -232,7 +232,8 @@
<?php endif; ?> <?php endif; ?>
<!-- Inclure SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
@ -245,7 +246,8 @@
if (window.location.search.startsWith("?_=")) { if (window.location.search.startsWith("?_=")) {
window.location.href = window.location.origin + window.location.pathname; window.location.href = window.location.origin + window.location.pathname;
} }
// datatable-fr.js
// Configuration DataTable en français
$.extend(true, $.fn.dataTable.defaults, { $.extend(true, $.fn.dataTable.defaults, {
language: { language: {
sProcessing: "Traitement en cours...", sProcessing: "Traitement en cours...",
@ -270,7 +272,7 @@ $.extend(true, $.fn.dataTable.defaults, {
} }
}); });
// Initialisation du DataTable
manageTable = $('#manageTable').DataTable({ manageTable = $('#manageTable').DataTable({
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>', 'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',
'order': [], 'order': [],
@ -289,49 +291,75 @@ $.extend(true, $.fn.dataTable.defaults, {
] ]
}); });
// ====== FONCTION DE RAFRAÎCHISSEMENT AUTOMATIQUE ======
function refreshData() {
// Recharger le DataTable
manageTable.ajax.reload(null, false);
// Recharger les totaux
fetchTotalData();
}
// Rafraîchir automatiquement toutes les 5 secondes
setInterval(refreshData, 5000);
}) })
// ====== CRÉATION DE RECOUVREMENT ======
$("#create_form").unbind('submit').on('submit', function() { $("#create_form").unbind('submit').on('submit', function() {
var form = $(this); var form = $(this);
// remove the text-danger // Supprimer les messages d'erreur
$(".text-danger").remove(); $(".text-danger").remove();
$.ajax({ $.ajax({
url: form.attr('action'), url: form.attr('action'),
type: form.attr('method'), type: form.attr('method'),
data: form.serialize(), // /converting the form data into array and sending it to server data: form.serialize(),
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
if (response.success === true) {
// Recharger immédiatement les données
manageTable.ajax.reload(null, false); manageTable.ajax.reload(null, false);
fetchTotalData();
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages + '<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>'); '</div>');
// Fermer le modal
// hide the modal
$("#createModal").modal('hide'); $("#createModal").modal('hide');
// reset the form // Réinitialiser le formulaire
$("#create_form")[0].reset(); $("#create_form")[0].reset();
$("#create_form .form-group").removeClass('has-error').removeClass('has-success'); $("#create_form .form-group").removeClass('has-error').removeClass('has-success');
} else { // Faire disparaître le message après 3 secondes
setTimeout(function() {
$("#messages").fadeOut('slow', function() {
$(this).html('').show();
});
}, 3000);
if (response.messages instanceof Object) { } else {
// Afficher une alerte SweetAlert pour les erreurs de solde
if (response.messages === 'Recouvrement impossible : solde insuffisant pour ce type de transaction') {
Swal.fire({
icon: 'error',
title: 'Recouvrement impossible',
text: 'Le solde est insuffisant pour effectuer ce recouvrement. Veuillez vérifier les totaux disponibles.',
confirmButtonColor: '#3085d6',
confirmButtonText: 'OK'
});
} else if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) { $.each(response.messages, function(index, value) {
var id = $("#" + index); var id = $("#" + index);
id.closest('.form-group') id.closest('.form-group')
.removeClass('has-error') .removeClass('has-error')
.removeClass('has-success') .removeClass('has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success'); .addClass(value.length > 0 ? 'has-error' : 'has-success');
id.after(value); id.after(value);
}); });
} else { } else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
@ -340,19 +368,22 @@ $.extend(true, $.fn.dataTable.defaults, {
'</div>'); '</div>');
} }
} }
},
error: function() {
$("#messages").html('<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Erreur!</strong> Une erreur est survenue lors de la création.' +
'</div>');
} }
}); });
return false; return false;
}); });
// ====== SUPPRESSION DE RECOUVREMENT ======
function removeFunc(id) { function removeFunc(id) {
if (id) { if (id) {
$("#removeForm").on('submit', function() { $("#removeForm").unbind('submit').on('submit', function() {
var form = $(this); var form = $(this);
// remove the text-danger
$(".text-danger").remove(); $(".text-danger").remove();
$.ajax({ $.ajax({
@ -363,26 +394,37 @@ $.extend(true, $.fn.dataTable.defaults, {
}, },
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
// Recharger immédiatement les données
manageTable.ajax.reload(null, false); manageTable.ajax.reload(null, false);
fetchTotalData();
if (response.success === true) { if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages + '<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>'); '</div>');
// hide the modal
$("#removeModal").modal('hide'); $("#removeModal").modal('hide');
} else { // Faire disparaître le message après 3 secondes
setTimeout(function() {
$("#messages").fadeOut('slow', function() {
$(this).html('').show();
});
}, 3000);
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages + '<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>'); '</div>');
} }
},
error: function() {
$("#messages").html('<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Erreur!</strong> Une erreur est survenue lors de la suppression.' +
'</div>');
} }
}); });
@ -391,7 +433,7 @@ $.extend(true, $.fn.dataTable.defaults, {
} }
} }
// update function // ====== MODIFICATION DE RECOUVREMENT ======
function editFunc(id) { function editFunc(id) {
$.ajax({ $.ajax({
url: '<?= base_url('recouvrement/fetchRecouvrementSingle') ?>/' + id, url: '<?= base_url('recouvrement/fetchRecouvrementSingle') ?>/' + id,
@ -400,81 +442,119 @@ $.extend(true, $.fn.dataTable.defaults, {
success: function(response) { success: function(response) {
$("#recouvrement_montant_edit").val(response.recouvrement_montant).change(); $("#recouvrement_montant_edit").val(response.recouvrement_montant).change();
$("#recouvrement_date_edit").val(response.recouvrement_date).change(); $("#recouvrement_date_edit").val(response.recouvrement_date).change();
// submit the edit from
// Soumettre le formulaire de modification
$("#update_form").unbind('submit').bind('submit', function() { $("#update_form").unbind('submit').bind('submit', function() {
var form = $(this); var form = $(this);
// remove the text-danger
$(".text-danger").remove(); $(".text-danger").remove();
$.ajax({ $.ajax({
url: form.attr('action').replace(/\/?$/, '/') + id, url: form.attr('action').replace(/\/?$/, '/') + id,
type: form.attr('method'), type: form.attr('method'),
data: form.serialize(), // /converting the form data into array and sending it to server data: form.serialize(),
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
// Recharger immédiatement les données
manageTable.ajax.reload(null, false); manageTable.ajax.reload(null, false);
fetchTotalData();
$("#updateModal").modal('hide'); $("#updateModal").modal('hide');
if (response.success === true) { if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages + '<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>'); '</div>');
// reset the form
$("#updateForm .form-group").removeClass('has-error').removeClass('has-success'); $("#updateForm .form-group").removeClass('has-error').removeClass('has-success');
// Faire disparaître le message après 3 secondes
setTimeout(function() {
$("#messages").fadeOut('slow', function() {
$(this).html('').show();
});
}, 3000);
} else { } else {
$("#updateModal").modal('hide');
if (response.messages instanceof Object) { if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) { $.each(response.messages, function(index, value) {
var id = $("#" + index); var id = $("#" + index);
id.closest('.form-group') id.closest('.form-group')
.removeClass('has-error') .removeClass('has-error')
.removeClass('has-success') .removeClass('has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success'); .addClass(value.length > 0 ? 'has-error' : 'has-success');
id.after(value); id.after(value);
}); });
} else { } else {
$("#updateModal").modal('hide');
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' + $("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages + '<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>'); '</div>');
} }
} }
},
error: function() {
$("#updateModal").modal('hide');
$("#messages").html('<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong>Erreur!</strong> Une erreur est survenue lors de la modification.' +
'</div>');
} }
}); });
return false; return false;
}); });
} }
}); });
} }
</script>
<script> // ====== MISE À JOUR DES TOTAUX ======
const endpoint = '<?= base_url('recouvrement/fetchTotalData') ?>'; const endpoint = '<?= base_url('recouvrement/fetchTotalData') ?>';
async function fetchTotalData() { async function fetchTotalData() {
try { try {
let res = await fetch(endpoint, { headers:{'Content-Type':'application/json'} }); let res = await fetch(endpoint, {
headers: {
'Content-Type': 'application/json'
}
});
if (!res.ok) throw new Error(res.status); if (!res.ok) throw new Error(res.status);
let data = await res.json(); let data = await res.json();
$('#total').text(formatNum(data.total));
$('#total_mvola').text(formatNum(data.total_mvola)); // Mettre à jour les totaux avec animation
$('#total_caisse').text(formatNum(data.total_espece)); updateValueWithAnimation('#total', data.total);
$('#total_banque').text(formatNum(data.total_virement_bancaire)); updateValueWithAnimation('#total_mvola', data.total_mvola);
updateValueWithAnimation('#total_espece', data.total_espece);
updateValueWithAnimation('#total_banque', data.total_virement_bancaire);
} catch (e) { } catch (e) {
console.error('fetchTotalData error', e); console.error('fetchTotalData error', e);
} }
} }
// Fonction pour mettre à jour une valeur avec animation
function updateValueWithAnimation(selector, newValue) {
const element = $(selector);
const currentText = element.text().replace(/\s/g, '');
const currentValue = parseFloat(currentText) || 0;
const formattedNewValue = formatNum(newValue);
// Si la valeur a changé, ajouter une animation
if (currentText !== formattedNewValue.replace(/\s/g, '')) {
element.fadeOut(200, function() {
$(this).text(formattedNewValue).fadeIn(200);
});
}
}
function formatNum(n) { function formatNum(n) {
n = parseFloat(n)||0; return n.toLocaleString('fr-FR').replace(/\s/g,' '); n = parseFloat(n) || 0;
return n.toLocaleString('fr-FR').replace(/\s/g, ' ');
} }
// Charger les totaux au démarrage
fetchTotalData(); fetchTotalData();
setInterval(fetchTotalData, 1000);
// Rafraîchir les totaux toutes les 3 secondes
setInterval(fetchTotalData, 3000);
</script> </script>

219
app/Views/sortieCaisse/index.php

@ -290,19 +290,41 @@ input[readonly], select[disabled], textarea[readonly] {
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="motif_select" class="form-label">Motif</label> <label for="motif_select" class="form-label">Motif</label>
<select class="form-control" id="motif_select" name="motif_select" required>
<!-- Select avec liste + option "Autre" -->
<select class="form-control" id="motif_select_dropdown" name="motif_select_dropdown">
<?php <?php
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil"; $isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
$isCaissier = $users['group_name'] == "Caissière"; $isCaissier = $users['group_name'] == "Caissière";
$options = $isAdmin ? $admin_options : $caissier_options; $options = $isAdmin ? $admin_options : $caissier_options;
echo "<option value=''>Veuillez sélectionner une raison</option>\n";
foreach ($options as $option) { foreach ($options as $option) {
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n"; echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
} }
?> ?>
<option value="" selected>Veuillez sélectionner une raison</option> <option value="AUTRE">✏️ Autre (saisir manuellement)</option>
</select> </select>
<!-- Input caché qui s'affiche quand on choisit "Autre" -->
<input
type="text"
class="form-control"
id="motif_select_custom"
name="motif_select_custom"
placeholder="Saisissez votre motif personnalisé"
style="display: none; margin-top: 10px;"
>
<!-- Champ caché pour envoyer la valeur finale -->
<input type="hidden" id="motif_select" name="motif_select" required>
<small class="text-muted" style="display: block; margin-top: 5px;">
<i class="fa fa-info-circle"></i> Sélectionnez un motif prédéfini ou choisissez "Autre" pour saisir librement
</small>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="mode_paiement" class="form-label">Mode de paiement</label> <label for="mode_paiement" class="form-label">Mode de paiement</label>
@ -574,22 +596,34 @@ input[readonly], select[disabled], textarea[readonly] {
<!-- Motif et Mode de paiement --> <!-- Motif et Mode de paiement -->
<div class="row"> <div class="row">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="motif_select_edit" class="form-label"><i class="fa fa-lock text-muted"></i> Motif</label> <label for="motif_select_edit" class="form-label">
<select class="form-control" id="motif_select_edit" name="motif_select_edit" required disabled style="background-color: #e9ecef; cursor: not-allowed;"> <i class="fa fa-lock text-muted"></i> Motif
</label>
<!-- Select désactivé pour l'affichage -->
<select class="form-control" id="motif_select_edit_dropdown" disabled style="background-color: #e9ecef; cursor: not-allowed;">
<?php <?php
$session = session(); $session = session();
$users = $session->get('user'); $users = $session->get('user');
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil"; $isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
$options = $isAdmin ? $admin_options : $caissier_options; $options = $isAdmin ? $admin_options : $caissier_options;
echo "<option value=''>Veuillez sélectionner une raison</option>\n";
foreach ($options as $option) { foreach ($options as $option) {
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n"; echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
} }
?> ?>
<option value="" selected>Veuillez sélectionner une raison</option>
</select> </select>
<!-- Champ caché pour envoyer la valeur du motif -->
<input type="hidden" id="motif_select_edit_hidden" name="motif_select_edit"> <!-- Champ caché pour envoyer la valeur (car le select est disabled) -->
<input type="hidden" id="motif_select_edit" name="motif_select_edit">
<small class="motif-help-text">
<i class="fa fa-info-circle"></i> Le motif ne peut pas être modifié
</small>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="mode_paiement_edit" class="form-label"><i class="fa fa-edit text-primary"></i> Mode de paiement</label> <label for="mode_paiement_edit" class="form-label"><i class="fa fa-edit text-primary"></i> Mode de paiement</label>
<select class="form-control" id="mode_paiement_edit" name="mode_paiement_edit" required> <select class="form-control" id="mode_paiement_edit" name="mode_paiement_edit" required>
@ -745,6 +779,7 @@ input[readonly], select[disabled], textarea[readonly] {
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
<script> <script>
// ============================================ // ============================================
// CONVERSION NOMBRE EN LETTRES (FRANÇAIS) // CONVERSION NOMBRE EN LETTRES (FRANÇAIS)
@ -891,6 +926,31 @@ $(document).ready(function() {
$('#date_demande').val(today); $('#date_demande').val(today);
$('#date_fiche').val(today); $('#date_fiche').val(today);
// ============================================
// ✅ GESTION DU CHAMP MOTIF SELECT MODIFIABLE
// ============================================
// Gestion du select motif avec option personnalisée
$('#motif_select_dropdown').on('change', function() {
const selectedValue = $(this).val();
if (selectedValue === 'AUTRE') {
// Afficher le champ de saisie libre
$('#motif_select_custom').show().focus();
$('#motif_select').val(''); // Vider le champ caché
} else {
// Cacher le champ de saisie et utiliser la valeur sélectionnée
$('#motif_select_custom').hide().val('');
$('#motif_select').val(selectedValue);
}
});
// Mettre à jour le champ caché quand on tape dans le champ personnalisé
$('#motif_select_custom').on('input', function() {
const customValue = $(this).val();
$('#motif_select').val(customValue);
});
// ============================================ // ============================================
// GESTION DU MONTANT ET CONVERSION EN LETTRES // GESTION DU MONTANT ET CONVERSION EN LETTRES
// ============================================ // ============================================
@ -911,8 +971,8 @@ $(document).ready(function() {
const words = numberToFrenchWords(num); const words = numberToFrenchWords(num);
montantLettreInput.val(words + ' ariary'); montantLettreInput.val(words + ' ariary');
// Gestion du basculement de formulaire // Gestion du basculement de formulaire - CORRIGÉ >= 1000000
if (num > 1000000) { if (num >= 1000000) {
$('#nom_demandeur').val($('#nom').val()); $('#nom_demandeur').val($('#nom').val());
$('#fonction_demandeur').val($('#fonction').val()); $('#fonction_demandeur').val($('#fonction').val());
$('#montant_estime').val(this.value); $('#montant_estime').val(this.value);
@ -930,7 +990,7 @@ $(document).ready(function() {
$('#back-to-im1').on('click', function() { $('#back-to-im1').on('click', function() {
$('#form-im23').hide(); $('#form-im23').hide();
$('#form-im1').show(); $('#form-im1').show();
montantInput.val('500000'); montantInput.val('999999');
montantInput.trigger('input'); montantInput.trigger('input');
}); });
@ -944,7 +1004,7 @@ $(document).ready(function() {
montantInput.trigger('input'); montantInput.trigger('input');
}); });
// Réinitialisation du modal // Réinitialisation du modal - AVEC RÉINITIALISATION DU MOTIF
$('#createModal').on('hidden.bs.modal', function () { $('#createModal').on('hidden.bs.modal', function () {
$('#form-im1').show(); $('#form-im1').show();
$('#form-im23').hide(); $('#form-im23').hide();
@ -952,6 +1012,11 @@ $(document).ready(function() {
$('#date_demande').val(today); $('#date_demande').val(today);
$('#date_fiche').val(today); $('#date_fiche').val(today);
montantLettreInput.val(''); montantLettreInput.val('');
// ✅ Réinitialiser les champs motif
$('#motif_select_dropdown').val('');
$('#motif_select_custom').hide().val('');
$('#motif_select').val('');
}); });
// ============================================ // ============================================
@ -1006,12 +1071,26 @@ $(document).ready(function() {
}); });
// ============================================ // ============================================
// CRÉATION AVEC SWEETALERT2 - CORRIGÉ // ✅ CRÉATION AVEC SWEETALERT2 + VALIDATION MOTIF
// ============================================ // ============================================
$("#create_form_sortie").unbind('submit').on('submit', function(e) { $("#create_form_sortie").unbind('submit').on('submit', function(e) {
e.preventDefault(); e.preventDefault();
$(".text-danger").remove(); $(".text-danger").remove();
// ✅ Validation du motif
const motifValue = $('#motif_select').val();
if (!motifValue || motifValue.trim() === '') {
Swal.fire({
icon: 'warning',
title: 'Motif requis',
text: 'Veuillez sélectionner ou saisir un motif',
confirmButtonColor: '#f39c12'
});
return false;
}
var formData = new FormData(this); var formData = new FormData(this);
// CORRECTION : Nettoyer le montant des espaces avant validation // CORRECTION : Nettoyer le montant des espaces avant validation
@ -1071,6 +1150,11 @@ $(document).ready(function() {
$("#create_form_sortie .form-group").removeClass('has-error has-success'); $("#create_form_sortie .form-group").removeClass('has-error has-success');
$('#date_demande').val(today); $('#date_demande').val(today);
$('#date_fiche').val(today); $('#date_fiche').val(today);
// ✅ Réinitialiser les champs motif
$('#motif_select_dropdown').val('');
$('#motif_select_custom').hide().val('');
$('#motif_select').val('');
}); });
} else { } else {
@ -1133,7 +1217,7 @@ $(document).ready(function() {
}); });
// ============================================ // ============================================
// MODIFICATION AVEC SWEETALERT2 - CORRIGÉ // ✅ MODIFICATION AVEC SWEETALERT2 + GESTION MOTIF PERSONNALISÉ
// ============================================ // ============================================
window.editFunc = function(id) { window.editFunc = function(id) {
$.ajax({ $.ajax({
@ -1158,20 +1242,24 @@ $(document).ready(function() {
const initialModePaiement = response.mode_paiement || 'En espèce'; const initialModePaiement = response.mode_paiement || 'En espèce';
$("#mode_paiement_edit").val(initialModePaiement); $("#mode_paiement_edit").val(initialModePaiement);
// Gérer le motif // Gérer le motif - AVEC SUPPORT DES MOTIFS PERSONNALISÉS
const motif = response.motif; const motif = response.motif;
const motifSelect = $("#motif_select_edit"); const motifSelectDropdown = $("#motif_select_edit_dropdown");
const optionExists = motifSelect.find('option').filter(function() {
// Vérifier si le motif existe dans la liste
const optionExists = motifSelectDropdown.find('option').filter(function() {
return $(this).val().trim().toLowerCase() === motif.trim().toLowerCase(); return $(this).val().trim().toLowerCase() === motif.trim().toLowerCase();
}).length > 0; }).length > 0;
if (optionExists) { if (optionExists) {
motifSelect.val(motif); motifSelectDropdown.val(motif);
} else { } else {
motifSelect.val(""); // ✅ Si le motif n'existe pas, l'afficher quand même avec un indicateur
motifSelectDropdown.append('<option value="' + motif + '" selected>🔸 ' + motif + ' (personnalisé)</option>');
} }
$("#motif_select_edit_hidden").val(motif); // Mettre la valeur dans le champ caché
$("#motif_select_edit").val(motif);
if (response.preuve_achat) { if (response.preuve_achat) {
$("#current_preuve_text").html('<br><i class="fa fa-file"></i> Fichier actuel: ' + response.preuve_achat); $("#current_preuve_text").html('<br><i class="fa fa-file"></i> Fichier actuel: ' + response.preuve_achat);
@ -1400,6 +1488,10 @@ $(document).ready(function() {
}; };
}); });
// ============================================
// MARQUER COMME PAYÉ
// ============================================
window.markAsPaidFunc = function(id_sortie) { window.markAsPaidFunc = function(id_sortie) {
Swal.fire({ Swal.fire({
title: '💰 Confirmer le paiement', title: '💰 Confirmer le paiement',
@ -1508,3 +1600,92 @@ window.markAsPaidFunc = function(id_sortie) {
}); });
}; };
</script> </script>
<style>
/* Style pour les champs en lecture seule */
input[readonly], select[disabled], textarea[readonly] {
background-color: #e9ecef !important;
cursor: not-allowed !important;
color: #6c757d;
}
/* Style pour la légende */
.field-legend {
background-color: #f8f9fa;
border-left: 4px solid #007bff;
padding: 10px 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.field-legend i {
margin-right: 5px;
}
.field-legend .legend-item {
display: inline-block;
margin-right: 20px;
margin-top: 5px;
}
/* Effet hover sur les champs éditables */
.form-control:not([readonly]):not([disabled]):hover {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.15);
transition: all 0.2s ease-in-out;
}
/* Icône d'édition */
.fa-edit {
font-size: 12px;
}
/* ============================================ */
/* ✅ STYLES POUR LE CHAMP MOTIF MODIFIABLE */
/* ============================================ */
/* Animation pour l'apparition du champ personnalisé */
#motif_select_custom {
animation: slideDown 0.3s ease-in-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Style pour l'option "Autre" */
#motif_select_dropdown option[value="AUTRE"] {
background-color: #fff3cd;
font-weight: bold;
color: #856404;
}
/* Mettre en évidence le champ actif */
#motif_select_custom:focus {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
/* Style pour le champ personnalisé */
#motif_select_custom {
border-left: 4px solid #28a745;
}
/* Style pour le texte d'aide */
.motif-help-text {
font-size: 12px;
color: #6c757d;
margin-top: 5px;
display: block;
}
.motif-help-text i {
margin-right: 3px;
}
</style>
Loading…
Cancel
Save