[ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Achat équipement de sécurité" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Achat mobilier de bureau" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Paiement salaire des collaborateurs" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Cotisation sociales" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Remboursement d'avance moto" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Payement prime ou endemnité" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Paiement sous-traitant" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Frais de formation" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Paiement loyer" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Frais de formation externe" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Abonnement internet" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Entretien locaux" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Paiement fournisseur" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Remboursement de frais" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Paiement assurance" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Réparation immobilisation" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "DVD" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Déclaration fiscale - Déclaration d'impôts" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], "Enregistrement des contrats de bail au centre fiscal" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'SuperAdmin' ], // ----- Raisons Admin ----- "Achat de matériel informatique" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Achat équipement de sécurité" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Achat mobilier de bureau" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Paiement salaire des collaborateurs" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Cotisation sociales" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Remboursement d'avance moto" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Payement prime ou endemnité" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Paiement sous-traitant" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Frais de formation" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Paiement loyer" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Frais de formation externe" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Abonnement internet" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Entretien locaux" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Paiement fournisseur" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Remboursement de frais" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Paiement assurance" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Réparation immobilisation" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "DVD" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Déclaration fiscale - Déclaration d'impôts" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], "Enregistrement des contrats de bail au centre fiscal" => [ 'source_fond' => 'Budget Directionnel', 'initiateur_demande' => 'Direction' ], // ----- Raisons Caissier ----- "Achat materiel - Réparation immobilisation" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Réparation matériel" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Maintenance équipement" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achats de Matériaux et Fournitures" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achat produits de nettoyage" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achat consommable informatique" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achat petit outillage" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Avance à un prestataire" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Payement prestataire" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Payement éléctricité" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Frais de mission - Déplacement" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achat de carburant" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Paiement transport marchandise" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], "Achat pièces pour réparation moto" => [ 'source_fond' => 'Caisse Courante', 'initiateur_demande' => 'Caissière' ], ]; private $pageTitle = 'Décaissement'; public function index() { $this->verifyRole('viewSortieCaisse'); $admin_options = [ "Achat de matériel informatique", "Achat équipement de sécurité", "Achat mobilier de bureau", "Paiement salaire des collaborateurs", "Cotisation sociales", "Remboursement d'avance moto", "Payement prime ou endemnité", "Paiement sous-traitant", "Frais de formation", "Paiement loyer", "Frais de formation externe", "Abonnement internet", "Entretien locaux", "Paiement fournisseur", "Remboursement de frais", "Paiement assurance", "Réparation immobilisation", "DVD", "Déclaration fiscale - Déclaration d'impôts", "Enregistrement des contrats de bail au centre fiscal" ]; $caissier_options = [ "Achat materiel - Réparation immobilisation", "Réparation matériel", "Maintenance équipement", "Achats de Matériaux et Fournitures", "Achat produits de nettoyage", "Achat consommable informatique", "Achat petit outillage", "Avance à un prestataire", "Payement prestataire", "Payement éléctricité", "Frais de mission - Déplacement", "Achat de carburant", "Paiement transport marchandise", "Achat pièces pour réparation moto" ]; $Stores = new Stores(); $stor = $Stores->getActiveStore(); $data = json_decode($this->fetchTotal(),true); $data['admin_options'] = $admin_options; $data['caissier_options'] = $caissier_options; $data['stores'] = $stor; $this->render_template('sortieCaisse/index', $data); } // Create an AJAX endpoint to access the fetchTotal() function public function fetchTotal(){ $data = [ 'user_permission' => $this->permission, 'page_title' => $this->pageTitle ]; return json_encode($data); } public function fetchSortieCaisseData() { helper(['url', 'form']); $SortieCaisse = new SortieCaisse(); // Initialiser les variables pour DataTables $draw = intval($this->request->getVar('draw')); $data = $SortieCaisse->getAllSortieCaisse(); $totalRecords = count($data); $session = session(); $users = $session->get('user'); // ✅ Vérifier si l'utilisateur est DAF ou Direction UNIQUEMENT $canManage = in_array($users['group_name'], ['Direction', 'DAF','SuperAdmin']); $isCaissiere = $users['group_name'] === 'Caissière'; $result = [ "draw" => $draw, "recordsTotal" => $totalRecords, "recordsFiltered" => $totalRecords, "data" => [] ]; foreach ($data as $key => $value) { $buttons = ''; // ✅ BOUTON MODIFICATION : Seulement pour DAF et Direction if (in_array('updateSortieCaisse', $this->permission) && $canManage) { $buttons .= ''; } // ✅ BOUTON VALIDATION : Seulement pour DAF et Direction if (in_array('validateSortieCaisse', $this->permission) && $canManage) { $buttons .= ''; } // ✅ NOUVEAU : BOUTON "MARQUER COMME PAYÉ" pour Caissière if ($isCaissiere && $value['statut'] === 'Valider') { $buttons .= ''; } // Afficher un badge si déjà payé if ($value['statut'] === 'Payé') { $buttons .= ' Payé'; } // ✅ CORRECTION : Enlever 'admin_raison' pour les caissières if($users["group_name"] === "Caissière"){ $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['statut'], $value['admin_raison'], $buttons // Les caissières ne verront PAS les boutons (car $canManage = false) ]; } elseif ($users["group_name"] === "Direction" || $users["group_name"] === "DAF" ||$users["group_name"] === "SuperAdmin" ) { $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['source_fond'], $value['initiateur_demande'], $this->returnStoreName($value['store_id']), $value['commentaire'], $value['statut'], $buttons // DAF et Direction verront les boutons ]; } elseif ($users["group_name"] === "Conseil") { // ✅ Conseil peut voir les données mais SANS boutons d'action $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['source_fond'], $value['initiateur_demande'], $this->returnStoreName($value['store_id']), $value['commentaire'], $value['statut'], '' // Pas de boutons pour Conseil ]; } } return $this->response->setJSON($result); } /** * ✅ NOUVELLE MÉTHODE : Marquer un décaissement comme payé * Accessible uniquement par les caissières */ /** * ✅ CORRECTION COMPLÈTE : Marquer un décaissement comme payé * Accessible uniquement par les caissières */ public function markAsPaid($id_sortie) { // Vérifier que l'utilisateur est une caissière $session = session(); $users = $session->get('user'); if ($users['group_name'] !== 'Caissière') { return $this->response->setJSON([ 'success' => false, 'messages' => 'Accès refusé. Seules les caissières peuvent marquer un décaissement comme payé.' ]); } try { $SortieCaisse = new SortieCaisse(); // Récupérer le décaissement $decaissement = $SortieCaisse->getSortieCaisseSingle($id_sortie); if (!$decaissement) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Décaissement introuvable.' ]); } // Vérifier que le statut est "Valider" if ($decaissement['statut'] !== 'Valider') { return $this->response->setJSON([ 'success' => false, 'messages' => 'Ce décaissement ne peut pas être marqué comme payé.
Statut actuel : ' . $decaissement['statut'] ]); } // Mettre à jour le statut $data = [ 'statut' => 'Payé', 'date_paiement_effectif' => date('Y-m-d H:i:s') ]; $result = $SortieCaisse->updateSortieCaisse($id_sortie, $data); if ($result) { // ✅ Créer une notification pour TOUS les DAF, Direction et SuperAdmin try { 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'] . "
" . "Store: " . $this->returnStoreName($decaissement['store_id']); // ✅ Récupérer TOUS les stores $Stores = new Stores(); $allStores = $Stores->getActiveStore(); // ✅ Notifier Direction, DAF et SuperAdmin de TOUS les stores if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { // Notifier Direction $Notification->createNotification( $message, "Direction", (int)$store['id'], 'sortieCaisse' ); // Notifier DAF $Notification->createNotification( $message, "DAF", (int)$store['id'], 'sortieCaisse' ); // Notifier SuperAdmin $Notification->createNotification( $message, "SuperAdmin", (int)$store['id'], 'sortieCaisse' ); } } } } catch (\Exception $e) { // Logger l'erreur mais continuer log_message('error', 'Erreur notification markAsPaid: ' . $e->getMessage()); } return $this->response->setJSON([ 'success' => true, 'messages' => '✅ Décaissement marqué comme PAYÉ
' . 'Tous les Direction, DAF et SuperAdmin ont été notifiés.
' . 'Montant: ' . number_format($decaissement['montant_retire'], 0, ',', ' ') . ' Ar' ]); } else { return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de la mise à jour du statut.' ]); } } catch (\Exception $e) { log_message('error', 'Erreur markAsPaid: ' . $e->getMessage() . ' | Ligne: ' . $e->getLine()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur serveur: ' . $e->getMessage() ]); } } public function fetchSortieCaisseData1() { helper(['url', 'form']); $SortieCaisse = new SortieCaisse(); // Initialiser les variables pour DataTables $draw = intval($this->request->getVar('draw')); $data = $SortieCaisse->getAllSortieCaisse1(); $totalRecords = count($data); $session = session(); $users = $session->get('user'); // ✅ Vérifier si l'utilisateur est DAF ou Direction UNIQUEMENT $canManage = in_array($users['group_name'], ['Direction', 'DAF','SuperAdmin']); $isCaissiere = $users['group_name'] === 'Caissière'; $result = [ "draw" => $draw, "recordsTotal" => $totalRecords, "recordsFiltered" => $totalRecords, "data" => [] ]; foreach ($data as $key => $value) { $buttons = ''; // ✅ BOUTON MODIFICATION : Seulement pour DAF et Direction if (in_array('updateSortieCaisse', $this->permission) && $canManage) { $buttons .= ''; } // ✅ BOUTON VALIDATION : Seulement pour DAF et Direction if (in_array('validateSortieCaisse', $this->permission) && $canManage) { $buttons .= ''; } // ✅ NOUVEAU : BOUTON "MARQUER COMME PAYÉ" pour Caissière if ($isCaissiere && $value['statut'] === 'Valider') { $buttons .= ''; } // Afficher un badge si déjà payé if ($value['statut'] === 'Payé') { $buttons .= ' Payé'; } if($users["group_name"] === "Caissière"){ $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['statut'], $value['admin_raison'], $buttons // Les caissières ne verront PAS les boutons ]; } elseif ($users["group_name"] === "Direction" || $users["group_name"] === "DAF" || $users["group_name"] === "SuperAdmin") { $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['source_fond'], $value['initiateur_demande'], $this->returnStoreName($value['store_id']), $value['commentaire'], $value['statut'], $buttons // DAF et Direction verront les boutons ]; } elseif ($users["group_name"] === "Conseil") { // ✅ Conseil peut voir les données mais SANS boutons d'action $result['data'][$key] = [ $value['id_sortie'], number_format($value['montant_retire'], 0, '.', ' '), $value['date_retrait'], $value['sortie_personnel'], $value['motif'], $value['source_fond'], $value['initiateur_demande'], $this->returnStoreName($value['store_id']), $value['commentaire'], $value['statut'], '' // Pas de boutons pour Conseil ]; } } return $this->response->setJSON($result); } private function returnStoreName(int $id) { $Stores = new Stores(); $stor = $Stores->getActiveStore(); $Storename = ""; foreach ($stor as $key => $value) { if ($value['id'] == $id) { $Storename = $value['name']; } } return $Storename; } public function createSortieCaisse() { $this->verifyRole('createSortieCaisse'); $validation = \Config\Services::validation(); $validation->setRules([ 'montant_retire' => 'required|numeric', 'motif_select' => 'required', 'nom' => 'required', 'fonction' => 'required', 'mode_paiement' => 'required', 'montant_lettre' => 'required', 'date_demande' => 'required' ]); if (!$validation->withRequest($this->request)->run()) { return $this->response->setJSON([ 'success' => false, 'messages' => $validation->getErrors() ]); } try { $session = session(); $user = $session->get('user'); // Nettoyage et conversion du montant $montant_retire_raw = $this->request->getPost('montant_retire'); $montant_retire = (float) str_replace([' ', ','], ['', '.'], $montant_retire_raw); // Récupérer le mode de paiement $mode_paiement = $this->request->getPost('mode_paiement'); if ($montant_retire <= 0) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Le montant doit être supérieur à 0' ]); } // RÉCUPÉRATION DES DONNÉES FINANCIÈRES $orders = new Orders(); $Recouvrement = new Recouvrement(); $sortieCaisse = new SortieCaisse(); $paymentData = $orders->getPaymentModes(); $totalRecouvrement = $Recouvrement->getTotalRecouvrements(); $total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse(); // EXTRACTION DES TOTAUX DES SORTIES PAR MODE DE PAIEMENT $total_sortie_espece = 0; $total_sortie_mvola = 0; $total_sortie_virement = 0; if (is_object($total_sortie_caisse)) { $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; } // Recouvrements $total_recouvrement_me = 0; $total_recouvrement_be = 0; $total_recouvrement_bm = 0; $total_recouvrement_mb = 0; if (is_object($totalRecouvrement)) { $total_recouvrement_me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0; $total_recouvrement_be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0; $total_recouvrement_bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0; $total_recouvrement_mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0; } // Orders $total_espece1 = 0; $total_espece2 = 0; $total_mvola1 = 0; $total_mvola2 = 0; $total_virement1 = 0; $total_virement2 = 0; if (is_object($paymentData)) { $total_espece1 = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0; $total_espece2 = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0; $total_mvola1 = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0; $total_mvola2 = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0; $total_virement1 = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0; $total_virement2 = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0; } // CALCUL DES SOLDES DISPONIBLES PAR MODE DE PAIEMENT $total_espece_disponible = $total_espece1 + $total_espece2 + $total_recouvrement_me + $total_recouvrement_be - $total_sortie_espece; $total_mvola_disponible = $total_mvola1 + $total_mvola2 - $total_recouvrement_me - $total_recouvrement_mb + $total_recouvrement_bm - $total_sortie_mvola; $total_virement_disponible = $total_virement1 + $total_virement2 - $total_recouvrement_be - $total_recouvrement_bm + $total_recouvrement_mb - $total_sortie_virement; // VÉRIFICATION SELON LE MODE DE PAIEMENT CHOISI $fonds_disponible = 0; $mode_paiement_label = ''; switch ($mode_paiement) { case 'En espèce': $fonds_disponible = $total_espece_disponible; $mode_paiement_label = 'en espèce'; break; case 'MVOLA': $fonds_disponible = $total_mvola_disponible; $mode_paiement_label = 'MVOLA'; break; case 'Virement Bancaire': $fonds_disponible = $total_virement_disponible; $mode_paiement_label = 'virement bancaire'; break; default: return $this->response->setJSON([ 'success' => false, 'messages' => 'Mode de paiement invalide' ]); } // Vérification des fonds if ($montant_retire > $fonds_disponible) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Décaissement échoué — fonds ' . $mode_paiement_label . ' insuffisants.
' . 'Disponible: ' . number_format($fonds_disponible, 0, ',', ' ') . ' Ar
' . 'Demandé: ' . number_format($montant_retire, 0, ',', ' ') . ' Ar

' . 'Soldes actuels:
' . '• Espèce: ' . number_format($total_espece_disponible, 0, ',', ' ') . ' Ar
' . '• MVOLA: ' . number_format($total_mvola_disponible, 0, ',', ' ') . ' Ar
' . '• Virement: ' . number_format($total_virement_disponible, 0, ',', ' ') . ' Ar' ]); } // PRÉPARATION DES DONNÉES $motif = $this->request->getPost('motif_select'); $data = [ 'montant_retire' => $montant_retire, 'date_retrait' => date('Y-m-d H:i:s'), 'motif' => $motif, 'commentaire' => $this->request->getPost('sortie_commentaire') ?? '', 'fournisseur' => $this->request->getPost('sortie_fournisseur') ?? '', 'nif_cin' => $this->request->getPost('sortie_nif') ?? '', 'statistique' => $this->request->getPost('sortie_statistique') ?? '', 'telephone' => $this->request->getPost('sortie_phone') ?? '', 'code_postal' => $this->request->getPost('sortie_adresse') ?? '', 'statut' => 'En attente', 'user_id' => $user['id'], 'store_id' => $user['store_id'], 'admin_raison' => '', 'sortie_personnel' => $user['firstname'] . ' ' . $user['lastname'], 'nom_demandeur' => $this->request->getPost('nom'), 'fonction_demandeur' => $this->request->getPost('fonction'), 'mode_paiement' => $mode_paiement, 'montant_lettre' => $this->request->getPost('montant_lettre'), 'date_demande' => $this->request->getPost('date_demande'), 'reference' => $this->request->getPost('reference') ?? '' ]; // Mapping source_fond et initiateur if (isset($this->mapping[$motif])) { $data['source_fond'] = $this->mapping[$motif]['source_fond']; $data['initiateur_demande'] = $this->mapping[$motif]['initiateur_demande']; } else { $data['source_fond'] = 'Caisse Courante'; $data['initiateur_demande'] = 'Caissière'; } // Champs supplémentaires pour montant > 1,000,000 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') ?? ''; $data['objet_depense'] = $this->request->getPost('objet_depense') ?? ''; $data['nature_depense'] = $this->request->getPost('nature_depense') ?? ''; $data['montant_estime'] = $this->request->getPost('montant_estime') ?? ''; $data['mode_reglement'] = $this->request->getPost('mode_reglement') ?? ''; $data['date_paiement_prevue'] = $this->request->getPost('date_paiement_prevue') ?? null; $justificatifs = $this->request->getPost('justificatifs'); if (is_array($justificatifs) && count($justificatifs) > 0) { $data['justificatifs'] = json_encode($justificatifs); } $data['visa_demandeur'] = $this->request->getPost('visa_demandeur') ?? ''; $data['date_visa_demandeur'] = $this->request->getPost('date_visa_demandeur') ?? null; $data['visa_chef_service'] = $this->request->getPost('visa_chef_service') ?? ''; $data['date_visa_chef_service'] = $this->request->getPost('date_visa_chef_service') ?? null; $data['visa_direction'] = $this->request->getPost('visa_direction') ?? ''; $data['date_visa_direction'] = $this->request->getPost('date_visa_direction') ?? null; $data['visa_superAdmin'] = $this->request->getPost('visa_superAdmin') ?? ''; $data['date_visa_superAdmin'] = $this->request->getPost('date_visa_superAdmin') ?? null; $data['observations'] = $this->request->getPost('observations') ?? ''; } // Gestion du fichier $preuveFile = $this->request->getFile('sortie_preuve'); if ($preuveFile && $preuveFile->isValid() && !$preuveFile->hasMoved()) { $newName = $preuveFile->getRandomName(); $uploadPath = WRITEPATH . 'uploads/sortie_caisse'; if (!is_dir($uploadPath)) { mkdir($uploadPath, 0755, true); } $preuveFile->move($uploadPath, $newName); $data['preuve_achat'] = $newName; $data['mime_type'] = $preuveFile->getClientMimeType(); } // INSERTION EN BASE $model = new SortieCaisse(); $result = $model->addSortieCaisse($data); if ($result) { // ✅ Notification pour TOUS les Direction, DAF et SuperAdmin try { if (class_exists('App\Controllers\NotificationController')) { $Notification = new NotificationController(); $message = "Nouvelle demande de décaissement de " . number_format($montant_retire, 0, ',', ' ') . " Ar (" . $mode_paiement . ")
" . "Store: " . $this->returnStoreName($user['store_id']) . "
" . "Demandeur: " . $user['firstname'] . ' ' . $user['lastname']; // ✅ Récupérer TOUS les stores $Stores = new Stores(); $allStores = $Stores->getActiveStore(); // ✅ Notifier Direction, DAF et SuperAdmin de TOUS les stores if (is_array($allStores) && count($allStores) > 0) { foreach ($allStores as $store) { $Notification->createNotification( $message, "Direction", (int)$store['id'], 'sortieCaisse' ); $Notification->createNotification( $message, "DAF", (int)$store['id'], 'sortieCaisse' ); $Notification->createNotification( $message, "SuperAdmin", (int)$store['id'], 'sortieCaisse' ); } } } } catch (\Exception $e) { log_message('error', 'Erreur notification createSortieCaisse: ' . $e->getMessage()); // Continue même si la notification échoue } return $this->response->setJSON([ '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
' . 'Notification envoyée à tous les Direction, DAF et SuperAdmin' ]); } else { return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de l\'enregistrement en base de données' ]); } } catch (\Exception $e) { log_message('error', 'Erreur createSortieCaisse: ' . $e->getMessage() . ' | Ligne: ' . $e->getLine()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur serveur: ' . $e->getMessage() ]); } } public function updateSortieCaisse($id_sortie) { $this->verifyRole('updateSortieCaisse'); $data['page_title'] = $this->pageTitle; if ($this->request->getMethod() === 'post') { $validation = \Config\Services::validation(); $validation->setRules([ 'montant_retire_edit' => 'required|numeric', 'nom_edit' => 'required', 'fonction_edit' => 'required', 'date_demande_edit' => 'required' // Suppression des règles de validation pour les champs qui seront en hidden // La preuve d'achat devient facultative ]); if (!$validation->withRequest($this->request)->run()) { return $this->response->setJSON([ 'success' => false, 'messages' => $validation->getErrors() ]); } try { $session = session(); $users = $session->get('user'); // Récupérer le décaissement actuel $sortieCaisse = new SortieCaisse(); $currentSortie = $sortieCaisse->getSortieCaisseSingle($id_sortie); if (!$currentSortie) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Décaissement introuvable' ]); } // Nettoyage et conversion du montant $montant_retire_raw = $this->request->getPost('montant_retire_edit'); $montant_retire = (float) str_replace([' ', ','], ['', '.'], $montant_retire_raw); // Récupérer le motif (disabled dans le form, donc on le récupère du champ hidden) $motif = $this->request->getPost('motif_select_edit') ?: $this->request->getPost('motif_select_edit_hidden'); // Récupérer le mode de paiement (maintenant éditable, donc directement du formulaire) $mode_paiement = $this->request->getPost('mode_paiement_edit'); if ($montant_retire <= 0) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Le montant doit être supérieur à 0' ]); } // RÉCUPÉRATION DES DONNÉES FINANCIÈRES $orders = new Orders(); $Recouvrement = new Recouvrement(); $paymentData = $orders->getPaymentModes(); $totalRecouvrement = $Recouvrement->getTotalRecouvrements(); $total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse(); // EXTRACTION DES TOTAUX DES SORTIES PAR MODE DE PAIEMENT $total_sortie_espece = 0; $total_sortie_mvola = 0; $total_sortie_virement = 0; if (is_object($total_sortie_caisse)) { $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; } // IMPORTANT : Ajouter le montant actuel du décaissement aux sorties // pour avoir le solde réel avant modification if ($currentSortie['statut'] === 'Valider') { switch ($currentSortie['mode_paiement']) { case 'En espèce': $total_sortie_espece -= (float) $currentSortie['montant_retire']; break; case 'MVOLA': $total_sortie_mvola -= (float) $currentSortie['montant_retire']; break; case 'Virement Bancaire': $total_sortie_virement -= (float) $currentSortie['montant_retire']; break; } } // Recouvrements $total_recouvrement_me = 0; $total_recouvrement_be = 0; $total_recouvrement_bm = 0; $total_recouvrement_mb = 0; if (is_object($totalRecouvrement)) { $total_recouvrement_me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0; $total_recouvrement_be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0; $total_recouvrement_bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0; $total_recouvrement_mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0; } // Orders $total_espece1 = 0; $total_espece2 = 0; $total_mvola1 = 0; $total_mvola2 = 0; $total_virement1 = 0; $total_virement2 = 0; if (is_object($paymentData)) { $total_espece1 = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0; $total_espece2 = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0; $total_mvola1 = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0; $total_mvola2 = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0; $total_virement1 = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0; $total_virement2 = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0; } // CALCUL DES SOLDES DISPONIBLES $total_espece_disponible = $total_espece1 + $total_espece2 + $total_recouvrement_me + $total_recouvrement_be - $total_sortie_espece; $total_mvola_disponible = $total_mvola1 + $total_mvola2 - $total_recouvrement_me - $total_recouvrement_mb + $total_recouvrement_bm - $total_sortie_mvola; $total_virement_disponible = $total_virement1 + $total_virement2 - $total_recouvrement_be - $total_recouvrement_bm + $total_recouvrement_mb - $total_sortie_virement; // VÉRIFICATION SELON LE MODE DE PAIEMENT CHOISI $fonds_disponible = 0; $mode_paiement_label = ''; switch ($mode_paiement) { case 'En espèce': $fonds_disponible = $total_espece_disponible; $mode_paiement_label = 'en espèce'; break; case 'MVOLA': $fonds_disponible = $total_mvola_disponible; $mode_paiement_label = 'MVOLA'; break; case 'Virement Bancaire': $fonds_disponible = $total_virement_disponible; $mode_paiement_label = 'virement bancaire'; break; default: return $this->response->setJSON([ 'success' => false, 'messages' => 'Mode de paiement invalide' ]); } // Vérification des fonds if ($montant_retire > $fonds_disponible) { return $this->response->setJSON([ 'success' => false, 'messages' => 'Modification échouée — fonds ' . $mode_paiement_label . ' insuffisants.
' . 'Disponible: ' . number_format($fonds_disponible, 0, ',', ' ') . ' Ar
' . 'Demandé: ' . number_format($montant_retire, 0, ',', ' ') . ' Ar' ]); } // PRÉPARATION DES DONNÉES $data = [ 'montant_retire' => $montant_retire, 'date_retrait' => date('Y-m-d H:i:s'), 'motif' => $motif, 'commentaire' => $this->request->getPost('sortie_commentaire_edit') ?? '', 'fournisseur' => $this->request->getPost('sortie_fournisseur_edit') ?? '', 'nif_cin' => $this->request->getPost('sortie_nif_edit') ?? '', 'statistique' => $this->request->getPost('sortie_statistique_edit') ?? '', 'telephone' => $this->request->getPost('sortie_phone_edit') ?? '', 'code_postal' => $this->request->getPost('sortie_adresse_edit') ?? '', 'sortie_personnel' => $users['firstname'] . ' ' . $users['lastname'], 'store_id' => $users['store_id'], 'nom_demandeur' => $this->request->getPost('nom_edit'), 'fonction_demandeur' => $this->request->getPost('fonction_edit'), 'mode_paiement' => $mode_paiement, 'montant_lettre' => $this->request->getPost('montant_lettre_edit'), 'date_demande' => $this->request->getPost('date_demande_edit'), 'reference' => $this->request->getPost('reference_edit') ?? '' ]; // Mapping source_fond et initiateur if (isset($this->mapping[$motif])) { $data['source_fond'] = $this->mapping[$motif]['source_fond']; $data['initiateur_demande'] = $this->mapping[$motif]['initiateur_demande']; } else { $data['source_fond'] = 'Caisse Courante'; $data['initiateur_demande'] = 'Caissière'; } // Gestion du fichier (FACULTATIF) $preuveFile = $this->request->getFile('sortie_preuve_edit'); if ($preuveFile && $preuveFile->isValid() && !$preuveFile->hasMoved()) { $newName = $preuveFile->getRandomName(); $uploadPath = WRITEPATH . 'uploads/sortie_caisse'; if (!is_dir($uploadPath)) { mkdir($uploadPath, 0755, true); } $preuveFile->move($uploadPath, $newName); $data['preuve_achat'] = $newName; $data['mime_type'] = $preuveFile->getClientMimeType(); } // Si aucun nouveau fichier n'est uploadé, on garde l'ancien (pas de modification) // MISE À JOUR EN BASE if ($sortieCaisse->updateSortieCaisse($id_sortie, $data)) { return $this->response->setJSON([ 'success' => true, 'messages' => 'Décaissement modifié avec succès !
' . 'Nouveau montant: ' . number_format($montant_retire, 0, ',', ' ') . ' Ar (' . $mode_paiement . ')' ]); } else { return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de la modification en base de données' ]); } } catch (\Exception $e) { log_message('error', 'Erreur updateSortieCaisse: ' . $e->getMessage() . ' | Ligne: ' . $e->getLine()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur serveur: ' . $e->getMessage() ]); } } } public function fetchSortieCaisseSingle($id) { if ($id) { $SortieCaisse = new SortieCaisse(); $data = $SortieCaisse->getSortieCaisseSingle($id); echo json_encode($data); } } public function validateSortieCaisse($id_sortie) { $this->verifyRole('validateSortieCaisse'); $data['page_title'] = $this->pageTitle; if ($this->request->getMethod() === 'post') { $data = [ 'admin_raison' => $this->request->getPost('admin_raison'), 'statut' => $this->request->getPost('statut'), ]; $session = session(); $users = $session->get('user'); $SortieCaisse = new SortieCaisse(); $Notification = new NotificationController(); if ($SortieCaisse->updateSortieCaisse($id_sortie, $data)) { $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 = "✅ Votre décaissement a été validé par la Direction
Store: " . $this->returnStoreName($store_id); $Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse'); break; case "Refuser": $message = "❌ Votre décaissement a été refusé par la Direction
Store: " . $this->returnStoreName($store_id) . "
Raison: " . $this->request->getPost('admin_raison'); $Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse'); break; case "En attente": $message = "⏳ Votre décaissement a été mis en attente par la Direction
Store: " . $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 ! Notification envoyée à la caissière de ' . $this->returnStoreName($store_id) ]); } else { return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de la modification du décaissement. Veuillez réessayer.' ]); } } } /** * Exporter les décaissements en Excel */ public function exportExcel() { $this->verifyRole('viewSortieCaisse'); try { $SortieCaisse = new SortieCaisse(); $data = $SortieCaisse->getAllSortieCaisse(); $session = session(); $user = $session->get('user'); // Créer un nouveau Spreadsheet $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // Définir le titre $sheet->setTitle('Décaissements'); // En-têtes selon le groupe de l'utilisateur if ($user["group_name"] === "Caissière") { $headers = [ 'ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Statut', 'Raison Admin' ]; } else { $headers = [ 'ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Source Fond', 'Initiateur', 'Magasin', 'Commentaire', 'Statut' ]; } // Écrire les en-têtes $col = 'A'; foreach ($headers as $header) { $sheet->setCellValue($col . '1', $header); $sheet->getStyle($col . '1')->applyFromArray([ 'font' => [ 'bold' => true, 'color' => ['rgb' => 'FFFFFF'] ], 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4'] ], 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER ] ]); $col++; } // Remplir les données $row = 2; foreach ($data as $item) { if ($user["group_name"] === "Caissière") { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['statut']); $sheet->setCellValue('G' . $row, $item['admin_raison']); } else { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['source_fond']); $sheet->setCellValue('G' . $row, $item['initiateur_demande']); $sheet->setCellValue('H' . $row, $this->returnStoreName($item['store_id'])); $sheet->setCellValue('I' . $row, $item['commentaire']); $sheet->setCellValue('J' . $row, $item['statut']); } $row++; } // Auto-dimensionner les colonnes foreach (range('A', $col) as $columnID) { $sheet->getColumnDimension($columnID)->setAutoSize(true); } // Ajouter des bordures $styleArray = [ 'borders' => [ 'allBorders' => [ 'borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => '000000'] ] ] ]; $sheet->getStyle('A1:' . $col . ($row - 1))->applyFromArray($styleArray); // Générer le fichier $writer = new Xlsx($spreadsheet); $filename = 'decaissements_' . date('Y-m-d_His') . '.xlsx'; // Envoyer le fichier au navigateur header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); header('Content-Disposition: attachment;filename="' . $filename . '"'); header('Cache-Control: max-age=0'); $writer->save('php://output'); exit; } catch (\Exception $e) { log_message('error', 'Erreur exportExcel: ' . $e->getMessage()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de l\'exportation: ' . $e->getMessage() ]); } } /** * Exporter les décaissements en CSV */ public function exportCsv() { $this->verifyRole('viewSortieCaisse'); try { $SortieCaisse = new SortieCaisse(); $data = $SortieCaisse->getAllSortieCaisse(); $session = session(); $user = $session->get('user'); // Créer un nouveau Spreadsheet $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // En-têtes selon le groupe de l'utilisateur if ($user["group_name"] === "Caissière") { $headers = [ 'ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Statut', 'Raison Admin' ]; } else { $headers = [ 'ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Source Fond', 'Initiateur', 'Magasin', 'Commentaire', 'Statut' ]; } // Écrire les en-têtes $col = 'A'; foreach ($headers as $header) { $sheet->setCellValue($col . '1', $header); $col++; } // Remplir les données $row = 2; foreach ($data as $item) { if ($user["group_name"] === "Caissière") { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['statut']); $sheet->setCellValue('G' . $row, $item['admin_raison']); } else { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['source_fond']); $sheet->setCellValue('G' . $row, $item['initiateur_demande']); $sheet->setCellValue('H' . $row, $this->returnStoreName($item['store_id'])); $sheet->setCellValue('I' . $row, $item['commentaire']); $sheet->setCellValue('J' . $row, $item['statut']); } $row++; } // Générer le fichier CSV $writer = new CsvWriter($spreadsheet); $writer->setDelimiter(';'); $writer->setEnclosure('"'); $writer->setLineEnding("\r\n"); $writer->setSheetIndex(0); $filename = 'decaissements_' . date('Y-m-d_His') . '.csv'; // Envoyer le fichier au navigateur header('Content-Type: text/csv'); header('Content-Disposition: attachment;filename="' . $filename . '"'); header('Cache-Control: max-age=0'); $writer->save('php://output'); exit; } catch (\Exception $e) { log_message('error', 'Erreur exportCsv: ' . $e->getMessage()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de l\'exportation: ' . $e->getMessage() ]); } } /** * Exporter avec filtres (optionnel) */ public function exportWithFilters() { $this->verifyRole('viewSortieCaisse'); $format = $this->request->getGet('format'); // 'excel' ou 'csv' $dateDebut = $this->request->getGet('date_debut'); $dateFin = $this->request->getGet('date_fin'); $statut = $this->request->getGet('statut'); try { $SortieCaisse = new SortieCaisse(); // Récupérer les données avec filtres $builder = $SortieCaisse->builder(); if ($dateDebut) { $builder->where('date_retrait >=', $dateDebut); } if ($dateFin) { $builder->where('date_retrait <=', $dateFin); } if ($statut) { $builder->where('statut', $statut); } $data = $builder->get()->getResultArray(); $session = session(); $user = $session->get('user'); // Créer le Spreadsheet $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Décaissements Filtrés'); // En-têtes if ($user["group_name"] === "Caissière") { $headers = ['ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Statut', 'Raison Admin']; } else { $headers = ['ID', 'Montant Retiré (Ar)', 'Date Retrait', 'Personnel', 'Motif', 'Source Fond', 'Initiateur', 'Magasin', 'Commentaire', 'Statut']; } $col = 'A'; foreach ($headers as $header) { $sheet->setCellValue($col . '1', $header); $sheet->getStyle($col . '1')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER] ]); $col++; } // Remplir les données $row = 2; foreach ($data as $item) { if ($user["group_name"] === "Caissière") { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['statut']); $sheet->setCellValue('G' . $row, $item['admin_raison']); } else { $sheet->setCellValue('A' . $row, $item['id_sortie']); $sheet->setCellValue('B' . $row, number_format($item['montant_retire'], 0, '.', ' ')); $sheet->setCellValue('C' . $row, $item['date_retrait']); $sheet->setCellValue('D' . $row, $item['sortie_personnel']); $sheet->setCellValue('E' . $row, $item['motif']); $sheet->setCellValue('F' . $row, $item['source_fond']); $sheet->setCellValue('G' . $row, $item['initiateur_demande']); $sheet->setCellValue('H' . $row, $this->returnStoreName($item['store_id'])); $sheet->setCellValue('I' . $row, $item['commentaire']); $sheet->setCellValue('J' . $row, $item['statut']); } $row++; } // Auto-dimensionner foreach (range('A', $col) as $columnID) { $sheet->getColumnDimension($columnID)->setAutoSize(true); } // Générer selon le format if ($format === 'csv') { $writer = new CsvWriter($spreadsheet); $writer->setDelimiter(';'); $filename = 'decaissements_filtres_' . date('Y-m-d_His') . '.csv'; header('Content-Type: text/csv'); } else { $writer = new Xlsx($spreadsheet); $filename = 'decaissements_filtres_' . date('Y-m-d_His') . '.xlsx'; header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); } header('Content-Disposition: attachment;filename="' . $filename . '"'); header('Cache-Control: max-age=0'); $writer->save('php://output'); exit; } catch (\Exception $e) { log_message('error', 'Erreur exportWithFilters: ' . $e->getMessage()); return $this->response->setJSON([ 'success' => false, 'messages' => 'Erreur lors de l\'exportation: ' . $e->getMessage() ]); } } }