diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 7126fd64..cb3cd98b 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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 diff --git a/app/Controllers/Dashboard.php b/app/Controllers/Dashboard.php index 8aa0d864..f499c8dd 100644 --- a/app/Controllers/Dashboard.php +++ b/app/Controllers/Dashboard.php @@ -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; } diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php index 262f2479..65fea499 100644 --- a/app/Controllers/OrderController.php +++ b/app/Controllers/OrderController.php @@ -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 = '
NIF : '.esc($company['NIF']).'
-STAT : '.esc($company['STAT']).'
-Contact : '.esc($company['phone']).' | '.esc($company['phone2']).'
-
- Facture N° '.esc($order['bill_no']).'
-DOIT Nom : '.esc($order['customer_name']).'
-Adresse : '.esc($order['customer_address']).'
-CIN : '.esc($order['customer_cin']).'
-Téléphone : '.esc($order['customer_phone'] ?? '').'
-Antananarivo, le '.$today.'
-| Produit | -Prix (Ar) | -||||
|---|---|---|---|---|---|
| Produit | +Prix (Ar) | +||||
|---|---|---|---|---|---|
| '.esc($details['product_name']);
+
+ if (!empty($details['commentaire'])) {
+ $html .= ' '.esc($details['commentaire']).''; + } + + $html .= ' |
+ '.number_format($prixAffiche, 0, '', ' ').' | +'.number_format($prixAffiche, 0, '', ' ').' | - '; - } - } else { - // ======================================== - // TABLE COMPLÈTE POUR AVANCE "SUR TERRE" OU COMMANDE NORMALE - // ======================================== - $html .= ' -
| MARQUE | -Désignation | -N° Moteur | -N° Châssis | -Puissance (CC) | -PRIX (Ar) | -
|---|
| MARQUE | +Désignation | +N° Moteur | +N° Châssis | +Puissance (CC) | +PRIX (Ar) | +
|---|---|---|---|---|---|
| '.esc($details['marque']).' | +'.esc($details['product_name']).' | +'.esc($details['numero_moteur']).' | +'.esc($details['numero_chassis']).' | +'.esc($details['puissance']).' | +'.number_format($prixAffiche, 0, '', ' ').' | +
| Prix (HT) : | +'.number_format($totalHT, 0, '', ' ').' Ar | +
| TVA (20%) : | +'.number_format($tva, 0, '', ' ').' Ar | +
| Total (TTC) : | +'.number_format($totalTTC, 0, '', ' ').' Ar | +
| Prix (HT) : | -'.number_format($totalHT, 0, '', ' ').' Ar | -
| TVA (20%) : | -'.number_format($tva, 0, '', ' ').' Ar | -
| Total (TTC) : | -'.number_format($totalTTC, 0, '', ' ').' Ar | -
-
+ ";
- // 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']);
diff --git a/app/Controllers/ReportController.php b/app/Controllers/ReportController.php
index 22836b87..f8540bae 100644
--- a/app/Controllers/ReportController.php
+++ b/app/Controllers/ReportController.php
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/app/Controllers/SortieCaisseController.php b/app/Controllers/SortieCaisseController.php
index 61ba799d..218979bf 100644
--- a/app/Controllers/SortieCaisseController.php
+++ b/app/Controllers/SortieCaisseController.php
@@ -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
" .
"Motif: " . $decaissement['motif'] . "
" .
- "Caissière: " . $users['firstname'] . ' ' . $users['lastname'];
+ "Caissière: " . $users['firstname'] . ' ' . $users['lastname'] . "
" .
+ "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 PAYÉ
' .
- 'Direction et DAF ont été notifiés.
' .
+ 'Direction et DAF de ' . $this->returnStoreName($users['store_id']) . ' ont été notifiés.
' .
'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
' .
'Mode de paiement: ' . $mode_paiement . '
' .
- '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
' .
+ 'Notification envoyée à la Direction de ' . $this->returnStoreName($user['store_id']) . ''
]);
+
} 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) . "
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([
diff --git a/app/Models/Orders.php b/app/Models/Orders.php
index 23689ba2..7bc3379f 100644
--- a/app/Models/Orders.php
+++ b/app/Models/Orders.php
@@ -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();
}
+
+}
\ No newline at end of file
diff --git a/app/Models/Recouvrement.php b/app/Models/Recouvrement.php
index beab52b1..b4a80d9c 100644
--- a/app/Models/Recouvrement.php
+++ b/app/Models/Recouvrement.php
@@ -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();
+}
}
\ No newline at end of file
diff --git a/app/Models/SortieCaisse.php b/app/Models/SortieCaisse.php
index 78e1f6e9..0a6006b3 100644
--- a/app/Models/SortieCaisse.php
+++ b/app/Models/SortieCaisse.php
@@ -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,
diff --git a/app/Views/dashboard.php b/app/Views/dashboard.php
index a505f5c0..0ffc7d78 100644
--- a/app/Views/dashboard.php
+++ b/app/Views/dashboard.php
@@ -712,14 +712,15 @@
-
+
+
+
-
- Ar
+ Ar
Totale CAISSE
@@ -728,11 +729,10 @@
-
- Ar
+ Ar
Totale MVOLA
@@ -741,11 +741,10 @@
-
- Ar
+ Ar
Totale en espèce
@@ -754,11 +753,10 @@
-
- Ar
+ Ar
Totale en banque
@@ -768,7 +766,7 @@
-
+
@@ -791,7 +789,7 @@
-
+
@@ -824,11 +822,12 @@
+
- Rapport de Performance du Caissier
+ 📊 Mes Performances de Vente
@@ -841,33 +840,53 @@
+
+
+ Liste de mes ventes validées
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
- MVOLA & Espèce
- Banque & MVOLA
- Banque & Espèce
- MVOLA & Banque
- Montant total
-
-
-
+
+
+
+
+
+ Caissier
+ Moto vendue
+ Date de vente
+ Prix de vente
+
+
+
+
+
+
+
+ Total :
+
+
+
+
@@ -878,88 +897,103 @@
-
-
-
-
-
-
+
-
-
+})();
+
+
@@ -1151,14 +1185,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+