01112025
This commit is contained in:
parent
25d8f834f5
commit
6a2fd61385
@ -197,12 +197,14 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
$routes->get('printDivBL/(:num)', [OrderController::class, 'print7']);
|
||||
$routes->get('printDivBLF/(:num)', [OrderController::class, 'print31']);
|
||||
$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('createFromEspace/(:num)', [OrderController::class, 'createById']);
|
||||
$routes->get('resrevation', [ReservationController::class, 'index']);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* route for the reports
|
||||
*/
|
||||
@ -215,7 +217,9 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
$routes->get('detail/fetctDataStock2/(:num)', [ReportController::class, 'fetchProductStock2']);
|
||||
$routes->get('detail/performance', [ReportController::class, 'performancedetail']);
|
||||
$routes->get('detail/fetchPerformances', [ReportController::class, 'fetchPerformances']);
|
||||
$routes->get('detail/fetchCaissierPerformances', [ReportController::class, 'fetchCaissierPerformances']);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* route for the company
|
||||
|
||||
@ -93,14 +93,14 @@ class Dashboard extends AdminController
|
||||
'total_virement_bancaire' => $total_virement_bancaire_final,
|
||||
|
||||
// === POUR CAISSIÈRE (MÊME CALCUL QUE DIRECTION) ===
|
||||
'total_caisse' => $total_final, // ← Identique à 'total'
|
||||
'total_mvola_caisse' => $total_mvola_final, // ← Identique à 'total_mvola'
|
||||
'total_espece_caisse' => $total_espece_final, // ← Identique à 'total_espece'
|
||||
'total_vb_caisse' => $total_virement_bancaire_final, // ← Identique à 'total_virement_bancaire'
|
||||
'total_caisse' => $total_final,
|
||||
'total_mvola_caisse' => $total_mvola_final,
|
||||
'total_espece_caisse' => $total_espece_final,
|
||||
'total_vb_caisse' => $total_virement_bancaire_final,
|
||||
|
||||
// === DÉTAIL POUR LA CAISSIÈRE ===
|
||||
'total_orders_only' => $total_orders, // Ventes complètes uniquement
|
||||
'total_avances' => $total_avances, // Avances uniquement
|
||||
'total_orders_only' => $total_orders,
|
||||
'total_avances' => $total_avances,
|
||||
|
||||
// ✅ Détails des sorties
|
||||
'total_sorties' => $total_sortie_global,
|
||||
@ -109,10 +109,10 @@ class Dashboard extends AdminController
|
||||
'total_sortie_virement' => $total_sortie_virement,
|
||||
|
||||
// ✅ Détails des recouvrements
|
||||
'recouvrement_me' => $me, // Mvola → Espèce
|
||||
'recouvrement_be' => $be, // Banque → Espèce
|
||||
'recouvrement_bm' => $bm, // Banque → Mvola
|
||||
'recouvrement_mb' => $mb, // Mvola → Banque
|
||||
'recouvrement_me' => $me,
|
||||
'recouvrement_be' => $be,
|
||||
'recouvrement_bm' => $bm,
|
||||
'recouvrement_mb' => $mb,
|
||||
'total_recouvrements' => $me + $be + $bm + $mb,
|
||||
|
||||
// Détail avances par mode de paiement
|
||||
@ -125,7 +125,7 @@ class Dashboard extends AdminController
|
||||
'total_espece_orders' => $es1_orders + $es2_orders,
|
||||
'total_vb_orders' => $vb1_orders + $vb2_orders,
|
||||
|
||||
// ✅ Montants bruts (avant recouvrements et sorties)
|
||||
// ✅ Montants bruts
|
||||
'total_brut' => $total_brut,
|
||||
'total_mvola_brut' => $total_mvola_brut,
|
||||
'total_espece_brut' => $total_espece_brut,
|
||||
@ -232,9 +232,14 @@ class Dashboard extends AdminController
|
||||
if ($user_id['group_name'] == "Cheffe d'Agence") {
|
||||
$data['isChef'] = true;
|
||||
}
|
||||
|
||||
// ✅ AJOUT POUR CAISSIER : Passer les données de performance
|
||||
if ($user_id['group_name'] == "Caissière") {
|
||||
$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") {
|
||||
$data['isMecanicien'] = true;
|
||||
}
|
||||
|
||||
@ -36,6 +36,31 @@ class OrderController extends AdminController
|
||||
return $this->render_template('orders/index', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un numéro de facture personnalisé selon le magasin
|
||||
* @param int $store_id
|
||||
* @return string
|
||||
*/
|
||||
private function generateBillNo(int $store_id): string
|
||||
{
|
||||
// Mapping des préfixes par magasin
|
||||
$storePrefixes = [
|
||||
1 => 'ANTS', // ANTSAKAVIRO
|
||||
2 => 'BESA', // BESARETY
|
||||
3 => 'BYPA', // BYPASS
|
||||
4 => 'TOAM', // TOAMASINA
|
||||
];
|
||||
|
||||
// Récupérer le préfixe du magasin, ou utiliser un préfixe par défaut
|
||||
$prefix = $storePrefixes[$store_id] ?? 'BILPR';
|
||||
|
||||
// Générer un identifiant unique
|
||||
$uniqueId = strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 6));
|
||||
|
||||
// Retourner le numéro de facture formaté
|
||||
return $prefix . '-' . $uniqueId;
|
||||
}
|
||||
|
||||
public function fetchOrdersData()
|
||||
{
|
||||
helper(['url', 'form']);
|
||||
@ -335,13 +360,15 @@ class OrderController extends AdminController
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$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
|
||||
$posts = $this->request->getPost('product[]');
|
||||
$rates = $this->request->getPost('rate_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;
|
||||
$gross_amount = $this->calculGross($amounts);
|
||||
|
||||
@ -352,14 +379,12 @@ class OrderController extends AdminController
|
||||
foreach ($posts as $index => $productId) {
|
||||
$productId = (int)$productId;
|
||||
|
||||
// Récupérer données produit + prix minimal
|
||||
$productData = $Products->getProductData($productId);
|
||||
$fourchette = $FourchettePrix->getFourchettePrixByProductId($productId);
|
||||
|
||||
if ($fourchette) {
|
||||
$prixMinimal = (float)$fourchette['prix_minimal'];
|
||||
|
||||
// ✅ Le rabais devient le prix de vente
|
||||
if ($discount < $prixMinimal) {
|
||||
$prixMinimalFormatted = number_format($prixMinimal, 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;
|
||||
|
||||
// Récupérer les tranches
|
||||
$tranche_1 = (float)$this->request->getPost('tranche_1') ?? 0;
|
||||
$tranche_2 = (float)$this->request->getPost('tranche_2') ?? 0;
|
||||
|
||||
// Calculer net_amount selon les tranches
|
||||
if ($tranche_1 > 0 && $tranche_2 > 0) {
|
||||
// Paiement en 2 tranches
|
||||
$net_amount = $tranche_1 + $tranche_2;
|
||||
} else {
|
||||
// Paiement en 1 tranche ou pas de tranches
|
||||
$net_amount = $montant_a_payer;
|
||||
}
|
||||
|
||||
// ✅ Création de la commande avec la nouvelle logique
|
||||
// ✅ Création de la commande
|
||||
$data = [
|
||||
'bill_no' => $bill_no,
|
||||
'customer_name' => $this->request->getPost('customer_name'),
|
||||
@ -408,7 +429,7 @@ class OrderController extends AdminController
|
||||
'amount_value' => $amounts,
|
||||
'gross_amount' => $gross_amount,
|
||||
'rate_value' => $rates,
|
||||
'puissance' => $puissances, // ✅ AJOUTER CETTE LIGNE
|
||||
'puissance' => $puissances,
|
||||
'store_id' => $users['store_id'],
|
||||
'tranche_1' => $tranche_1,
|
||||
'tranche_2' => $tranche_2,
|
||||
@ -423,9 +444,8 @@ class OrderController extends AdminController
|
||||
|
||||
$Notification = new NotificationController();
|
||||
|
||||
// ✅ WORKFLOW SELON LA REMISE
|
||||
if ($discount > 0) {
|
||||
// AVEC REMISE : Créer demande + Notifier Conseil
|
||||
// Logique demande de remise...
|
||||
$Order_item1 = new OrderItems();
|
||||
$order_item_data = $Order_item1->getOrdersItemData($order_id);
|
||||
$product_ids = array_column($order_item_data, 'product_id');
|
||||
@ -462,7 +482,6 @@ class OrderController extends AdminController
|
||||
$Remise = new Remise();
|
||||
$id_remise = $Remise->addDemande($data1);
|
||||
|
||||
// Notification au CONSEIL
|
||||
$Notification->createNotification(
|
||||
"Nouvelle demande de remise à valider - Commande " . $bill_no,
|
||||
"Direction",
|
||||
@ -471,7 +490,6 @@ class OrderController extends AdminController
|
||||
);
|
||||
|
||||
} else {
|
||||
// SANS REMISE : Notifier directement la Caissière
|
||||
$Notification->createNotification(
|
||||
"Nouvelle commande à valider - " . $bill_no,
|
||||
"Caissière",
|
||||
@ -480,7 +498,6 @@ class OrderController extends AdminController
|
||||
);
|
||||
}
|
||||
|
||||
// Redirection selon le rôle
|
||||
if ($users["group_name"] != "COMMERCIALE") {
|
||||
$this->checkProductisNull($posts, $users['store_id']);
|
||||
}
|
||||
@ -662,7 +679,7 @@ public function getTableProductRow()
|
||||
return $this->response->setJSON($product_data);
|
||||
}
|
||||
|
||||
public function update(int $id)
|
||||
public function update(int $id)
|
||||
{
|
||||
$this->verifyRole('updateOrder');
|
||||
|
||||
@ -714,6 +731,22 @@ public function getTableProductRow()
|
||||
$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');
|
||||
$original_discount = $this->request->getPost('original_discount');
|
||||
if ($discount === '' || $discount === null) {
|
||||
@ -737,11 +770,14 @@ public function getTableProductRow()
|
||||
'product_sold' => true,
|
||||
'rate_value' => $this->request->getPost('rate_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_2' => $role !== 'COMMERCIALE' ? $this->request->getPost('tranche_2') : null,
|
||||
'order_payment_mode' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_1') : null,
|
||||
'order_payment_mode_1' => $role !== 'COMMERCIALE' ? $this->request->getPost('order_payment_mode_2') : null
|
||||
'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)) {
|
||||
@ -760,6 +796,16 @@ public function getTableProductRow()
|
||||
(int)$user['store_id'],
|
||||
'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) {
|
||||
@ -1604,16 +1650,14 @@ public function print5(int $id)
|
||||
{
|
||||
$this->verifyRole('viewOrder');
|
||||
|
||||
if (! $id) {
|
||||
if (!$id) {
|
||||
throw new \CodeIgniter\Exceptions\PageNotFoundException();
|
||||
}
|
||||
|
||||
// Modèles
|
||||
$Orders = new Orders();
|
||||
$Company = new Company();
|
||||
$OrderItems = new OrderItems();
|
||||
|
||||
// Récupération des données
|
||||
$order = $Orders->getOrdersData($id);
|
||||
$items = $OrderItems->getOrdersItemData($id);
|
||||
$company = $Company->getCompanyData(1);
|
||||
@ -1628,7 +1672,6 @@ public function print5(int $id)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculs
|
||||
$discount = (float) $order['discount'];
|
||||
$grossAmount = (float) $order['gross_amount'];
|
||||
$totalTTC = ($discount > 0) ? $discount : $grossAmount;
|
||||
@ -1638,164 +1681,344 @@ public function print5(int $id)
|
||||
|
||||
$paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé';
|
||||
|
||||
// Début du HTML
|
||||
$html = '<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Facture '.$order['bill_no'].'</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; font-size:14px; color:#000;margin:0; padding:0; }
|
||||
.header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }
|
||||
.header .infos { line-height:1.4; }
|
||||
.header img { max-height:80px; }
|
||||
.client { margin-bottom:20px; }
|
||||
table { width:100%; border-collapse:collapse; margin-bottom:20px; }
|
||||
th, td { border:1px solid #000; padding:6px; }
|
||||
th { background:#f0f0f0; }
|
||||
.right { text-align:right; }
|
||||
.signature { display:flex; justify-content:space-between; margin-top:50px; }
|
||||
.signature div { text-align:center; }
|
||||
.conditions { page-break-before: always; padding:20px; line-height:1.5; }
|
||||
/* ✅ FORMAT A4 PAYSAGE DIVISÉ EN 2 */
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ✅ CONTENEUR : 2 COLONNES CÔTE À CÔTE */
|
||||
.page {
|
||||
display: flex;
|
||||
width: 297mm;
|
||||
height: 210mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ✅ CHAQUE FACTURE = 50% DE LA LARGEUR */
|
||||
.facture-box {
|
||||
flex: 1;
|
||||
width: 148.5mm;
|
||||
padding: 10mm;
|
||||
box-sizing: border-box;
|
||||
border-right: 2px dashed #999;
|
||||
}
|
||||
|
||||
.facture-box:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.header .infos {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.header .infos h2 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.header .infos p {
|
||||
margin: 2px 0;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.header img {
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
.header p.facture-num {
|
||||
margin: 5px 0;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.client {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.client p {
|
||||
margin: 3px 0;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 15px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #000;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.words-box {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.words-box strong {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.signature {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.signature div {
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
/* ✅ CONDITIONS SUR PAGE SÉPARÉE (VERSO) */
|
||||
.conditions-page {
|
||||
page-break-before: always;
|
||||
display: flex;
|
||||
width: 297mm;
|
||||
height: 210mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.conditions-box {
|
||||
flex: 1;
|
||||
width: 148.5mm;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
border-right: 2px dashed #999;
|
||||
}
|
||||
|
||||
.conditions-box:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.conditions-box h3 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.conditions-box ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.conditions-box li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.conditions-box .buyer-signature {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.conditions-box img {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
* {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="window.print()">
|
||||
|
||||
<div class="header">
|
||||
<div class="infos">
|
||||
<h2 style="margin:0;">'.esc($company['company_name']).'</h2>
|
||||
<p style="margin:2px 0;"><strong>NIF :</strong> '.esc($company['NIF']).'</p>
|
||||
<p style="margin:2px 0;"><strong>STAT :</strong> '.esc($company['STAT']).'</p>
|
||||
<p style="margin:2px 0;"><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
|
||||
<p style="margin:5px 0; font-weight:bold;">Facture N° '.esc($order['bill_no']).'</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ✅ PAGE 1 : RECTO - 2 FACTURES CÔTE À CÔTE -->
|
||||
<div class="page">';
|
||||
|
||||
<div class="client">
|
||||
<p><strong>DOIT Nom :</strong> '.esc($order['customer_name']).'</p>
|
||||
<p><strong>Adresse :</strong> '.esc($order['customer_address']).'</p>
|
||||
<p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p>
|
||||
<p><strong>Téléphone :</strong> '.esc($order['customer_phone'] ?? '').'</p>
|
||||
<p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p>
|
||||
</div>';
|
||||
|
||||
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
|
||||
if ($isAvanceMere) {
|
||||
// ========================================
|
||||
// TABLE SIMPLIFIÉE POUR AVANCE "SUR MER"
|
||||
// ========================================
|
||||
// ✅ GÉNÉRER 2 FACTURES IDENTIQUES
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$html .= '
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Produit</th>
|
||||
<th class="right">Prix (Ar)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
<div class="facture-box">
|
||||
<div class="header">
|
||||
<div class="infos">
|
||||
<h2>'.esc($company['company_name']).'</h2>
|
||||
<p><strong>NIF :</strong> '.esc($company['NIF']).'</p>
|
||||
<p><strong>STAT :</strong> '.esc($company['STAT']).'</p>
|
||||
<p><strong>Contact :</strong> '.esc($company['phone']).' | '.esc($company['phone2']).'</p>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
|
||||
<p class="facture-num">Facture N° '.esc($order['bill_no']).'</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
foreach ($items as $it) {
|
||||
$details = $this->getOrderItemDetails($it);
|
||||
|
||||
if (!$details) continue;
|
||||
|
||||
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
|
||||
|
||||
|
||||
// Afficher le commentaire s'il existe
|
||||
if (!empty($details['commentaire'])) {
|
||||
$html .= '<br><em style="font-size:12px; color:#666;">'.esc($details['commentaire']).'</em>';
|
||||
}
|
||||
|
||||
$html .= '</td>
|
||||
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
|
||||
</tr>';
|
||||
}
|
||||
<div class="client">
|
||||
<p><strong>DOIT Nom :</strong> '.esc($order['customer_name']).'</p>
|
||||
<p><strong>Adresse :</strong> '.esc($order['customer_address']).'</p>
|
||||
<p><strong>CIN :</strong> '.esc($order['customer_cin']).'</p>
|
||||
<p><strong>Téléphone :</strong> '.esc($order['customer_phone'] ?? '').'</p>
|
||||
<p style="text-align:right;"><em>Antananarivo, le '.$today.'</em></p>
|
||||
</div>';
|
||||
|
||||
} else {
|
||||
// ========================================
|
||||
// TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE
|
||||
// ========================================
|
||||
$html .= '
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>MARQUE</th>
|
||||
<th>Désignation</th>
|
||||
<th>N° Moteur</th>
|
||||
<th>N° Châssis</th>
|
||||
<th>Puissance (CC)</th>
|
||||
<th class="right">PRIX (Ar)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
foreach ($items as $it) {
|
||||
$details = $this->getOrderItemDetails($it);
|
||||
|
||||
if (!$details) continue;
|
||||
|
||||
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
|
||||
|
||||
|
||||
// ✅ TABLEAU ADAPTÉ SELON LE TYPE
|
||||
if ($isAvanceMere) {
|
||||
$html .= '
|
||||
<tr>
|
||||
<td>'.esc($details['marque']).'</td>
|
||||
<td>'.esc($details['product_name']).'</td>
|
||||
<td>'.esc($details['numero_moteur']).'</td>
|
||||
<td>'.esc($details['numero_chassis']).'</td>
|
||||
<td>'.esc($details['puissance']).'</td> <!-- ✅ ICI -->
|
||||
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
|
||||
</tr>';
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Produit</th>
|
||||
<th class="right">Prix (Ar)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
foreach ($items as $it) {
|
||||
$details = $this->getOrderItemDetails($it);
|
||||
|
||||
if (!$details) continue;
|
||||
|
||||
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
|
||||
|
||||
$html .= '<tr><td>'.esc($details['product_name']);
|
||||
|
||||
if (!empty($details['commentaire'])) {
|
||||
$html .= '<br><em style="font-size:8px; color:#666;">'.esc($details['commentaire']).'</em>';
|
||||
}
|
||||
|
||||
$html .= '</td>
|
||||
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Fermer le tableau pour avance
|
||||
$html .= '
|
||||
</tbody>
|
||||
</table>';
|
||||
|
||||
} else {
|
||||
$html .= '
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>MARQUE</th>
|
||||
<th>Désignation</th>
|
||||
<th>N° Moteur</th>
|
||||
<th>N° Châssis</th>
|
||||
<th>Puissance (CC)</th>
|
||||
<th class="right">PRIX (Ar)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>';
|
||||
|
||||
foreach ($items as $it) {
|
||||
$details = $this->getOrderItemDetails($it);
|
||||
|
||||
if (!$details) continue;
|
||||
|
||||
$prixAffiche = ($discount > 0) ? $discount : $details['prix'];
|
||||
|
||||
$html .= '
|
||||
<tr>
|
||||
<td>'.esc($details['marque']).'</td>
|
||||
<td>'.esc($details['product_name']).'</td>
|
||||
<td>'.esc($details['numero_moteur']).'</td>
|
||||
<td>'.esc($details['numero_chassis']).'</td>
|
||||
<td>'.esc($details['puissance']).'</td>
|
||||
<td class="right">'.number_format($prixAffiche, 0, '', ' ').'</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
// ✅ Fermer le tableau pour produit normal
|
||||
$html .= '
|
||||
</tbody>
|
||||
</table>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Prix (HT) :</strong></td>
|
||||
<td class="right">'.number_format($totalHT, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>TVA (20%) :</strong></td>
|
||||
<td class="right">'.number_format($tva, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Total (TTC) :</strong></td>
|
||||
<td class="right">'.number_format($totalTTC, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="words-box">
|
||||
<strong>Arrêté à la somme de :</strong><br>
|
||||
'.$inWords.'
|
||||
</div>
|
||||
|
||||
<div class="signature">
|
||||
<div>L\'Acheteur<br><br>__________________</div>
|
||||
<div>Le Vendeur<br><br>__________________</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Prix (HT) :</strong></td>
|
||||
<td class="right">'.number_format($totalHT, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>TVA (20%) :</strong></td>
|
||||
<td class="right">'.number_format($tva, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Total (TTC) :</strong></td>
|
||||
<td class="right">'.number_format($totalTTC, 0, '', ' ').' Ar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="border:1px solid #000; padding:10px; margin-bottom:30px;">
|
||||
<strong>Arrêté à la somme de :</strong><br>
|
||||
'.$inWords.'
|
||||
</div>
|
||||
|
||||
<div class="signature">
|
||||
<div>L\'Acheteur<br><br>__________________</div>
|
||||
<div>Le Vendeur<br><br>__________________</div>
|
||||
</div>
|
||||
<!-- ✅ PAGE 2 : VERSO - 2 CONDITIONS GÉNÉRALES CÔTE À CÔTE -->
|
||||
<div class="conditions-page">';
|
||||
|
||||
<!-- Conditions Générales avec saut de page -->
|
||||
<div class="conditions">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3 style="margin:0;">Conditions Générales</h3>
|
||||
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo" style="height:60px;">
|
||||
</div>
|
||||
<ul>
|
||||
<li>Aucun accessoire (casque, rétroviseur, batterie, etc.) n\'est inclus avec la moto. Si le client en a besoin, il doit les acheter séparément.</li>
|
||||
<li>Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.</li>
|
||||
<li>Aucun service après-vente n\'est fourni.</li>
|
||||
<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</li>
|
||||
<li>La facture étant un document provisoire ne peut se substituer au certificat modèle (si requis) délivré au client au moment de l\'achat. Il appartient à ce dernier de procéder à l\'immatriculation dans le délai prévu par la loi.</li>
|
||||
</ul>
|
||||
<div style="text-align:center; margin-top:50px;">L\'Acheteur</div>
|
||||
// ✅ GÉNÉRER 2 CONDITIONS IDENTIQUES
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$html .= '
|
||||
<div class="conditions-box">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<h3>Conditions Générales</h3>
|
||||
<img src="'.base_url('assets/images/company_logo.jpg').'" alt="Logo">
|
||||
</div>
|
||||
<ul>
|
||||
<li>Aucun accessoire (casque, rétroviseur, batterie, etc.) n\'est inclus avec la moto. Si le client en a besoin, il doit les acheter séparément.</li>
|
||||
<li>Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.</li>
|
||||
<li>Aucun service après-vente n\'est fourni.</li>
|
||||
<li>La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.</li>
|
||||
<li>La facture étant un document provisoire ne peut se substituer au certificat modèle (si requis) délivré au client au moment de l\'achat. Il appartient à ce dernier de procéder à l\'immatriculation dans le délai prévu par la loi.</li>
|
||||
</ul>
|
||||
<div class="buyer-signature">L\'Acheteur</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
@ -219,84 +219,58 @@ class RecouvrementController extends AdminController
|
||||
public function createRecouvrement()
|
||||
{
|
||||
$this->verifyRole('createRecouvrement');
|
||||
|
||||
|
||||
$data['page_title'] = $this->pageTitle;
|
||||
|
||||
// echo "<pre>";
|
||||
// die(var_dump($this->request->getPost()));
|
||||
|
||||
|
||||
// Load validation service
|
||||
$validation = \Config\Services::validation();
|
||||
|
||||
|
||||
$validation->setRules([
|
||||
'send_mode' => 'required',
|
||||
'get_mode' => 'required',
|
||||
'recouvrement_montant' => 'required',
|
||||
'recouvrement_date' => 'required',
|
||||
]);
|
||||
|
||||
|
||||
$validationData = [
|
||||
'send_mode' => $this->request->getPost('send_mode'),
|
||||
'get_mode' => $this->request->getPost('get_mode'),
|
||||
'recouvrement_montant' => $this->request->getPost('recouvrement_montant'),
|
||||
'recouvrement_date' => $this->request->getPost('recouvrement_date'),
|
||||
];
|
||||
|
||||
// Set validation rules
|
||||
|
||||
|
||||
$Notification = new NotificationController();
|
||||
$Recouvrement = new Recouvrement();
|
||||
// $recouvrement_id = $this->request->getPost('recouvrement_id');
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
if ($users && isset($users['firstname'], $users['lastname'])) {
|
||||
$fullname = $users['firstname'] . ' ' . $users['lastname'];
|
||||
}
|
||||
|
||||
// $orders = new Orders();
|
||||
// $Recouvrement = new Recouvrement();
|
||||
// $paymentData = $orders->getPaymentModes();
|
||||
// $totalRecouvrement = $Recouvrement->getTotalRecouvrements();
|
||||
// $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/');
|
||||
// return redirect()->to('recouvrement/');
|
||||
// } else {
|
||||
// session()->setFlashdata('errors', 'Error occurred while creating the product');
|
||||
// return redirect()->to('recouvrement/');
|
||||
// }
|
||||
// } else {
|
||||
// session()->setFlashdata('errors', 'Solde MVOLA insuffisant');
|
||||
// return redirect()->to('recouvrement/');
|
||||
// }
|
||||
|
||||
|
||||
if ($validation->run($validationData)) {
|
||||
// // Prepare data
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$send_mode = $this->request->getPost('send_mode');
|
||||
$get_mode = $this->request->getPost('get_mode');
|
||||
$amount = (float) $this->request->getPost('recouvrement_montant');
|
||||
|
||||
// Vérifier si le recouvrement est possible
|
||||
if (!$this->canMakeRecouvrement($send_mode, $get_mode, $amount)) {
|
||||
$response['success'] = false;
|
||||
$response['messages'] = 'Recouvrement impossible : solde insuffisant pour ce type de transaction';
|
||||
return $this->response->setJSON($response);
|
||||
}
|
||||
|
||||
// Préparer les données
|
||||
$data = [
|
||||
'recouvrement_montant' => $this->request->getPost('recouvrement_montant'),
|
||||
'recouvrement_montant' => $amount,
|
||||
'recouvrement_date' => $this->request->getPost('recouvrement_date'),
|
||||
'recouvrement_personnel' => $fullname,
|
||||
'get_money' => $this->request->getPost('get_mode'),
|
||||
'send_money' => $this->request->getPost('send_mode'),
|
||||
'get_money' => $get_mode,
|
||||
'send_money' => $send_mode,
|
||||
'user_id' => $users['id'],
|
||||
'store_id' => $users['store_id'],
|
||||
|
||||
];
|
||||
|
||||
|
||||
if ($Recouvrement->addRecouvrement($data)) {
|
||||
$Notification->createNotification("Un nouveau recouvrement a été crée", "Direction", (int)$users["store_id"], 'recouvrement');
|
||||
$response['success'] = true;
|
||||
@ -310,12 +284,11 @@ class RecouvrementController extends AdminController
|
||||
$response['success'] = false;
|
||||
$response['messages'] = $validation->getErrors();
|
||||
}
|
||||
|
||||
|
||||
return $this->response->setJSON($response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function updateRecouvrement($recouvrement_id)
|
||||
{
|
||||
$this->verifyRole('updateRecouvrement');
|
||||
@ -367,6 +340,73 @@ class RecouvrementController extends AdminController
|
||||
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() {
|
||||
helper(['url', 'form']);
|
||||
|
||||
@ -305,7 +305,26 @@ class ReportController extends AdminController
|
||||
}
|
||||
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
|
||||
if ($users['group_name'] === "COMMERCIALE") {
|
||||
$orderPaid = $Orders->getPerformanceByOrders2();
|
||||
@ -334,4 +353,42 @@ class ReportController extends AdminController
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@ -371,28 +371,29 @@ class SortieCaisseController extends AdminController
|
||||
$result = $SortieCaisse->updateSortieCaisse($id_sortie, $data);
|
||||
|
||||
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')) {
|
||||
$Notification = new NotificationController();
|
||||
|
||||
$montant = number_format($decaissement['montant_retire'], 0, ',', ' ');
|
||||
$message = "💰 Décaissement payé - " . $montant . " Ar<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(
|
||||
$message,
|
||||
"Direction",
|
||||
(int)$users['store_id'],
|
||||
(int)$users['store_id'], // ✅ Store ID de la caissière
|
||||
'sortieCaisse'
|
||||
);
|
||||
|
||||
// Notifier le DAF
|
||||
// ✅ Notifier le DAF DU MÊME STORE
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"DAF",
|
||||
(int)$users['store_id'],
|
||||
(int)$users['store_id'], // ✅ Store ID de la caissière
|
||||
'sortieCaisse'
|
||||
);
|
||||
}
|
||||
@ -400,9 +401,10 @@ class SortieCaisseController extends AdminController
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'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'
|
||||
]);
|
||||
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
@ -719,7 +721,7 @@ class SortieCaisseController extends AdminController
|
||||
}
|
||||
|
||||
// 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['date_fiche'] = $this->request->getPost('date_fiche') ?? null;
|
||||
$data['service_demandeur'] = $this->request->getPost('service_demandeur') ?? '';
|
||||
@ -765,13 +767,23 @@ class SortieCaisseController extends AdminController
|
||||
$result = $model->addSortieCaisse($data);
|
||||
|
||||
if ($result) {
|
||||
// Notification
|
||||
// Notification UNIQUEMENT pour la Direction du même store
|
||||
if (class_exists('App\Controllers\NotificationController')) {
|
||||
$Notification = new NotificationController();
|
||||
|
||||
// ✅ Notifier UNIQUEMENT la Direction du store concerné
|
||||
$Notification->createNotification(
|
||||
"Nouvelle demande de décaissement de " . number_format($montant_retire, 0, ',', ' ') . " Ar (" . $mode_paiement . ")",
|
||||
"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'
|
||||
);
|
||||
}
|
||||
@ -780,8 +792,10 @@ class SortieCaisseController extends AdminController
|
||||
'success' => true,
|
||||
'messages' => 'Décaissement de ' . number_format($montant_retire, 0, ',', ' ') . ' Ar créé avec succès<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 {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
@ -1076,24 +1090,28 @@ class SortieCaisseController extends AdminController
|
||||
$statut = $this->request->getPost('statut');
|
||||
$message = '';
|
||||
|
||||
// ✅ Récupérer le décaissement pour avoir son store_id
|
||||
$decaissement = $SortieCaisse->getSortieCaisseSingle($id_sortie);
|
||||
$store_id = $decaissement['store_id'];
|
||||
|
||||
switch ($statut) {
|
||||
case "Valider":
|
||||
$message = "Décaissement validé avec succès";
|
||||
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse');
|
||||
$message = "✅ Votre décaissement a été validé par la Direction de " . $this->returnStoreName($store_id);
|
||||
$Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
case "Refuser":
|
||||
$message = "Un décaissement a été refusé";
|
||||
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse');
|
||||
$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)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
case "En attente":
|
||||
$message = "Décaissement mis en attente";
|
||||
$Notification->createNotification($message, "Caissière", (int)$users["store_id"], 'sortieCaisse');
|
||||
$message = "⏳ Votre décaissement a été mis en attente par la Direction de " . $this->returnStoreName($store_id);
|
||||
$Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'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 {
|
||||
return $this->response->setJSON([
|
||||
|
||||
@ -16,26 +16,28 @@ class Orders extends Model
|
||||
protected $table = 'orders';
|
||||
protected $allowedFields = [
|
||||
'bill_no',
|
||||
'customer_name',
|
||||
'customer_name',
|
||||
'customer_address',
|
||||
'customer_phone',
|
||||
'customer_cin',
|
||||
'date_time',
|
||||
'gross_amount',
|
||||
'service_charge_rate',
|
||||
'vat_charge_rate',
|
||||
'vat_charge_rate',
|
||||
'vat_charge',
|
||||
'net_amount',
|
||||
'discount',
|
||||
'paid_status',
|
||||
'user_id',
|
||||
'validated_by',
|
||||
'validated_at',
|
||||
'store_id',
|
||||
'tranche_1',
|
||||
'tranche_2',
|
||||
'order_payment_mode',
|
||||
'order_payment_mode_1',
|
||||
'delivered_at',
|
||||
'delivered_by'
|
||||
'delivered_by'
|
||||
];
|
||||
|
||||
|
||||
@ -825,4 +827,44 @@ public function getUserPerformanceByMonth(string $month)
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -186,4 +186,29 @@ class Recouvrement extends Model{
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -62,84 +62,116 @@ protected $allowedFields = [
|
||||
'date_visa_conseil',
|
||||
'observations'
|
||||
];
|
||||
public function getAllSortieCaisse()
|
||||
{
|
||||
try {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
if ($users['group_name'] === 'Direction') {
|
||||
return $this
|
||||
->select('*')
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
if ($users['group_name'] === 'Conseil') {
|
||||
return $this
|
||||
->select('*')
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
if($users["group_name"]==="Caissière"){
|
||||
return $this
|
||||
public function getAllSortieCaisse()
|
||||
{
|
||||
try {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
// ✅ DIRECTION : Voir uniquement les décaissements de SON store
|
||||
if ($users['group_name'] === 'Direction') {
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ DAF : Voir uniquement les décaissements de SON store
|
||||
if ($users['group_name'] === 'DAF') {
|
||||
return $this
|
||||
->select('*')
|
||||
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->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"){
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors de la récupération des sorties caisse : ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
|
||||
// ✅ AUTRES : Par défaut, voir uniquement leurs décaissements
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors de la récupération des sorties caisse : ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllSortieCaisse1()
|
||||
{
|
||||
try {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
if ($users['group_name'] === 'Direction') {
|
||||
return $this
|
||||
->select('*')
|
||||
->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
|
||||
->where('user_group.group_id', 7)
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if($users["group_name"]==="Caissière"){
|
||||
return $this
|
||||
public function getAllSortieCaisse1()
|
||||
{
|
||||
try {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
// ✅ DIRECTION : Voir uniquement les décaissements de SON store
|
||||
if ($users['group_name'] === 'Direction') {
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
|
||||
->where('user_group.group_id', 7)
|
||||
->where('sortie_caisse.store_id', $users['store_id']) // ✅ FILTRE PAR STORE
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ DAF : Voir uniquement les décaissements de SON store
|
||||
if ($users['group_name'] === 'DAF') {
|
||||
return $this
|
||||
->select('*')
|
||||
->join('user_group', 'user_group.user_id = sortie_caisse.user_id')
|
||||
->where('user_group.group_id', 7)
|
||||
->where('sortie_caisse.store_id', $users['store_id']) // ✅ FILTRE PAR STORE
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->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"){
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors de la récupération des sorties caisse : ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors de la récupération des sorties caisse : ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
public function addSortieCaisse(array $data) {
|
||||
try {
|
||||
return $this->insert($data);
|
||||
@ -174,7 +206,12 @@ protected $allowedFields = [
|
||||
public function getTotalSortieCaisse() {
|
||||
$session = session();
|
||||
$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) {
|
||||
try {
|
||||
@ -184,6 +221,7 @@ protected $allowedFields = [
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
|
||||
SUM(montant_retire) AS mr
|
||||
')
|
||||
->where('store_id', $users['store_id']) // ✅ FILTRE PAR STORE
|
||||
->whereIn('statut', ['Valider', 'Payé'])
|
||||
->get()
|
||||
->getRowObject();
|
||||
@ -196,7 +234,29 @@ protected $allowedFields = [
|
||||
'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 {
|
||||
// ✅ CAISSIÈRE : Uniquement son store
|
||||
try {
|
||||
return $this->select('
|
||||
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
|
||||
|
||||
@ -712,14 +712,15 @@
|
||||
</section>
|
||||
|
||||
|
||||
<?php if ($isCaissier === true): ?>
|
||||
<!-- ✅ SECTION CAISSIER - SIMPLIFIÉ SANS GRAPHIQUE -->
|
||||
<?php if ($isCaissier === true): ?>
|
||||
|
||||
<!-- Section des totaux caisse -->
|
||||
<div class="container-fluid row">
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_caisse au lieu de total -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<h2><?php echo number_format($total_caisse, 0, '.', ' '); ?> Ar</h2>
|
||||
<p>Totale CAISSE</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
@ -728,11 +729,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_mvola_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<h2><?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?> Ar</h2>
|
||||
<p>Totale MVOLA</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
@ -741,11 +741,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_espece_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_espece_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<h2><?php echo number_format($total_espece_caisse, 0, '.', ' '); ?> Ar</h2>
|
||||
<p>Totale en espèce</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
@ -754,11 +753,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_vb_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_vb_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<h2><?php echo number_format($total_vb_caisse, 0, '.', ' '); ?> Ar</h2>
|
||||
<p>Totale en banque</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
@ -768,7 +766,7 @@
|
||||
</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="col-lg-6 col-xs-12">
|
||||
<div class="info-box bg-aqua">
|
||||
@ -791,7 +789,7 @@
|
||||
</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="col-lg-12">
|
||||
<div class="box box-info">
|
||||
@ -824,11 +822,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Rapport de Performance -->
|
||||
<section class="content-header">
|
||||
<h1>Rapport de Performance du Caissier</h1>
|
||||
<h1>📊 Mes Performances de Vente</h1>
|
||||
<ol class="breadcrumb">
|
||||
<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>
|
||||
</section>
|
||||
|
||||
@ -841,33 +840,53 @@
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<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">
|
||||
<!-- Filtres -->
|
||||
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDate" class="form-control">
|
||||
<label for="startDateCaissier" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDateCaissier" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDate" class="form-control">
|
||||
<label for="endDateCaissier" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDateCaissier" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer 🔍</button>
|
||||
<button id="ExportBTN1" class="btn btn-success w-100">Exporter 📤</button>
|
||||
<button id="filteredBtnCaissier" class="btn btn-primary w-100">
|
||||
<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>
|
||||
<table id="caissierperf" class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="me">MVOLA & Espèce</th>
|
||||
<th id="bm">Banque & MVOLA</th>
|
||||
<th id="be">Banque & Espèce</th>
|
||||
<th id="mb">MVOLA & Banque</th>
|
||||
<th id="mr">Montant total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
<!-- 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>
|
||||
<tr>
|
||||
<th>Caissier</th>
|
||||
<th>Moto vendue</th>
|
||||
<th>Date de vente</th>
|
||||
<th>Prix de vente</th>
|
||||
</tr>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -878,88 +897,103 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="width: 80%; margin: auto;">
|
||||
<canvas id="salesChart"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script>
|
||||
var manageTable;
|
||||
<!-- ✅ SCRIPT POUR LE TABLEAU CAISSIER UNIQUEMENT -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var caissierTable;
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('🔍 Initialisation du tableau caissier...');
|
||||
|
||||
|
||||
// Initialize the datatable
|
||||
manageTable = $('#caissierperf').DataTable({
|
||||
'ajax': 'recouvrement/fetchTotalRecouvrementData',
|
||||
'order': [],
|
||||
'pageLength': 5,
|
||||
'lengthMenu': [
|
||||
[5, 10, 25, 50, -1],
|
||||
[5, 10, 25, 50, "All"]
|
||||
// Configuration DataTable pour caissier
|
||||
caissierTable = $('#caissierperf').DataTable({
|
||||
'ajax': {
|
||||
'url': '<?= base_url('reports/detail/fetchCaissierPerformances') ?>',
|
||||
'data': function(d) {
|
||||
// ✅ AJOUT : Envoyer les dates au serveur
|
||||
d.startDate = $('#startDateCaissier').val();
|
||||
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": [{
|
||||
"targets": "_all",
|
||||
"className": "text-left"
|
||||
}],
|
||||
|
||||
"footerCallback": function (row, data, start, end, display) {
|
||||
'order': [[2, 'desc']],
|
||||
'pageLength': 10,
|
||||
'lengthMenu': [[5, 10, 25, 50, -1], [5, 10, 25, 50, "Tout"]],
|
||||
'language': {
|
||||
'processing': "Traitement en cours...",
|
||||
'search': "Rechercher :",
|
||||
'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();
|
||||
|
||||
// Helper function to parse string to float
|
||||
|
||||
const parseNumber = function (i) {
|
||||
return typeof i === 'string' ?
|
||||
parseFloat(i.replace(/[^\d.-]/g, '')) : // remove currency symbols, commas
|
||||
typeof i === 'number' ?
|
||||
i : 0;
|
||||
return typeof i === 'string'
|
||||
? parseFloat(i.replace(/[^\d.-]/g, ''))
|
||||
: typeof i === 'number' ? i : 0;
|
||||
};
|
||||
|
||||
const totalPrixVente = api
|
||||
.column(3, { page: 'current' })
|
||||
.data()
|
||||
.reduce((a, b) => parseNumber(a) + parseNumber(b), 0);
|
||||
|
||||
const totalFormate = totalPrixVente.toLocaleString('fr-FR');
|
||||
|
||||
$(api.column(3).footer()).html('<strong>' + totalFormate + ' Ar</strong>');
|
||||
}
|
||||
});
|
||||
|
||||
$('#filteredB1').on('click', function () {
|
||||
const startDate = $('#startDate').val();
|
||||
const endDate = $('#endDate').val();
|
||||
const pvente = $('#pvente').val(); // id de l'utilisateur ici
|
||||
|
||||
$.ajax({
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('ExportBTN1').addEventListener('click', function () {
|
||||
// Select your table
|
||||
var table = document.getElementById('caissierperf');
|
||||
|
||||
// Convert it to a workbook
|
||||
var wb = XLSX.utils.table_to_book(table, {
|
||||
sheet: "Feuille1"
|
||||
// ✅ CORRECTION : Filtrage par dates
|
||||
$('#filteredBtnCaissier').on('click', function () {
|
||||
const startDate = $('#startDateCaissier').val();
|
||||
const endDate = $('#endDateCaissier').val();
|
||||
|
||||
console.log('🔍 Filtrage:', startDate, endDate);
|
||||
|
||||
// Recharger les données avec les nouveaux paramètres
|
||||
caissierTable.ajax.reload();
|
||||
});
|
||||
|
||||
// Export it
|
||||
XLSX.writeFile(wb, 'export-caissier-performance.xlsx');
|
||||
// Export Excel
|
||||
$('#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>
|
||||
<!-- </div> -->
|
||||
<?php endif; ?>
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- securite -->
|
||||
|
||||
@ -1151,14 +1185,14 @@
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="startDateCaissier" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDateCaissier" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDateCaissier" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDateCaissier" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer
|
||||
|
||||
@ -327,22 +327,38 @@
|
||||
$("#mainOrdersNav").addClass('active');
|
||||
$("#manageOrdersNav").addClass('active');
|
||||
|
||||
// ✅ CORRECTION : Gestion des tranches de paiement
|
||||
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);
|
||||
|
||||
function addPaymentTranche(paymentTranche) {
|
||||
if (parseInt(paymentTranche) === 2) {
|
||||
$("#paid_status_1").show();
|
||||
$("#paid_status_2").show();
|
||||
$("#montant_reference").show(); // ✅ Afficher le montant de référence
|
||||
|
||||
var amount1 = parseFloat($('#payment_amount_1').val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
var montantTotal = getMontantTotal();
|
||||
var amount2 = montantTotal - amount1;
|
||||
$('#payment_amount_2').val(amount2.toFixed(2));
|
||||
} else {
|
||||
$("#paid_status_1").show();
|
||||
$("#paid_status_2").hide();
|
||||
$("#montant_reference").hide(); // ✅ Cacher le montant de référence
|
||||
$('#payment_mode_2').val('');
|
||||
|
||||
// ✅ Remplir tranche 1 avec le montant total
|
||||
var montantTotal = getMontantTotal();
|
||||
$('#payment_amount_1').val(montantTotal.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,59 +367,66 @@
|
||||
updateMontantTranches();
|
||||
});
|
||||
|
||||
// ✅ CORRECTION : Recalculer tranche 2 quand tranche 1 change
|
||||
$('#payment_amount_1').on("input", function() {
|
||||
var amount1 = parseFloat($(this).val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
var montantTotal = getMontantTotal();
|
||||
var amount2 = montantTotal - amount1;
|
||||
|
||||
if (amount2 < 0) amount2 = 0;
|
||||
|
||||
$('#payment_amount_2').val(amount2.toFixed(2));
|
||||
});
|
||||
|
||||
addPaymentTranche(paymentTranche);
|
||||
|
||||
$("#add_row").unbind('click').bind('click', function() {
|
||||
var table = $("#product_info_table");
|
||||
var count_table_tbody_tr = $("#product_info_table tbody tr").length;
|
||||
var row_id = count_table_tbody_tr + 1;
|
||||
var table = $("#product_info_table");
|
||||
var count_table_tbody_tr = $("#product_info_table tbody tr").length;
|
||||
var row_id = count_table_tbody_tr + 1;
|
||||
|
||||
$.ajax({
|
||||
$.ajax({
|
||||
url: base_url + '/orders/getTableProductRow/',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
var html = '<tr id="row_' + row_id + '">' +
|
||||
'<td>' +
|
||||
'<select class="form-control select_group product" data-row-id="' + row_id + '" id="product_' + row_id + '" name="product[]" style="width:100%;" onchange="getProductData(' + row_id + ')">' +
|
||||
'<option value=""></option>';
|
||||
|
||||
$.each(response, function(index, value) {
|
||||
var displayText = value.sku + ' | ' + value.name;
|
||||
if (value.numero_de_moteur) {
|
||||
displayText += ' | ' + value.numero_de_moteur;
|
||||
}
|
||||
// ✅ Ne plus afficher la puissance
|
||||
html += '<option value="' + value.id + '">' + displayText + '</option>';
|
||||
});
|
||||
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
// ✅ Colonne puissance visible et modifiable
|
||||
'<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 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 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>' +
|
||||
'</tr>';
|
||||
|
||||
if (count_table_tbody_tr >= 1) {
|
||||
$("#product_info_table tbody tr:last").after(html);
|
||||
} else {
|
||||
$("#product_info_table tbody").html(html);
|
||||
var html = '<tr id="row_' + row_id + '">' +
|
||||
'<td>' +
|
||||
'<select class="form-control select_group product" data-row-id="' + row_id + '" id="product_' + row_id + '" name="product[]" style="width:100%;" onchange="getProductData(' + row_id + ')">' +
|
||||
'<option value=""></option>';
|
||||
|
||||
$.each(response, function(index, value) {
|
||||
var displayText = value.sku + ' | ' + value.name;
|
||||
if (value.numero_de_moteur) {
|
||||
displayText += ' | ' + value.numero_de_moteur;
|
||||
}
|
||||
if (value.puissance) {
|
||||
displayText += ' | ' + value.puissance;
|
||||
}
|
||||
html += '<option value="' + value.id + '">' + displayText + '</option>';
|
||||
});
|
||||
|
||||
$(".product").select2();
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
'<td><input type="text" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="Puissance" autocomplete="off"></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="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><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
|
||||
'</tr>';
|
||||
|
||||
if (count_table_tbody_tr >= 1) {
|
||||
$("#product_info_table tbody tr:last").after(html);
|
||||
} else {
|
||||
$("#product_info_table tbody").html(html);
|
||||
}
|
||||
|
||||
$(".product").select2();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function getTotal(row = null) {
|
||||
if (row) {
|
||||
@ -419,52 +442,33 @@
|
||||
|
||||
function getProductData(row_id) {
|
||||
var product_id = $("#product_" + row_id).val();
|
||||
|
||||
if (product_id == "") {
|
||||
$("#rate_" + row_id).val("");
|
||||
$("#rate_value_" + row_id).val("");
|
||||
$("#min_price_" + row_id).val("");
|
||||
$("#puissance_" + row_id).val("1"); // ✅ Réinitialiser à 1
|
||||
$("#amount_" + row_id).val("");
|
||||
$("#amount_value_" + row_id).val("");
|
||||
$("#rate_" + row_id).val("");
|
||||
$("#rate_value_" + row_id).val("");
|
||||
$("#puissance_" + row_id).val("");
|
||||
$("#amount_" + row_id).val("");
|
||||
$("#amount_value_" + row_id).val("");
|
||||
} else {
|
||||
$.ajax({
|
||||
url: base_url + 'orders/getProductValueById',
|
||||
type: 'post',
|
||||
data: { product_id: product_id },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
console.log('✅ Response:', response); // Debug
|
||||
|
||||
var prixVente = parseFloat(response.prix_vente) || 0;
|
||||
var prixMinimal = parseFloat(response.prix_minimal) || 0;
|
||||
|
||||
if (prixVente < 0) prixVente = 0;
|
||||
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);
|
||||
$("#amount_" + row_id).val(total);
|
||||
$("#amount_value_" + row_id).val(total);
|
||||
|
||||
subAmount();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('❌ Erreur AJAX:', error);
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
url: base_url + 'orders/getProductValueById',
|
||||
type: 'post',
|
||||
data: { product_id: product_id },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$("#rate_" + row_id).val(response.prix_vente);
|
||||
$("#rate_value_" + row_id).val(response.prix_vente);
|
||||
|
||||
$("#puissance_" + row_id).val(response.puissance || '');
|
||||
|
||||
var total = Number(response.prix_vente);
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row_id).val(total);
|
||||
$("#amount_value_" + row_id).val(total);
|
||||
subAmount();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function subAmount() {
|
||||
var service_charge = <?php echo ($company_data['service_charge_value'] > 0) ? $company_data['service_charge_value'] : 0; ?>;
|
||||
@ -515,6 +519,7 @@
|
||||
$("#remaining_value").val(remaning.toFixed(2));
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Mettre à jour les tranches après chaque calcul
|
||||
updateMontantTranches();
|
||||
}
|
||||
|
||||
@ -533,12 +538,14 @@
|
||||
subAmount();
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Fonction pour obtenir le montant pour les tranches
|
||||
function getMontantPourTranches() {
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
var grossAmount = parseFloat($("#gross_amount_value").val()) || 0;
|
||||
return discount > 0 ? discount : grossAmount;
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Mettre à jour l'affichage du montant de référence
|
||||
function updateMontantTranches() {
|
||||
var montant = getMontantPourTranches();
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
@ -552,11 +559,17 @@
|
||||
$("#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();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Calculer tranche 2 basé sur le montant total actuel
|
||||
function calculerTranche2() {
|
||||
var montantTotal = getMontantPourTranches();
|
||||
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
|
||||
@ -565,12 +578,17 @@
|
||||
$("#payment_amount_2").val(tranche2.toFixed(2));
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : Écouter les changements de discount
|
||||
$("#discount").on('keyup', function() {
|
||||
updateMontantTranches();
|
||||
subAmount();
|
||||
});
|
||||
|
||||
// ✅ Initialisation au chargement
|
||||
const net_amount_value = document.getElementById('net_amount_value');
|
||||
const net_amount = document.getElementById('net_amount');
|
||||
const payment_amount_1 = document.getElementById('payment_amount_1');
|
||||
payment_amount_1.value = net_amount.value;
|
||||
|
||||
if (payment_amount_1 && net_amount) {
|
||||
payment_amount_1.value = net_amount.value;
|
||||
}
|
||||
</script>
|
||||
@ -106,7 +106,7 @@
|
||||
<div class="small-box" style="background-color:#A9A9A9;">
|
||||
<div class="inner">
|
||||
<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 class="icon">
|
||||
<i class="ion ion-cash"></i>
|
||||
@ -232,78 +232,171 @@
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
|
||||
<!-- Inclure SweetAlert2 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#motos").select2();
|
||||
$("#motos_edit").select2();
|
||||
$('.mecanic').select2();
|
||||
$("#recouvrement").addClass('active');
|
||||
$(document).ready(function() {
|
||||
$("#motos").select2();
|
||||
$("#motos_edit").select2();
|
||||
$('.mecanic').select2();
|
||||
$("#recouvrement").addClass('active');
|
||||
|
||||
// Check if the URL contains the _ parameter
|
||||
if (window.location.search.startsWith("?_=")) {
|
||||
window.location.href = window.location.origin + window.location.pathname;
|
||||
}
|
||||
// datatable-fr.js
|
||||
$.extend(true, $.fn.dataTable.defaults, {
|
||||
// Check if the URL contains the _ parameter
|
||||
if (window.location.search.startsWith("?_=")) {
|
||||
window.location.href = window.location.origin + window.location.pathname;
|
||||
}
|
||||
|
||||
// Configuration DataTable en français
|
||||
$.extend(true, $.fn.dataTable.defaults, {
|
||||
language: {
|
||||
sProcessing: "Traitement en cours...",
|
||||
sSearch: "Rechercher :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Affichage de l'élement 0 à 0 sur 0 élément",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun élément à afficher",
|
||||
sEmptyTable: "Aucune donnée disponible dans le tableau",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
},
|
||||
oAria: {
|
||||
sSortAscending: ": activer pour trier la colonne par ordre croissant",
|
||||
sSortDescending: ": activer pour trier la colonne par ordre décroissant"
|
||||
}
|
||||
sProcessing: "Traitement en cours...",
|
||||
sSearch: "Rechercher :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Affichage de l'élement 0 à 0 sur 0 élément",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun élément à afficher",
|
||||
sEmptyTable: "Aucune donnée disponible dans le tableau",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
},
|
||||
oAria: {
|
||||
sSortAscending: ": activer pour trier la colonne par ordre croissant",
|
||||
sSortDescending: ": activer pour trier la colonne par ordre décroissant"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialisation du DataTable
|
||||
manageTable = $('#manageTable').DataTable({
|
||||
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',
|
||||
'order': [],
|
||||
'columnDefs': [{
|
||||
targets: 1,
|
||||
className: 'text-right rowmontant'
|
||||
},
|
||||
{
|
||||
targets: 4,
|
||||
className: 'rowtype'
|
||||
},
|
||||
{
|
||||
targets: 5,
|
||||
className: 'rowdestination'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
manageTable = $('#manageTable').DataTable({
|
||||
'ajax': '<?= base_url('recouvrement/fetchRecouvrementData') ?>',
|
||||
'order': [],
|
||||
'columnDefs': [{
|
||||
targets: 1,
|
||||
className: 'text-right rowmontant'
|
||||
},
|
||||
{
|
||||
targets: 4,
|
||||
className: 'rowtype'
|
||||
},
|
||||
{
|
||||
targets: 5,
|
||||
className: 'rowdestination'
|
||||
}
|
||||
]
|
||||
});
|
||||
// ====== FONCTION DE RAFRAÎCHISSEMENT AUTOMATIQUE ======
|
||||
function refreshData() {
|
||||
// Recharger le DataTable
|
||||
manageTable.ajax.reload(null, false);
|
||||
|
||||
})
|
||||
// Recharger les totaux
|
||||
fetchTotalData();
|
||||
}
|
||||
|
||||
$("#create_form").unbind('submit').on('submit', function() {
|
||||
// Rafraîchir automatiquement toutes les 5 secondes
|
||||
setInterval(refreshData, 5000);
|
||||
|
||||
})
|
||||
|
||||
// ====== CRÉATION DE RECOUVREMENT ======
|
||||
$("#create_form").unbind('submit').on('submit', function() {
|
||||
var form = $(this);
|
||||
|
||||
// Supprimer les messages d'erreur
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success === true) {
|
||||
// Recharger immédiatement les données
|
||||
manageTable.ajax.reload(null, false);
|
||||
fetchTotalData();
|
||||
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
// Fermer le modal
|
||||
$("#createModal").modal('hide');
|
||||
|
||||
// Réinitialiser le formulaire
|
||||
$("#create_form")[0].reset();
|
||||
$("#create_form .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 {
|
||||
// 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) {
|
||||
var id = $("#" + index);
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
id.after(value);
|
||||
});
|
||||
} else {
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</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">×</span></button>' +
|
||||
'<strong>Erreur!</strong> Une erreur est survenue lors de la création.' +
|
||||
'</div>');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
// ====== SUPPRESSION DE RECOUVREMENT ======
|
||||
function removeFunc(id) {
|
||||
if (id) {
|
||||
$("#removeForm").unbind('submit').on('submit', function() {
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(), // /converting the form data into array and sending it to server
|
||||
data: {
|
||||
recouvrement_id: id
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
// Recharger immédiatement les données
|
||||
manageTable.ajax.reload(null, false);
|
||||
fetchTotalData();
|
||||
|
||||
if (response.success === true) {
|
||||
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
|
||||
@ -311,170 +404,157 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
$("#removeModal").modal('hide');
|
||||
|
||||
// hide the modal
|
||||
$("#createModal").modal('hide');
|
||||
|
||||
// reset the form
|
||||
$("#create_form")[0].reset();
|
||||
$("#create_form .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 {
|
||||
|
||||
if (response.messages instanceof Object) {
|
||||
$.each(response.messages, function(index, value) {
|
||||
var id = $("#" + index);
|
||||
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
|
||||
id.after(value);
|
||||
|
||||
});
|
||||
} else {
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
}
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</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">×</span></button>' +
|
||||
'<strong>Erreur!</strong> Une erreur est survenue lors de la suppression.' +
|
||||
'</div>');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeFunc(id) {
|
||||
if (id) {
|
||||
$("#removeForm").on('submit', function() {
|
||||
// ====== MODIFICATION DE RECOUVREMENT ======
|
||||
function editFunc(id) {
|
||||
$.ajax({
|
||||
url: '<?= base_url('recouvrement/fetchRecouvrementSingle') ?>/' + id,
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$("#recouvrement_montant_edit").val(response.recouvrement_montant).change();
|
||||
$("#recouvrement_date_edit").val(response.recouvrement_date).change();
|
||||
|
||||
// Soumettre le formulaire de modification
|
||||
$("#update_form").unbind('submit').bind('submit', function() {
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
url: form.attr('action').replace(/\/?$/, '/') + id,
|
||||
type: form.attr('method'),
|
||||
data: {
|
||||
recouvrement_id: id
|
||||
},
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
// Recharger immédiatement les données
|
||||
manageTable.ajax.reload(null, false);
|
||||
|
||||
|
||||
fetchTotalData();
|
||||
|
||||
$("#updateModal").modal('hide');
|
||||
|
||||
if (response.success === true) {
|
||||
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
// hide the modal
|
||||
$("#removeModal").modal('hide');
|
||||
$("#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 {
|
||||
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
if (response.messages instanceof Object) {
|
||||
$.each(response.messages, function(index, value) {
|
||||
var id = $("#" + index);
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
id.after(value);
|
||||
});
|
||||
} else {
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</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">×</span></button>' +
|
||||
'<strong>Erreur!</strong> Une erreur est survenue lors de la modification.' +
|
||||
'</div>');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// update function
|
||||
function editFunc(id) {
|
||||
$.ajax({
|
||||
url: '<?= base_url('recouvrement/fetchRecouvrementSingle') ?>/' + id,
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$("#recouvrement_montant_edit").val(response.recouvrement_montant).change();
|
||||
$("#recouvrement_date_edit").val(response.recouvrement_date).change();
|
||||
// submit the edit from
|
||||
$("#update_form").unbind('submit').bind('submit', function() {
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action').replace(/\/?$/, '/') + id,
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(), // /converting the form data into array and sending it to server
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
manageTable.ajax.reload(null, false);
|
||||
$("#updateModal").modal('hide');
|
||||
if (response.success === true) {
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
|
||||
// reset the form
|
||||
$("#updateForm .form-group").removeClass('has-error').removeClass('has-success');
|
||||
|
||||
} else {
|
||||
$("#updateModal").modal('hide');
|
||||
if (response.messages instanceof Object) {
|
||||
$.each(response.messages, function(index, value) {
|
||||
var id = $("#" + index);
|
||||
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
|
||||
id.after(value);
|
||||
|
||||
});
|
||||
} else {
|
||||
$("#updateModal").modal('hide');
|
||||
$("#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">×</span></button>' +
|
||||
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
// ====== MISE À JOUR DES TOTAUX ======
|
||||
const endpoint = '<?= base_url('recouvrement/fetchTotalData') ?>';
|
||||
|
||||
async function fetchTotalData() {
|
||||
try {
|
||||
let res = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
let data = await res.json();
|
||||
|
||||
// Mettre à jour les totaux avec animation
|
||||
updateValueWithAnimation('#total', data.total);
|
||||
updateValueWithAnimation('#total_mvola', data.total_mvola);
|
||||
updateValueWithAnimation('#total_espece', data.total_espece);
|
||||
updateValueWithAnimation('#total_banque', data.total_virement_bancaire);
|
||||
|
||||
} catch (e) {
|
||||
console.error('fetchTotalData error', e);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
const endpoint = '<?= base_url('recouvrement/fetchTotalData') ?>';
|
||||
async function fetchTotalData() {
|
||||
try {
|
||||
let res = await fetch(endpoint, { headers:{'Content-Type':'application/json'} });
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
let data = await res.json();
|
||||
$('#total').text(formatNum(data.total));
|
||||
$('#total_mvola').text(formatNum(data.total_mvola));
|
||||
$('#total_caisse').text(formatNum(data.total_espece));
|
||||
$('#total_banque').text(formatNum(data.total_virement_bancaire));
|
||||
} catch(e) {
|
||||
console.error('fetchTotalData error', e);
|
||||
}
|
||||
}
|
||||
function formatNum(n){
|
||||
n = parseFloat(n)||0; return n.toLocaleString('fr-FR').replace(/\s/g,' ');
|
||||
}
|
||||
fetchTotalData();
|
||||
setInterval(fetchTotalData, 1000);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
n = parseFloat(n) || 0;
|
||||
return n.toLocaleString('fr-FR').replace(/\s/g, ' ');
|
||||
}
|
||||
|
||||
// Charger les totaux au démarrage
|
||||
fetchTotalData();
|
||||
|
||||
// Rafraîchir les totaux toutes les 3 secondes
|
||||
setInterval(fetchTotalData, 3000);
|
||||
</script>
|
||||
|
||||
@ -288,22 +288,44 @@ input[readonly], select[disabled], textarea[readonly] {
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="motif_select" class="form-label">Motif</label>
|
||||
<select class="form-control" id="motif_select" name="motif_select" required>
|
||||
<?php
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
|
||||
$isCaissier = $users['group_name'] == "Caissière";
|
||||
$options = $isAdmin ? $admin_options : $caissier_options;
|
||||
foreach ($options as $option) {
|
||||
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
|
||||
}
|
||||
?>
|
||||
<option value="" selected>Veuillez sélectionner une raison</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="motif_select" class="form-label">Motif</label>
|
||||
|
||||
<!-- Select avec liste + option "Autre" -->
|
||||
<select class="form-control" id="motif_select_dropdown" name="motif_select_dropdown">
|
||||
<?php
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
|
||||
$isCaissier = $users['group_name'] == "Caissière";
|
||||
$options = $isAdmin ? $admin_options : $caissier_options;
|
||||
|
||||
echo "<option value=''>Veuillez sélectionner une raison</option>\n";
|
||||
|
||||
foreach ($options as $option) {
|
||||
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
|
||||
}
|
||||
?>
|
||||
<option value="AUTRE">✏️ Autre (saisir manuellement)</option>
|
||||
</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 class="col-md-6 mb-3">
|
||||
<label for="mode_paiement" class="form-label">Mode de paiement</label>
|
||||
<select class="form-control" id="mode_paiement" name="mode_paiement" required>
|
||||
@ -573,24 +595,36 @@ input[readonly], select[disabled], textarea[readonly] {
|
||||
|
||||
<!-- Motif et Mode de paiement -->
|
||||
<div class="row">
|
||||
<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>
|
||||
<select class="form-control" id="motif_select_edit" name="motif_select_edit" required disabled style="background-color: #e9ecef; cursor: not-allowed;">
|
||||
<?php
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
|
||||
$options = $isAdmin ? $admin_options : $caissier_options;
|
||||
foreach ($options as $option) {
|
||||
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
|
||||
}
|
||||
?>
|
||||
<option value="" selected>Veuillez sélectionner une raison</option>
|
||||
</select>
|
||||
<!-- Champ caché pour envoyer la valeur du motif -->
|
||||
<input type="hidden" id="motif_select_edit_hidden" name="motif_select_edit">
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Select désactivé pour l'affichage -->
|
||||
<select class="form-control" id="motif_select_edit_dropdown" disabled style="background-color: #e9ecef; cursor: not-allowed;">
|
||||
<?php
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = $users['group_name'] == "Direction" || $users['group_name'] == "Conseil";
|
||||
$options = $isAdmin ? $admin_options : $caissier_options;
|
||||
|
||||
echo "<option value=''>Veuillez sélectionner une raison</option>\n";
|
||||
|
||||
foreach ($options as $option) {
|
||||
echo "<option value=\"" . htmlspecialchars($option) . "\">" . htmlspecialchars($option) . "</option>\n";
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
||||
<!-- 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 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>
|
||||
<select class="form-control" id="mode_paiement_edit" name="mode_paiement_edit" required>
|
||||
<option value="En espèce">En espèce</option>
|
||||
@ -745,6 +779,7 @@ input[readonly], select[disabled], textarea[readonly] {
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
// ============================================
|
||||
// CONVERSION NOMBRE EN LETTRES (FRANÇAIS)
|
||||
@ -891,6 +926,31 @@ $(document).ready(function() {
|
||||
$('#date_demande').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
|
||||
// ============================================
|
||||
@ -911,8 +971,8 @@ $(document).ready(function() {
|
||||
const words = numberToFrenchWords(num);
|
||||
montantLettreInput.val(words + ' ariary');
|
||||
|
||||
// Gestion du basculement de formulaire
|
||||
if (num > 1000000) {
|
||||
// ✅ Gestion du basculement de formulaire - CORRIGÉ >= 1000000
|
||||
if (num >= 1000000) {
|
||||
$('#nom_demandeur').val($('#nom').val());
|
||||
$('#fonction_demandeur').val($('#fonction').val());
|
||||
$('#montant_estime').val(this.value);
|
||||
@ -930,7 +990,7 @@ $(document).ready(function() {
|
||||
$('#back-to-im1').on('click', function() {
|
||||
$('#form-im23').hide();
|
||||
$('#form-im1').show();
|
||||
montantInput.val('500000');
|
||||
montantInput.val('999999');
|
||||
montantInput.trigger('input');
|
||||
});
|
||||
|
||||
@ -944,7 +1004,7 @@ $(document).ready(function() {
|
||||
montantInput.trigger('input');
|
||||
});
|
||||
|
||||
// Réinitialisation du modal
|
||||
// ✅ Réinitialisation du modal - AVEC RÉINITIALISATION DU MOTIF
|
||||
$('#createModal').on('hidden.bs.modal', function () {
|
||||
$('#form-im1').show();
|
||||
$('#form-im23').hide();
|
||||
@ -952,6 +1012,11 @@ $(document).ready(function() {
|
||||
$('#date_demande').val(today);
|
||||
$('#date_fiche').val(today);
|
||||
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) {
|
||||
e.preventDefault();
|
||||
|
||||
$(".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);
|
||||
|
||||
// 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');
|
||||
$('#date_demande').val(today);
|
||||
$('#date_fiche').val(today);
|
||||
|
||||
// ✅ Réinitialiser les champs motif
|
||||
$('#motif_select_dropdown').val('');
|
||||
$('#motif_select_custom').hide().val('');
|
||||
$('#motif_select').val('');
|
||||
});
|
||||
|
||||
} else {
|
||||
@ -1133,7 +1217,7 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// MODIFICATION AVEC SWEETALERT2 - CORRIGÉ
|
||||
// ✅ MODIFICATION AVEC SWEETALERT2 + GESTION MOTIF PERSONNALISÉ
|
||||
// ============================================
|
||||
window.editFunc = function(id) {
|
||||
$.ajax({
|
||||
@ -1158,20 +1242,24 @@ $(document).ready(function() {
|
||||
const initialModePaiement = response.mode_paiement || 'En espèce';
|
||||
$("#mode_paiement_edit").val(initialModePaiement);
|
||||
|
||||
// Gérer le motif
|
||||
// ✅ Gérer le motif - AVEC SUPPORT DES MOTIFS PERSONNALISÉS
|
||||
const motif = response.motif;
|
||||
const motifSelect = $("#motif_select_edit");
|
||||
const optionExists = motifSelect.find('option').filter(function() {
|
||||
const motifSelectDropdown = $("#motif_select_edit_dropdown");
|
||||
|
||||
// Vérifier si le motif existe dans la liste
|
||||
const optionExists = motifSelectDropdown.find('option').filter(function() {
|
||||
return $(this).val().trim().toLowerCase() === motif.trim().toLowerCase();
|
||||
}).length > 0;
|
||||
|
||||
if (optionExists) {
|
||||
motifSelect.val(motif);
|
||||
motifSelectDropdown.val(motif);
|
||||
} 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) {
|
||||
$("#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) {
|
||||
Swal.fire({
|
||||
title: '💰 Confirmer le paiement',
|
||||
@ -1507,4 +1599,93 @@ 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…
Reference in New Issue
Block a user