diff --git a/app/Config/Routes.php b/app/Config/Routes.php index c3c64bdc..27fc28b3 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -49,6 +49,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) { * dashboard route */ $routes->get('/', [Dashboard::class, 'index']); + $routes->get('/dashboard/getTresorerieData', [Dashboard::class, 'getTresorerieData']); $routes->get('/ventes', [Auth::class, 'ventes']); $routes->get('/ventes/(:num)', [Auth::class, 'addImage']); $routes->get('/ventes/fetchProductVente/(:num)', [Auth::class, 'fetchProductVente']); @@ -388,6 +389,13 @@ $routes->group('avances', function ($routes) { $routes->post('validateAvance', [AvanceController::class, 'validateAvance']); }); + // Historique des actions (SuperAdmin) + $routes->group('action-log', ['filter' => 'auth'], static function ($routes) { + $routes->get('/', 'ActionLogController::index'); + $routes->get('fetchData', 'ActionLogController::fetchData'); + $routes->get('export', 'ActionLogController::export'); + }); + // historique $routes->group('historique', ['filter' => 'auth'], static function ($routes) { $routes->get('/', 'HistoriqueController::index'); diff --git a/app/Controllers/ActionLogController.php b/app/Controllers/ActionLogController.php new file mode 100644 index 00000000..162b309c --- /dev/null +++ b/app/Controllers/ActionLogController.php @@ -0,0 +1,154 @@ +get('user'); + + if ($user['group_name'] !== 'SuperAdmin') { + return redirect()->to('/'); + } + + $storesModel = new Stores(); + + $data['page_title'] = $this->pageTitle; + $data['stores'] = $storesModel->getActiveStore(); + + return $this->render_template('action_log/index', $data); + } + + public function fetchData() + { + $session = session(); + $user = $session->get('user'); + + if ($user['group_name'] !== 'SuperAdmin') { + return $this->response->setJSON(['data' => []]); + } + + $historiqueModel = new Historique(); + + $filters = [ + 'action' => $this->request->getGet('action'), + 'store_name' => $this->request->getGet('store_name'), + 'product_name' => $this->request->getGet('product'), + 'sku' => $this->request->getGet('sku'), + 'date_from' => $this->request->getGet('date_from'), + 'date_to' => $this->request->getGet('date_to'), + ]; + + $allData = $historiqueModel->getHistoriqueWithFilters($filters); + + $result = ['data' => []]; + + foreach ($allData as $row) { + $result['data'][] = [ + date('d/m/Y H:i', strtotime($row['created_at'])), + $row['user_name'] ?? 'Système', + $this->getActionBadge($row['action']), + $this->getTableLabel($row['table_name']), + $row['description'] ?? '', + ]; + } + + return $this->response->setJSON($result); + } + + private function getActionBadge($action) + { + $badges = [ + 'CREATE' => 'Création', + 'UPDATE' => 'Modification', + 'DELETE' => 'Suppression', + 'PAYMENT' => 'Paiement', + 'VALIDATE' => 'Validation', + 'REFUSE' => 'Refus', + 'DELIVERY' => 'Livraison', + 'ASSIGN_STORE' => 'Assignation', + 'ENTRER' => 'Entrée', + 'SORTIE' => 'Sortie', + 'IMPORT' => 'Import', + 'LOGIN' => 'Connexion', + ]; + + return $badges[$action] ?? '' . $action . ''; + } + + private function getTableLabel($tableName) + { + $labels = [ + 'orders' => 'Commande', + 'products' => 'Produit', + 'users' => 'Utilisateur', + 'groups' => 'Rôle', + 'avances' => 'Avance', + 'securite' => 'Sécurité', + 'remise' => 'Remise', + 'sortie_caisse' => 'Décaissement', + 'autres_encaissements' => 'Encaissement', + 'recouvrement' => 'Recouvrement', + 'stores' => 'Point de vente', + 'brands' => 'Marque', + 'categories' => 'Catégorie', + ]; + + return $labels[$tableName] ?? $tableName; + } + + public function export() + { + $session = session(); + $user = $session->get('user'); + + if ($user['group_name'] !== 'SuperAdmin') { + return redirect()->to('/'); + } + + $historiqueModel = new Historique(); + + $filters = [ + 'action' => $this->request->getGet('action'), + 'store_name' => $this->request->getGet('store_name'), + 'date_from' => $this->request->getGet('date_from'), + 'date_to' => $this->request->getGet('date_to'), + ]; + + $data = $historiqueModel->getHistoriqueWithFilters($filters); + + $csv = "\xEF\xBB\xBF"; // BOM UTF-8 pour Excel + $csv .= "Date;Heure;Utilisateur;Action;Module;Description\n"; + + foreach ($data as $row) { + $date = date('d-m-Y', strtotime($row['created_at'])); + $heure = date('H:i', strtotime($row['created_at'])); + $userName = $row['user_name'] ?? 'Système'; + $action = $row['action']; + $module = $this->getTableLabel($row['table_name']); + $description = str_replace('"', '""', $row['description'] ?? ''); + + $csv .= "{$date};{$heure};{$userName};{$action};{$module};\"{$description}\"\n"; + } + + $filename = 'historique_actions_' . date('Y-m-d_H-i') . '.csv'; + + return $this->response + ->setHeader('Content-Type', 'text/csv; charset=utf-8') + ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') + ->setBody($csv); + } +} diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index 897b7cb9..36e13482 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -44,6 +44,13 @@ abstract class AdminController extends BaseController protected function render_template($page = null, $data = []) { $data['user_permission'] = $this->permission; + // Charger le logo dynamiquement + if (!isset($data['company_logo'])) { + $companyModel = new Company(); + $companyData = $companyModel->getCompanyData(1); + $data['company_logo'] = $companyData['logo'] ?? 'assets/images/company_logo.jpg'; + $data['company_name'] = $companyData['company_name'] ?? 'MotorBike'; + } echo view('templates/header', $data); echo view('templates/header_menu', $data); echo view('templates/side_menubar', $data); @@ -51,6 +58,14 @@ abstract class AdminController extends BaseController echo view('templates/footer', $data); } + // Get company logo path + protected function getCompanyLogo() + { + $model = new Company(); + $data = $model->getCompanyData(1); + return $data['logo'] ?? 'assets/images/company_logo.jpg'; + } + // Get company currency using model public function company_currency() { diff --git a/app/Controllers/Auth.php b/app/Controllers/Auth.php index 17117f3a..f990c17a 100644 --- a/app/Controllers/Auth.php +++ b/app/Controllers/Auth.php @@ -6,6 +6,7 @@ use App\Models\ProductImage; use App\Models\Users; use App\Models\Stores; use App\Models\Products; +use App\Models\Historique; class Auth extends AdminController { @@ -79,6 +80,10 @@ public function loginPost() 'logged_in' => true ]); + // Log connexion + $historique = new Historique(); + $historique->logAction('users', 'LOGIN', $user['id'], "Connexion de {$user['firstname']} {$user['lastname']} ({$user['group_name']})"); + // Redirect to dashboard return redirect()->to('/'); } diff --git a/app/Controllers/AutresEncaissementsController.php b/app/Controllers/AutresEncaissementsController.php index 2e394b01..53c5f980 100644 --- a/app/Controllers/AutresEncaissementsController.php +++ b/app/Controllers/AutresEncaissementsController.php @@ -5,6 +5,7 @@ namespace App\Controllers; use App\Models\AutresEncaissements; use App\Models\Stores; use App\Models\Users; +use App\Models\Historique; class AutresEncaissementsController extends AdminController { @@ -106,7 +107,11 @@ class AutresEncaissementsController extends AdminController $Notification->notifyGroupsByPermissionAllStores('notifEncaissement', $notificationMessage, 'encaissements'); log_message('info', "✅ Encaissement {$encaissementId} créé - Notifications envoyées"); - + + // Log de l'action + $historique = new Historique(); + $historique->logAction('autres_encaissements', 'CREATE', $encaissementId, "Encaissement {$finalType} - Montant: {$montantFormate} Ar"); + return $this->response->setJSON([ 'success' => true, 'messages' => 'Encaissement enregistré avec succès' diff --git a/app/Controllers/AvanceController.php b/app/Controllers/AvanceController.php index f4c801b3..96df98b7 100644 --- a/app/Controllers/AvanceController.php +++ b/app/Controllers/AvanceController.php @@ -195,7 +195,72 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData') public function fetchAvanceBecameOrder() { - return $this->fetchAvanceDataGeneric('getCompletedAvances'); + helper(['url', 'form']); + $Avance = new Avance(); + $product = new Products(); + $result = ['data' => []]; + + $data = $Avance->getCompletedAvances(); + $session = session(); + $users = $session->get('user'); + + $isAdmin = $this->isAdmin($users); + $isCommerciale = $this->isCommerciale($users); + $isCaissier = $this->isCaissier($users); + + foreach ($data as $key => $value) { + $isOwner = $users['id'] === $value['user_id']; + + $buttons = $this->buildActionButtons( + $value, + $isAdmin, + $isOwner, + $isCaissier, + $isCommerciale + ); + + // Déterminer le statut + if ($value['is_order'] == 1) { + $status = ' Payé'; + } else { + $status = ' En attente de paiement'; + } + + $date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); + + if ($value['type_avance'] === 'mere') { + $productName = $value['product_name'] ?? 'Produit sur mer'; + } else { + $productName = !empty($value['product_name']) + ? $value['product_name'] + : $product->getProductNameById($value['product_id'] ?? 0); + } + + if ($isAdmin) { + $result['data'][] = [ + $value['customer_name'], + $value['customer_phone'], + $value['customer_address'], + $productName, + number_format((int)$value['gross_amount'], 0, ',', ' '), + number_format((int)$value['avance_amount'], 0, ',', ' '), + $status, + $date_time, + $buttons, + ]; + } elseif ($isCommerciale || $isCaissier) { + $result['data'][] = [ + $value['avance_id'], + $productName, + number_format((int)$value['avance_amount'], 0, ',', ' '), + $status, + $date_time, + $buttons, + ]; + } + } + + return $this->response->setJSON($result); } public function fetchExpiredAvance() @@ -998,24 +1063,29 @@ public function printInvoice($avance_id) return redirect()->back()->with('error', 'Accès non autorisé'); } - // ✅ CORRECTION SIMPLIFIÉE + // Récupérer les données de l'entreprise + $companyModel = new Company(); + $companyData = $companyModel->getCompanyData(1); + + // Récupérer les détails du produit + $product = null; + $brandName = ''; if ($avance['type_avance'] === 'mere' && !empty($avance['product_name'])) { - $productName = $avance['product_name']; $productDetails = [ 'marque' => $avance['product_name'], 'numero_moteur' => '', - 'puissance' => '' + 'puissance' => '', + 'couleur' => '', + 'chasis' => '', + 'type_avance' => 'mere', ]; } else { $product = $Products->find($avance['product_id']); - + if (!$product) { return redirect()->back()->with('error', 'Produit non trouvé'); } - - $productName = $product['name'] ?? 'N/A'; - - // ✅ Récupérer le nom de la marque + $brandName = 'N/A'; if (!empty($product['marque'])) { $db = \Config\Database::connect(); @@ -1028,15 +1098,18 @@ public function printInvoice($avance_id) $brandName = $brandResult['name']; } } - + $productDetails = [ 'marque' => $brandName, 'numero_moteur' => $product['numero_de_moteur'] ?? '', - 'puissance' => $product['puissance'] ?? '' + 'puissance' => $product['puissance'] ?? '', + 'couleur' => $product['cler'] ?? '', + 'chasis' => $product['chasis'] ?? '', + 'type_avance' => $avance['type_avance'] ?? 'terre', ]; } - - $html = $this->generatePrintableInvoiceHTML($avance, $productName, $productDetails); + + $html = $this->generatePrintableInvoiceHTML($avance, $productDetails, $companyData); return $this->response->setBody($html); @@ -1821,27 +1894,42 @@ public function getFullInvoiceForPrint($avance_id) /** * Générer le HTML optimisé pour l'impression (version identique à printInvoice) */ -private function generatePrintableInvoiceHTML($avance, $productName, $productDetails) +private function generatePrintableInvoiceHTML($avance, $productDetails, $companyData) { $avanceDate = date('d/m/Y', strtotime($avance['avance_date'])); $avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT); $customerName = strtoupper(esc($avance['customer_name'])); - $customerPhone = esc($avance['customer_phone']); - $customerCin = esc($avance['customer_cin']); + $customerPhone = esc($avance['customer_phone'] ?? ''); $grossAmount = number_format($avance['gross_amount'], 0, ',', ' '); $avanceAmount = number_format($avance['avance_amount'], 0, ',', ' '); $amountDue = number_format($avance['amount_due'], 0, ',', ' '); - $marque = esc($productDetails['marque']) ?: $productName; + $marque = esc($productDetails['marque']); $numeroMoteur = esc($productDetails['numero_moteur']); $puissance = esc($productDetails['puissance']); - + $couleur = esc($productDetails['couleur'] ?? ''); + $chasis = esc($productDetails['chasis'] ?? ''); + $typeAvance = $productDetails['type_avance'] ?? 'terre'; + $nSerieOuArrivage = ($typeAvance === 'mere') ? 'Arrivage' : ($chasis ?: $numeroMoteur); + $typePayment = esc($avance['type_payment'] ?? ''); + $unite = '1'; + + // Company data + $companyName = esc($companyData['company_name'] ?? 'MOTORBIKE STORE'); + $companyNIF = esc($companyData['NIF'] ?? ''); + $companySTAT = esc($companyData['STAT'] ?? ''); + $companyPhone = esc($companyData['phone'] ?? ''); + $companyPhone2 = esc($companyData['phone2'] ?? ''); + $companyAddress = esc($companyData['address'] ?? ''); + + $year = date('Y'); + return << - Facture Avance - KELY SCOOTERS + Facture d'Acompte - {$companyName} - +
-
- -
-

KELY SCOOTERS

-
-
-
NIF: 401 840 5554
-
STAT: 46101 11 2024 00317
-
-
-
Contact: +261 34 27 946 35 / +261 34 07 079 69
-
Antsakaviro en face WWF
-
+
ORIGINAL
+ +
FACTURE D'ACOMPTE DE RESERVATION
+ +
+
+ {$companyName}
+ NIF : {$companyNIF}
+ STAT : {$companySTAT}
+ Contact : {$companyPhone} / {$companyPhone2}
+ {$companyAddress} +
+
+
+ Date : {$avanceDate}
+ N° : {$avanceNumber}
+
- - -
-
-

FACTURE

-
Date: {$avanceDate}
-
N°: {$avanceNumber}CI 2025
-
-
DOIT ORIGINAL
+
+ +
+
DOIT
+ +
+ NOM : + {$customerName}
- - -
-
NOM: {$customerName} ({$customerPhone})
-
CIN: {$customerCin}
-
PC: {$grossAmount} Ar
-
AVANCE: {$avanceAmount} Ar
-
RAP: {$amountDue} Ar
+ +
+ N de Série ou Arrivage : + {$nSerieOuArrivage}
- - + +
+ Si Arrivage, préciser la caractéristique de la moto : Couleur + {$couleur} +
+ +
+ MOTO : + {$marque} +
+ +
+ Unité : + {$unite} +
+ +
+ Prix Unitaire : + {$grossAmount} Ar +
+ +
+ Montant Total : + {$grossAmount} Ar +
+ +
+ Mode de Paiement : + {$typePayment} +
+ +
+ AVANCE : + {$avanceAmount} Ar +
+ +
+ Reste à payer : + {$amountDue} Ar +
+ + - + - + - +
MARQUEN°MOTEURN° Châssis/Arrivage PUISSANCERAP (Ariary)Reste à payer (Ariary)
{$marque}{$numeroMoteur}{$chasis} {$puissance} {$amountDue}
- -
- - -
-
- -
-
-

CONDITIONS GÉNÉRALES

-
Date: {$avanceDate}
-
N°: {$avanceNumber}CI 2025
- -
- -
-

FIFANEKENA ARA-BAROTRA (Réservations)

-

Ry mpanjifa hajaina,

-

Natao ity fifanekena ity mba hialana amin'ny fivadihana hampitokisana amin'ny andaniny sy ankilany.

- -
- Andininy faha-1: FAMANDRAHANA SY FANDOAVAM-BOLA -

Ny mpividy dia manao famandrahana amin'ny alalan'ny fandoavambola mihoatra ny 25 isan-jato amin'ny vidin'entana rehetra (avances).

-
- -
- Andininy faha-2: FANDOAVAM-BOLA REHEFA TONGA NY ENTANA (ARRIVAGE) -

Rehefa tonga ny moto/pieces dia tsy maintsy mandoa ny 50 isan-jato ny vidin'entana ny mpamandrika.

-

Manana 15 andro kosa adoavana ny 25 isan-jato raha misy tsy fahafahana alohan'ny famoahana ny entana.

-
- -
- Andininy faha-3: FAMERENANA VOLA -

Raha toa ka misy antony tsy hakana ny entana indray dia tsy mamerina ny vola efa voaloha (avance) ny société.

-
- -
- Andininy faha-4: FEPETRA FANAMPINY -
    -
  • Tsy misafidy raha toa ka mamafa no ifanarahana.
  • -
  • Tsy azo atao ny mamerina ny entana efa nofandrahana.
  • -
  • Tsy azo atao ny manakalo ny entana efa nofandrahana.
  • -
-
-
- - -
- OBSERVATIONS / NOTES: -
-
- - -
-
-

NY MPAMANDRIKA

-
Signature
-
-
-

NY MPIVAROTRA

-
- KELY SCOOTERS
- NIF: 401 840 5554 -
-
-
+ +
+
+ {$companyName}
+ NIF : {$companyNIF}
+ STAT : {$companySTAT}
+ Contact : {$companyPhone} / {$companyPhone2}
+ {$companyAddress} +
+ +
FIFANEKENA ARA-BAROTRA (Reservations)
+ +
+ Ry mpanjifa hajaina,

+ Natao ity fifanekena ity mba hialana amin'ny fivadiahampitokisana amin'ny andaniny sy ankilany. +
+ +
+
• Andininy faha-1 : FAMANDRIHANA SY FANDOAVAM-BOLA
+

Ny mpividy dia manao famandrihana amin'ny alalan'ny fandoavam-bola mihoatra ny 25 isan-jato amin'ny vidin'entana rehetra (avances).

+
+ +
+
• Andininy faha-2 : FANDOAVAM-BOLA REHEFA TONGA NY ENTANA (ARRIVAGE)
+

Rehefa tonga ny moto/pieces dia tsy maintsy mandoha ny 50 isan-jato ny vidin'entana ny mpamandrika.

+

Manana 15 andro kosa andoaovana ny 25 isan-jato raha misy tsy fahafahana alohan'ny famoahana ny entana.

+
+ +
+
• Andininy faha-3 : FAMERENANA VOLA
+

Raha toa ka misy antony tsy hakana ny entana indray dia tsy mamerina ny vola efa voaloha (avance) ny société.

+
+ +
+
• Andininy faha-4 : FEPETRA FANAMPINY
+
    +
  • Tsy misafidy raha toa ka mamafa no ifanarahana.
  • +
  • Tsy azo atao ny mamerina vola efa naloha.
  • +
  • Tsy azo atao ny manakalo ny entana efa nofandrihana.
  • +
+
+ + +
+
+
NY MPAMANDRIKA
+
+
+
NY MPIVAROTRA
+ + + + HTML; diff --git a/app/Controllers/CompanyController.php b/app/Controllers/CompanyController.php index 2f2c4088..dce6ebe4 100644 --- a/app/Controllers/CompanyController.php +++ b/app/Controllers/CompanyController.php @@ -44,7 +44,15 @@ class CompanyController extends AdminController 'message' => $this->request->getPost('message'), 'currency' => $this->request->getPost('currency'), ]; - + + // Upload du logo + $logoFile = $this->request->getFile('logo'); + if ($logoFile && $logoFile->isValid() && !$logoFile->hasMoved()) { + $newName = 'company_logo.' . $logoFile->getExtension(); + $logoFile->move(FCPATH . 'assets/images/', $newName, true); + $data['logo'] = 'assets/images/' . $newName; + } + if ($Company->updateCompany($data, 1)) { session()->setFlashdata('success', 'Successfully updated'); return redirect()->to('/company'); diff --git a/app/Controllers/Dashboard.php b/app/Controllers/Dashboard.php index b4dc3fdf..7a5de5d6 100644 --- a/app/Controllers/Dashboard.php +++ b/app/Controllers/Dashboard.php @@ -284,4 +284,89 @@ class Dashboard extends AdminController return $this->render_template('dashboard', $data); } + /** + * AJAX : Recalculer la trésorerie avec filtre de dates + */ + public function getTresorerieData() + { + $session = session(); + $user_id = $session->get('user'); + + $startDate = $this->request->getGet('start_date'); + $endDate = $this->request->getGet('end_date'); + + $orderModel = new Orders(); + $Avance = new Avance(); + $sortieCaisse = new SortieCaisse(); + $Recouvrement = new Recouvrement(); + $autresEncaissementsModel = new AutresEncaissements(); + + $isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction', 'SuperAdmin']); + $storeIdFilter = $isAdmin ? null : $user_id['store_id']; + + // Données avec filtre de dates + $paymentData = $orderModel->getPaymentModes($startDate, $endDate); + $totalAvance = $Avance->getTotalAvance($startDate, $endDate); + $paymentDataAvance = $Avance->getPaymentModesAvance($startDate, $endDate); + $totalRecouvrement = $Recouvrement->getTotalRecouvrements(null, $startDate, $endDate); + $total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse($startDate, $endDate); + $totauxAutresEncaissements = $autresEncaissementsModel->getTotalEncaissementsByMode($storeIdFilter, $startDate, $endDate); + + // Autres encaissements + $total_autres_enc_espece = $totauxAutresEncaissements['total_espece']; + $total_autres_enc_mvola = $totauxAutresEncaissements['total_mvola']; + $total_autres_enc_virement = $totauxAutresEncaissements['total_virement']; + $total_autres_encaissements = $total_autres_enc_espece + $total_autres_enc_mvola + $total_autres_enc_virement; + + // Sorties + $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 + $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; + + // Orders + $total_orders = isset($paymentData->total) ? (float) $paymentData->total : 0; + $mv1_orders = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0; + $mv2_orders = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0; + $es1_orders = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0; + $es2_orders = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0; + $vb1_orders = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0; + $vb2_orders = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0; + + // Avances + $total_avances = isset($paymentDataAvance->total) ? (float) $paymentDataAvance->total : 0; + $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; + + // Brut + $total_mvola_brut = $mv1_orders + $mv2_orders + $mv_avances + $total_autres_enc_mvola; + $total_espece_brut = $es1_orders + $es2_orders + $es_avances + $total_autres_enc_espece; + $total_vb_brut = $vb1_orders + $vb2_orders + $vb_avances + $total_autres_enc_virement; + $total_brut = $total_orders + $total_avances + $total_autres_encaissements; + + // Final + $total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement; + $total_mvola_final = $total_mvola_brut - $me - $mb + $bm - $total_sortie_mvola; + $total_espece_final = $total_espece_brut + $me + $be - $total_sortie_espece; + $total_vb_final = $total_vb_brut - $be - $bm + $mb - $total_sortie_virement; + $total_final = $total_brut - $total_sortie_global; + + return $this->response->setJSON([ + 'total_brut' => number_format($total_brut, 0, '.', ' '), + 'total_sorties' => number_format($total_sortie_global, 0, '.', ' '), + 'total_net' => number_format($total_final, 0, '.', ' '), + 'espece' => number_format($total_espece_final, 0, '.', ' '), + 'mvola' => number_format($total_mvola_final, 0, '.', ' '), + 'banque' => number_format($total_vb_final, 0, '.', ' '), + 'avance_mvola' => number_format($mv_avances, 0, '.', ' '), + 'avance_espece' => number_format($es_avances, 0, '.', ' '), + 'avance_banque' => number_format($vb_avances, 0, '.', ' '), + ]); + } } \ No newline at end of file diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php index c3d90720..9933daa2 100644 --- a/app/Controllers/OrderController.php +++ b/app/Controllers/OrderController.php @@ -15,6 +15,7 @@ use App\Models\Attributes; use App\Models\OrderItems; use App\Models\Remise; use App\Models\FourchettePrix; +use App\Models\Historique; use PhpParser\Node\Stmt\Else_; @@ -562,20 +563,25 @@ class OrderController extends AdminController $order_id = $Orders->create($data, $posts); if ($order_id) { - // ✅ MARQUER LES PRODUITS COMME VENDUS (SEULEMENT SI QTY = 1) + // ✅ MARQUER LES PRODUITS COMME RÉSERVÉS (product_sold = 2) + // Le produit reste dans le stock mais n'est plus commandable + // Il passera à product_sold = 1 quand la sécurité confirmera la livraison $productModel = new Products(); foreach ($posts as $index => $product_id) { $qty = isset($quantities[$index]) ? (int)$quantities[$index] : 1; - + if ($qty == 1) { - // Vente unitaire : marquer comme vendu - $productModel->update($product_id, ['product_sold' => 1]); + $productModel->update($product_id, ['product_sold' => 2]); } - // Si qty > 1, vous devriez décrémenter un stock si vous en avez un } session()->setFlashdata('success', 'Créé avec succès'); - + + // Log de l'action + $historique = new Historique(); + $customerName = $this->request->getPost('customer_name'); + $historique->logAction('orders', 'CREATE', $order_id, "Création de la commande pour le client: {$customerName}"); + $Notification = new NotificationController(); $Stores = new Stores(); @@ -752,6 +758,16 @@ public function markAsDelivered() $updated = $Orders->update((int)$order_id, $updateData); if ($updated) { + // ✅ MARQUER LES PRODUITS COMME VENDUS (product_sold = 1) À LA LIVRAISON + $OrderItems = new OrderItems(); + $productModel = new Products(); + $orderItems = $OrderItems->getOrdersItemData((int)$order_id); + foreach ($orderItems as $item) { + if (!empty($item['product_id'])) { + $productModel->update($item['product_id'], ['product_sold' => 1]); + } + } + // ✅ Créer une notification centralisée pour tous les stores try { $Notification = new NotificationController(); @@ -1003,16 +1019,15 @@ public function update(int $id) if ($Orders->updates($id, $dataUpdate)) { - // ✅ GÉRER LE STATUT product_sold SELON LA QUANTITÉ + // ✅ MARQUER LES PRODUITS COMME RÉSERVÉS (product_sold = 2) + // Livraison confirmée par la sécurité → product_sold = 1 $productModel = new Products(); foreach ($posts as $index => $product_id) { $qty = isset($quantities[$index]) ? (int)$quantities[$index] : 1; - + if ($qty == 1) { - // Vente unitaire : marquer comme vendu - $productModel->update($product_id, ['product_sold' => 1]); + $productModel->update($product_id, ['product_sold' => 2]); } else { - // Vente multiple : ne pas marquer comme vendu (géré par stock si applicable) $productModel->update($product_id, ['product_sold' => 0]); } } @@ -1020,6 +1035,15 @@ public function update(int $id) $order_item_data = $OrderItems->getOrdersItemData($id); $product_ids = array_column($order_item_data, 'product_id'); + // Log de l'action paiement + $historique = new Historique(); + $bill_no_log = $current_order['bill_no']; + if ($old_paid_status == 2 && $paid_status == 1) { + $historique->logAction('orders', 'PAYMENT', $id, "Paiement de la commande {$bill_no_log}"); + } else { + $historique->logAction('orders', 'UPDATE', $id, "Modification de la commande {$bill_no_log}"); + } + $Notification = new NotificationController(); // ✅ NOTIFICATION CENTRALISÉE POUR VALIDATION @@ -1027,6 +1051,21 @@ public function update(int $id) $customer_name = $this->request->getPost('customer_name'); $bill_no = $current_order['bill_no']; + // ✅ CRÉER LES ENTRÉES DANS LA TABLE SECURITE POUR CHAQUE PRODUIT + $SecuriteModel = new \App\Models\Securite(); + foreach ($order_item_data as $item) { + if (!empty($item['product_id'])) { + $SecuriteModel->insert([ + 'product_id' => $item['product_id'], + 'bill_no' => $bill_no, + 'store_id' => (int)$user['store_id'], + 'status' => 'PENDING', + 'date' => date('Y-m-d H:i:s'), + 'active' => 1 + ]); + } + } + // ✅ Notification groupes ayant createSecurite $Notification->notifyGroupsByPermission( 'notifSecurite', @@ -1638,6 +1677,10 @@ public function update(int $id) // Supprimer la commande if ($Orders->remove($order_id)) { + // Log suppression + $historique = new Historique(); + $historique->logAction('orders', 'DELETE', $order_id, "Suppression de la commande ID: {$order_id}"); + $response['success'] = true; $response['messages'] = "Successfully removed"; } else { @@ -1959,362 +2002,316 @@ public function print5(int $id) $order = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($id); $company = $Company->getCompanyData(1); - $today = date('d/m/Y'); - - // ✅ RÉCUPÉRER LE TYPE DE DOCUMENT - $documentType = $order['document_type'] ?? 'facture'; - $documentTitle = ''; - - switch($documentType) { - case 'facture': - $documentTitle = 'FACTURE'; - break; - case 'bl': - $documentTitle = 'BON DE LIVRAISON'; - break; - case 'both': - $documentTitle = 'FACTURE & BON DE LIVRAISON'; - break; - default: - $documentTitle = 'FACTURE'; - } - - // ✅ Vérifier si c'est une avance "sur mer" - $isAvanceMere = false; - foreach ($items as $item) { - if (!empty($item['product_name']) && empty($item['product_id'])) { - $isAvanceMere = true; - break; - } - } + $today = date('d-m-Y'); + $year = date('Y'); $discount = (float) $order['discount']; $grossAmount = (float) $order['gross_amount']; $totalTTC = ($discount > 0) ? $discount : $grossAmount; - $totalHT = $totalTTC / 1.20; - $tva = $totalTTC - $totalHT; - $inWords = $this->numberToWords((int) round($totalTTC)); + $inWords = strtoupper($this->numberToWords((int) round($totalTTC))); - $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; + // Construire les lignes du tableau + $tableRows = ''; + foreach ($items as $it) { + $details = $this->getOrderItemDetails($it); + if (!$details) continue; + + $qty = isset($it['qty']) ? (int)$it['qty'] : 1; + $prix = ($discount > 0) ? ($discount / $qty) : $details['prix']; + + $tableRows .= ' + '.esc($details['marque']).' + '.esc($details['numero_chassis']).' + '.esc($details['puissance']).' + '.number_format($prix * $qty, 0, ' ', ' ').' MGA + '; + } + + // Lignes vides + $itemCount = count($items); + for ($j = $itemCount; $j < 3; $j++) { + $tableRows .= '    '; + } + + $billNo = esc($order['bill_no']); + $customerName = esc($order['customer_name']); + $customerAddress = esc($order['customer_address'] ?? ''); + $customerCin = esc($order['customer_cin'] ?? ''); + $companyName = esc($company['company_name']); + $companyNIF = esc($company['NIF']); + $companySTAT = esc($company['STAT']); + $companyPhone = esc($company['phone']); + $companyPhone2 = esc($company['phone2']); + $companyAddress = esc($company['address'] ?? ''); + $totalFormatted = number_format($totalTTC, 0, ' ', ' '); + $qrValue = "FACTURE N° {$billNo} | Client: {$customerName} | Montant: {$totalFormatted} MGA | Date: {$today} | Facebook: https://www.facebook.com/MOTORBIKESTORE2021/"; $html = ' - '.$documentTitle.' '.$order['bill_no'].' + FACTURE '.$billNo.' - + - +
'; - // ✅ GÉNÉRER 2 DOCUMENTS IDENTIQUES for ($i = 0; $i < 2; $i++) { + $qrId = 'qr_recto_'.$i; $html .= '
-
-
-

'.esc($company['company_name']).'

-

NIF : '.esc($company['NIF']).'

-

STAT : '.esc($company['STAT']).'

-

Contact : '.esc($company['phone']).' | '.esc($company['phone2']).'

+
+
+ '.$companyName.'
+ NIF : '.$companyNIF.'
+ STAT : '.$companySTAT.'
+ Contact : '.$companyPhone.' / '.$companyPhone2.'
+ '.$companyAddress.'
-
- Logo -

'.$documentTitle.' N° '.esc($order['bill_no']).'

+
+
DATE : '.$today.'
+
N° : '.$billNo.'
+
-
-

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.'

-
'; +
FACTURE
- // ✅ TABLEAU ADAPTÉ SELON LE TYPE -// Dans la boucle foreach ($items as $item) -if ($isAvanceMere) { - $html .= ' - - - - - - - - - - '; +
DOIT
- foreach ($items as $it) { - $details = $this->getOrderItemDetails($it); - - if (!$details) continue; - - $qty = isset($it['qty']) ? (int)$it['qty'] : 1; // ✅ RÉCUPÉRATION - $prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix']; - $prixTotal = $prixUnitaire * $qty; - - $html .= ' - - - - '; - } +
+
Nom : '.$customerName.'
+
Prenom :
+
Adresse : '.$customerAddress.'
+
CIN :
+
- $html .= ' - -
ProduitQuantité Prix unitaire (Ar)Montant (Ar)
'.esc($details['product_name']); - - if (!empty($details['commentaire'])) { - $html .= '
'.esc($details['commentaire']).''; - } - - $html .= '
'.esc($qty).' '.number_format($prixUnitaire, 0, '', ' ').''.number_format($prixTotal, 0, '', ' ').'
'; - -} else { - $html .= ' - - - - - - - - - - - - - - '; - - foreach ($items as $it) { - $details = $this->getOrderItemDetails($it); - - if (!$details) continue; - - $qty = isset($it['qty']) ? (int)$it['qty'] : 1; // ✅ RÉCUPÉRATION - $prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix']; - $prixTotal = $prixUnitaire * $qty; - - $html .= ' - - - - - - - - - - '; - } - - $html .= ' - -
MARQUEDésignationN° MoteurN° ChâssisPuissance (CC)Quantité Prix Unit. (Ar)Montant (Ar)
'.esc($details['marque']).''.esc($details['product_name']).''.esc($details['numero_moteur']).''.esc($details['numero_chassis']).''.esc($details['puissance']).''.esc($qty).' '.number_format($prixUnitaire, 0, '', ' ').''.number_format($prixTotal, 0, '', ' ').'
'; -} - $html .= ' - - - - - - - - - - - - - +
Prix (HT) :'.number_format($totalHT, 0, '', ' ').' Ar
TVA (20%) :'.number_format($tva, 0, '', ' ').' Ar
Total (TTC) :'.number_format($totalTTC, 0, '', ' ').' Ar
+ + + + + + + + + + '.$tableRows.' +
MARQUEN° CHASSISPUISSANCEPRIX (Ariary)
-
- Arrêté à la somme de :
- '.$inWords.' +
+ Arrête à la somme de : '.$inWords.' Ariary
-
-
L\'Acheteur

__________________
-
Le Vendeur

__________________
+
+
+
ACHETEUR
+
+
+
VENDEUR
+
'; } @@ -2322,31 +2319,44 @@ if ($isAvanceMere) { $html .= '
- +
'; - // ✅ GÉNÉRER 2 CONDITIONS IDENTIQUES for ($i = 0; $i < 2; $i++) { + $qrIdV = 'qr_verso_'.$i; $html .= ' -
-
-

Conditions Générales

- Logo -
+
+
Conditions Générales :
  • 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.
  • -
  • Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.
  • +
  • Le client doit s\'assurer de vérifier soigneusement la marchandise avant de quitter notre établissement.
  • Aucun service après-vente n\'est fourni.
  • -
  • La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.
  • -
  • 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.
  • +
  • La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion que la marchandise soit fonctionnelle ou pas
  • +
  • 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 un délai prévu par la loi.
-
L\'Acheteur
+
Acheteur
+
+ Logo + +
'; } $html .= '
+ + + '; @@ -2513,267 +2523,264 @@ public function print7(int $id) $Orders = new Orders(); $Company = new Company(); $OrderItems = new OrderItems(); + $Products = new Products(); $order = $Orders->getOrdersData($id); $items = $OrderItems->getOrdersItemData($id); $company = $Company->getCompanyData(1); $today = date('d/m/Y'); - // ✅ RÉCUPÉRER LE TYPE DE DOCUMENT - $documentType = $order['document_type'] ?? 'bl'; - $documentTitle = ''; - - switch($documentType) { - case 'facture': - $documentTitle = 'FACTURE'; - break; - case 'bl': - $documentTitle = 'BON DE LIVRAISON'; - break; - case 'both': - $documentTitle = 'FACTURE & BON DE LIVRAISON'; - break; - default: - $documentTitle = 'BON DE LIVRAISON'; - } - - // ✅ VÉRIFIER SI C'EST UNE AVANCE "SUR MER" - $isAvanceMere = false; - foreach ($items as $item) { - if (!empty($item['product_name']) && empty($item['product_id'])) { - $isAvanceMere = true; - break; - } - } - $discount = (float) $order['discount']; $grossAmount = (float) $order['gross_amount']; $totalTTC = ($discount > 0) ? $discount : $grossAmount; - $totalHT = $totalTTC / 1.20; - $tva = $totalTTC - $totalHT; - $paidLabel = $order['paid_status'] == 1 ? 'Payé' : 'Non payé'; + // Construire les lignes du tableau + $tableRows = ''; + foreach ($items as $item) { + $details = $this->getOrderItemDetails($item); + if (!$details) continue; + + $qty = isset($item['qty']) ? (int)$item['qty'] : 1; + $prix = ($discount > 0) ? ($discount / $qty) : $details['prix']; + + $tableRows .= ' + '.esc($details['marque']).' + '.esc($details['numero_moteur']).' + '.esc($details['puissance']).' + '.number_format($prix * $qty, 0, '', ' ').' + '; + } + + // Lignes vides pour compléter le tableau (min 4 lignes) + $itemCount = count($items); + for ($i = $itemCount; $i < 4; $i++) { + $tableRows .= '    '; + } $html = ' - '.$documentTitle.' '.$order['bill_no'].' + BON DE LIVRAISON '.esc($order['bill_no']).' - + +
+ +
BON DE LIVRAISON
-
-
-

'.esc($company['company_name']).'

-

NIF : '.esc($company['NIF']).'

-

STAT : '.esc($company['STAT']).'

-

Contact : '.esc($company['phone']).' | '.esc($company['phone2']).'

+ +
+
+ '.esc($company['company_name']).'
+ NIF : '.esc($company['NIF']).'
+ STAT : '.esc($company['STAT']).'
+ Contact : '.esc($company['phone']).' / '.esc($company['phone2']).'
+ '.esc($company['address']).' +
+
+
Date : '.$today.'
+ +
-
- Logo -

'.$documentTitle.' N° '.esc($order['bill_no']).'

+ + +
+
DOIT
+
Nom : '.esc($order['customer_name']).'
+
Prenom :
+
Adresse : '.esc($order['customer_address']).'
+
CIN : '.esc($order['customer_cin']).'
-
-
-

Client : '.esc($order['customer_name']).'

-

Adresse : '.esc($order['customer_address']).'

-

Téléphone : '.esc($order['customer_phone']).'

-

CIN : '.esc($order['customer_cin']).'

-

Antananarivo, le '.$today.'

-
'; - - // ✅ TABLEAU ADAPTÉ SELON LE TYPE - if ($isAvanceMere) { - $html .= ' - + +
- - - - + + + + - '; - - foreach ($items as $item) { - $details = $this->getOrderItemDetails($item); - - if (!$details) continue; - - $qty = isset($item['qty']) ? (int)$item['qty'] : 1; // ✅ RÉCUPÉRATION - $prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix']; - $prixTotal = $prixUnitaire * $qty; - - $html .= ' - - - - - - '; - } - - $html .= ' + + '.$tableRows.' -
ProduitQuantité Prix Unitaire (Ar)Montant (Ar) MARQUEN°MOTEURPUISSANCEPRIX (Ariary)
'.esc($details['product_name']); - - if (!empty($details['commentaire'])) { - $html .= '
Remarque : '.esc($details['commentaire']).''; - } - - $html .= '
'.esc($qty).' '.number_format($prixUnitaire, 0, '', ' ').''.number_format($prixTotal, 0, '', ' ').'
'; - - } else { - $html .= ' - - - - - - - - - - - - - - - '; - - $Products = new Products(); - $Brand = new Brands(); - $Category = new Category(); - - foreach ($items as $item) { - $details = $this->getOrderItemDetails($item); - - if (!$details) continue; - - $qty = isset($item['qty']) ? (int)$item['qty'] : 1; // ✅ RÉCUPÉRATION - $prixUnitaire = ($discount > 0) ? ($discount / $qty) : $details['prix']; - $prixTotal = $prixUnitaire * $qty; - - $categoryName = 'Non définie'; - if ($details['type'] === 'terre' && !empty($item['product_id'])) { - $p = $Products->getProductData($item['product_id']); - if (!empty($p['categorie_id'])) { - $categoryData = $Category->find($p['categorie_id']); - if ($categoryData && isset($categoryData['name'])) { - $categoryName = $categoryData['name']; - } - } - } - - $html .= ' - - - - - - - - - - - '; - } - - $html .= ' - -
NomMarqueCatégorieN° MoteurChâssisPuissance (CC)Quantité Prix Unit. (Ar)Montant (Ar)
'.esc($details['product_name']).''.esc($details['marque']).''.esc($categoryName).''.esc($details['numero_moteur']).''.esc($details['numero_chassis']).''.esc($details['puissance']).''.esc($qty).' '.number_format($prixUnitaire, 0, '', ' ').''.number_format($prixTotal, 0, '', ' ').'
'; - } - $html .= ' - - - - - - - - - - - - - - - - - '; +
Total HT :'.number_format($totalHT, 0, '', ' ').' Ar
TVA (20%) :'.number_format($tva, 0, '', ' ').' Ar
Total TTC :'.number_format($totalTTC, 0, '', ' ').' Ar
Statut :'.$paidLabel.'
- if (!empty($order['order_payment_mode'])) { - $html .= ' - - Mode de paiement : - '.esc($order['order_payment_mode']).' - '; - } - - if (!empty($order['tranche_1'])) { - $html .= ' - - Tranche 1 : - '.number_format((float)$order['tranche_1'], 0, '', ' ').' Ar - '; - } - - if (!empty($order['tranche_2'])) { - $html .= ' - - Tranche 2 : - '.number_format((float)$order['tranche_2'], 0, '', ' ').' Ar - '; - } - - $html .= ' - - -
-
L\'acheteur

__________________
-
Le vendeur

__________________
-
- - - -
-
-

Conditions Générales

- Logo + + -
    -
  • 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.
  • -
  • Le client doit vérifier soigneusement la marchandise avant de quitter notre établissement.
  • -
  • Aucun service après-vente n\'est fourni.
  • -
  • La moto est vendue sans garantie, car il s\'agit d\'un modèle d\'occasion.
  • -
  • 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.
  • -
-
L\'Acheteur
+ + '; @@ -2898,7 +2905,7 @@ public function print31(int $id) echo '

Magasin : ' . esc($this->returnStore($order_data['store_id'])) . '

'; echo '
'; echo '
'; - echo 'Logo'; + echo 'Logo'; echo '

' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '

'; echo '

Antananarivo, le ' . date('d/m/Y') . '

'; echo '
'; @@ -3022,7 +3029,7 @@ public function print31(int $id) echo '

Contact : ' . esc($company_info['phone']) . ' | ' . esc($company_info['phone2']) . '

'; echo '
'; echo '
'; - echo 'Logo'; + echo 'Logo'; echo '

' . $documentTitle . ' N° ' . esc($order_data['bill_no']) . '

'; echo '
'; echo '
'; @@ -3136,7 +3143,7 @@ public function print31(int $id) echo '
'; echo '
'; echo '

Conditions Générales

'; - echo 'Logo'; + echo 'Logo'; echo '
'; echo '
    '; echo '
  • 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.
  • '; diff --git a/app/Controllers/RemiseController.php b/app/Controllers/RemiseController.php index 699363f5..d4aeb2e1 100644 --- a/app/Controllers/RemiseController.php +++ b/app/Controllers/RemiseController.php @@ -5,8 +5,11 @@ namespace App\Controllers; use App\Controllers\AdminController; use App\Models\Notification; use App\Models\Orders; +use App\Models\OrderItems; +use App\Models\Products; use App\Models\Remise; use App\Models\Stores; +use App\Models\Historique; class RemiseController extends AdminController { @@ -149,6 +152,16 @@ class RemiseController extends AdminController if ($demande_status == 'Refusé') { if ($order_id) { $ordersModel->update($order_id, ['paid_status' => 0]); + + // Libérer les produits de la commande (retour en stock) + $orderItemsModel = new OrderItems(); + $productModel = new Products(); + $orderItems = $orderItemsModel->getOrdersItemData($order_id); + foreach ($orderItems as $item) { + if (!empty($item['product_id'])) { + $productModel->update($item['product_id'], ['product_sold' => 0]); + } + } } // Message de refus @@ -163,7 +176,11 @@ class RemiseController extends AdminController $Notification->notifyGroupsByPermissionAllStores('notifRemise', $messageRefus . "
    Pour information", 'remise/'); $message = 'La demande de remise a été refusée. Le commercial et les responsables ont été notifiés.'; - + + // Log refus remise + $historique = new Historique(); + $historique->logAction('remise', 'REFUSE', $id_demande, "Refus de la demande de remise - Facture: {$bill_no}"); + } elseif ($demande_status == 'Accepté' || $demande_status == 'Validé') { // ✅ SI ACCEPTÉ PAR LE SUPERADMIN if ($order_id) { @@ -184,6 +201,10 @@ class RemiseController extends AdminController $Notification->notifyGroupsByPermissionAllStores('notifRemise', $messageAcceptation . "
    Pour information", 'remise/'); $message = 'La demande de remise a été acceptée. La caissière, le commercial et les responsables ont été notifiés.'; + + // Log validation remise + $historique = new Historique(); + $historique->logAction('remise', 'VALIDATE', $id_demande, "Validation de la demande de remise - Facture: {$bill_no}"); } return $this->response->setJSON([ diff --git a/app/Controllers/SecuriteController.php b/app/Controllers/SecuriteController.php index c9468714..a5b3f84f 100644 --- a/app/Controllers/SecuriteController.php +++ b/app/Controllers/SecuriteController.php @@ -6,6 +6,7 @@ use App\Models\Securite; use App\Models\Products; use App\Models\Orders; use App\Models\Stores; +use App\Models\Historique; class SecuriteController extends AdminController { @@ -48,7 +49,7 @@ class SecuriteController extends AdminController // Bouton d’action $buttons = in_array('validateCommande1', $this->permission) - ? '' + ? '' : ''; // Statut @@ -102,7 +103,7 @@ class SecuriteController extends AdminController public function update(int $id) { $this->verifyRole('updateCommande1'); - $storeModel = new Securite(); + $securiteModel = new Securite(); $post = $this->request->getPost(); $response = []; @@ -113,12 +114,40 @@ class SecuriteController extends AdminController ]; $session = session(); $users = $session->get('user'); - $Notification = new NotificationController(); - if ($storeModel->updateSecurite($data, $id)) { + $Notification = new NotificationController(); + + if ($securiteModel->updateSecurite($data, $id)) { if ($post['status'] === "Validé") { - $Notification->notifyGroupsByPermission('notifRemise', 'Une commande a été validé', (int)$users['store_id'], 'orders'); + // ✅ Récupérer les infos de la ligne securite + $securiteData = $securiteModel->getSecuriteData($id); + + if ($securiteData) { + // ✅ Marquer le produit comme vendu (product_sold = 1) + $productModel = new Products(); + $productModel->update($securiteData['product_id'], ['product_sold' => 1]); + + // ✅ Mettre à jour la commande liée (paid_status = 3 = livré) + if (!empty($securiteData['bill_no'])) { + $orderModel = new Orders(); + $order = $orderModel->getOrdersDataByBillNo($securiteData['bill_no']); + if ($order) { + $orderModel->update($order['id'], [ + 'paid_status' => 3, + 'delivered_by' => $users['id'], + 'delivered_at' => date('Y-m-d H:i:s') + ]); + } + } + } + + $Notification->notifyGroupsByPermission('notifRemise', 'Une commande a été validée et livrée', (int)$users['store_id'], 'orders'); } - $response = ['success' => true, 'messages' => 'Mise à jour réussie']; + // Log de l'action livraison + $historique = new Historique(); + $billNo = $securiteData['bill_no'] ?? 'N/A'; + $historique->logAction('securite', 'DELIVERY', $id, "Confirmation de livraison - Facture: {$billNo}"); + + $response = ['success' => true, 'messages' => 'Livraison confirmée avec succès']; } else { $response = ['success' => false, 'messages' => 'Erreur en base lors de la mise à jour']; } diff --git a/app/Models/Avance.php b/app/Models/Avance.php index 7804e4d6..573936e2 100644 --- a/app/Models/Avance.php +++ b/app/Models/Avance.php @@ -189,20 +189,26 @@ class Avance extends Model { } // ✅ CORRECTION : getTotalAvance pour la caissière - public function getTotalAvance() { + public function getTotalAvance($startDate = null, $endDate = null) { $session = session(); $users = $session->get('user'); $isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']); - + try { $builder = $this->select('SUM(avance_amount) AS ta') ->where('is_order', 0) - ->where('active', 1); // ✅ Ajout du filtre active - + ->where('active', 1); + if (!$isAdmin) { - $builder->where('store_id', $users['store_id']); // ✅ Filtre par store pour caissière + $builder->where('store_id', $users['store_id']); } - + if ($startDate) { + $builder->where('DATE(avance_date) >=', $startDate); + } + if ($endDate) { + $builder->where('DATE(avance_date) <=', $endDate); + } + return $builder->get()->getRowObject(); } catch (\Exception $e) { log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage()); @@ -212,7 +218,7 @@ class Avance extends Model { // ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière // ✅ MODIFICATION : Ne compter QUE les avances VALIDÉES -public function getPaymentModesAvance() +public function getPaymentModesAvance($startDate = null, $endDate = null) { $session = session(); $users = $session->get('user'); @@ -226,14 +232,19 @@ public function getPaymentModesAvance() SUM(CASE WHEN LOWER(type_payment) = "en espèce" THEN avance_amount ELSE 0 END) AS total_espece, SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire ') - ->where('validated', 1) // ✅ AJOUT : Uniquement les avances validées + ->where('validated', 1) ->where('active', 1) ->where('is_order', 0); - - // ✅ Filtre par store pour non-admin + if (!$isAdmin) { $builder->where('store_id', $users['store_id']); } + if ($startDate) { + $builder->where('DATE(avance_date) >=', $startDate); + } + if ($endDate) { + $builder->where('DATE(avance_date) <=', $endDate); + } $result = $builder->get()->getRowObject(); @@ -442,9 +453,11 @@ public function getPaymentModesAvance() $isCommercial = in_array($users['group_name'], ['COMMERCIALE']); $isCaissier = in_array($users['group_name'], ['Caissière']); - $builder = $this->where('is_order', 0) - ->where('active', 1) - ->where('amount_due', 0); + $builder = $this->where('amount_due', 0) + ->groupStart() + ->where('active', 1) + ->orWhere('is_order', 1) + ->groupEnd(); if ($isCommercial) { $builder->where('user_id', $users['id']); diff --git a/app/Models/Company.php b/app/Models/Company.php index a405782b..8d050368 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -13,7 +13,7 @@ class Company extends Model protected $table = 'company'; // List all the fields that are allowed to be updated or inserted protected $allowedFields = [ - 'company_name', 'service_charge_value', 'vat_charge_value', 'address', 'phone', 'phone2', 'NIF', 'STAT', 'country', 'message', 'currency', + 'company_name', 'service_charge_value', 'vat_charge_value', 'address', 'phone', 'phone2', 'NIF', 'STAT', 'country', 'message', 'currency', 'logo', ]; /** diff --git a/app/Models/Historique.php b/app/Models/Historique.php index dc002a6b..cafcf3a3 100644 --- a/app/Models/Historique.php +++ b/app/Models/Historique.php @@ -16,6 +16,8 @@ class Historique extends Model 'sku', 'store_name', 'description', + 'user_id', + 'user_name', 'created_at' ]; protected $useTimestamps = false; @@ -111,6 +113,32 @@ class Historique extends Model return $this->insert($data); } + /** + * Enregistrer une action utilisateur dans l'historique + */ + public function logAction($tableName, $action, $rowId, $description) + { + $session = session(); + $user = $session->get('user'); + + $data = [ + 'table_name' => $tableName, + 'action' => $action, + 'row_id' => $rowId, + 'product_name' => null, + 'sku' => null, + 'store_name' => $user['store_name'] ?? null, + 'description' => $description, + 'user_id' => $user['id'] ?? null, + 'user_name' => isset($user['firstname'], $user['lastname']) + ? $user['firstname'] . ' ' . $user['lastname'] + : ($user['username'] ?? 'Système'), + 'created_at' => date('Y-m-d H:i:s') + ]; + + return $this->insert($data); + } + /** * Nettoyer l'historique ancien (plus de X jours) */ diff --git a/app/Models/Orders.php b/app/Models/Orders.php index 0c838711..3b61036d 100644 --- a/app/Models/Orders.php +++ b/app/Models/Orders.php @@ -387,12 +387,12 @@ class Orders extends Model ->findAll(); } - public function getPaymentModes() + public function getPaymentModes($startDate = null, $endDate = null) { $session = session(); $users = $session->get('user'); $isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']); - + $baseQuery = $this->db->table('orders') ->select(' @@ -462,12 +462,19 @@ class Orders extends Model ELSE 0 END) AS total_virement_bancaire2 ') - ->whereIn('orders.paid_status', [1, 3]); - + ->whereIn('orders.paid_status', [1, 3]); + if (!$isAdmin) { $baseQuery->where('orders.store_id', $users['store_id']); } - + + if ($startDate) { + $baseQuery->where('DATE(orders.date_time) >=', $startDate); + } + if ($endDate) { + $baseQuery->where('DATE(orders.date_time) <=', $endDate); + } + return $baseQuery->get()->getRowObject(); } diff --git a/app/Models/Products.php b/app/Models/Products.php index 5debdf50..2b818bb2 100644 --- a/app/Models/Products.php +++ b/app/Models/Products.php @@ -21,8 +21,8 @@ class Products extends Model $isAdmin = in_array($user['group_name'], ['SuperAdmin', 'Direction', 'DAF']); $builder = $this->where('is_piece', 0) - ->where('product_sold', 0); - + ->whereIn('product_sold', [0, 2]); + if (!$isAdmin) { if (empty($user['store_id']) || $user['store_id'] == 0) { $builder->where('id', -1); @@ -58,8 +58,7 @@ class Products extends Model return $builder->join('brands', 'brands.id = products.marque') ->orderBy('products.id', 'DESC') - ->select('brands.name as brand_name,COUNT( products.id) as total_product, products.store_id as store_id,products.*') - ->groupBy('store_id') + ->select('brands.name as brand_name, products.store_id as store_id, products.*') ->findAll(); } @@ -75,7 +74,7 @@ class Products extends Model public function getProductDataStore(int $store_id, bool $excludeAvance = true, int $currentProductId = null) { $builder = $this->where('is_piece', 0) - ->where('product_sold', 0) + ->whereIn('product_sold', [0, 2]) ->where('availability', 1) ->where('store_id', $store_id); @@ -92,13 +91,14 @@ class Products extends Model $builder->where("id NOT IN ($subQueryAvances)", null, false); } - // ✅ LISTE : Exclure TOUS les produits ayant une commande (statuts 1, 2, 3) + // ✅ LISTE : Exclure uniquement les produits dont la commande est LIVRÉE (paid_status = 3) + // Les produits commandés mais non livrés restent visibles dans les produits disponibles $subQueryOrders = $db->table('orders_item') ->select('orders_item.product_id') ->join('orders', 'orders.id = orders_item.order_id') - ->whereIn('orders.paid_status', [1, 2, 3]) // ✅ Disparaît de la liste dès qu'il y a une commande + ->where('orders.paid_status', 3) ->getCompiledSelect(); - + $builder->where("id NOT IN ($subQueryOrders)", null, false); // Exception pour le produit actuel lors de modification de commande diff --git a/app/Models/Securite.php b/app/Models/Securite.php index 4cacc93b..96907c2d 100644 --- a/app/Models/Securite.php +++ b/app/Models/Securite.php @@ -12,7 +12,7 @@ class Securite extends Model */ protected $table = 'securite'; protected $primaryKey = 'id'; // Primary key column - protected $allowedFields = ['product_id', 'status', 'date', 'active']; + protected $allowedFields = ['product_id', 'bill_no', 'store_id', 'status', 'date', 'active']; diff --git a/app/Models/SortieCaisse.php b/app/Models/SortieCaisse.php index 0cff65fe..0c44cfac 100644 --- a/app/Models/SortieCaisse.php +++ b/app/Models/SortieCaisse.php @@ -165,25 +165,24 @@ class SortieCaisse extends Model /** * ✅ MODIFICATION : DAF, Direction, SuperAdmin voient TOUS les totaux */ - public function getTotalSortieCaisse() { + public function getTotalSortieCaisse($startDate = null, $endDate = null) { $session = session(); $users = $session->get('user'); - - // ✅ DAF, Direction, SuperAdmin : Voir TOUS les stores + $isAdmin = in_array($users['group_name'], ['Direction', 'DAF', 'SuperAdmin']); - + if ($isAdmin) { try { - return $this->select(' + $builder = $this->select(' SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece, SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola, SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement, SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr ') - // ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider") - ->where('statut', 'Payé') - ->get() - ->getRowObject(); + ->where('statut', 'Payé'); + if ($startDate) { $builder->where('DATE(created_at) >=', $startDate); } + if ($endDate) { $builder->where('DATE(created_at) <=', $endDate); } + return $builder->get()->getRowObject(); } catch (\Exception $e) { log_message('error', 'Erreur getTotalSortieCaisse (Admin) : ' . $e->getMessage()); return (object)[ @@ -194,19 +193,18 @@ class SortieCaisse extends Model ]; } } else { - // ✅ CAISSIÈRE : Uniquement son store ET statut "Payé" try { - return $this->select(' + $builder = $this->select(' SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece, SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola, SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement, SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr ') ->where('store_id', $users['store_id']) - // ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider") - ->where('statut', 'Payé') - ->get() - ->getRowObject(); + ->where('statut', 'Payé'); + if ($startDate) { $builder->where('DATE(created_at) >=', $startDate); } + if ($endDate) { $builder->where('DATE(created_at) <=', $endDate); } + return $builder->get()->getRowObject(); } catch (\Exception $e) { log_message('error', 'Erreur getTotalSortieCaisse (Store) : ' . $e->getMessage()); return (object)[ diff --git a/app/Views/action_log/index.php b/app/Views/action_log/index.php new file mode 100644 index 00000000..028ee881 --- /dev/null +++ b/app/Views/action_log/index.php @@ -0,0 +1,178 @@ + +
    +
    +

    + Historique des Actions +

    + +
    + +
    +
    + + +
    +
    +

    Filtres

    +
    + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +

    Toutes les actions

    +
    +
    + + + + + + + + + + +
    DateUtilisateurActionModuleDescription
    +
    +
    +
    +
    + + diff --git a/app/Views/avances/avance.php b/app/Views/avances/avance.php index 2cec4e4e..b1c73ff0 100644 --- a/app/Views/avances/avance.php +++ b/app/Views/avances/avance.php @@ -547,9 +547,33 @@ $(document).ready(function() { manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns); }); + var adminCompletedColumns = [ + { title: "Client" }, + { title: "Téléphone" }, + { title: "Adresse" }, + { title: "Produit" }, + { + title: "Prix", + render: function(data, type, row) { + return type === 'display' ? formatNumber(data) : data; + } + }, + { + title: "Avance", + render: function(data, type, row) { + return type === 'display' ? formatNumber(data) : data; + } + }, + { title: "Statut" }, + { title: "Date" } + + ,{ title: "Action", orderable: false, searchable: false } + + ]; + $('#avance_order').on('click', function() { $('#table-title').text('Avances Complètes'); - manageTable = initAvanceTable(base_url + 'avances/fetchAvanceBecameOrder', adminColumns); + manageTable = initAvanceTable(base_url + 'avances/fetchAvanceBecameOrder', adminCompletedColumns); }); $('#avance_expired').on('click', function() { @@ -627,21 +651,32 @@ $('#avance_order').on('click', function() { } rebuildTableHeaders([ - '#', - 'Produit', - 'Avance', - 'Reste à payer', + '#', + 'Produit', + 'Avance', + 'Statut', 'Date', 'Action' ]); - + + var completedUserColumns = [ + { title: "#" }, + { title: "Produit" }, + { title: "Avance" }, + { title: "Statut" }, + { title: "Date" } + + ,{ title: "Action", orderable: false, searchable: false } + + ]; + manageTable = $('#avanceTable').DataTable({ ajax: { url: base_url + 'avances/fetchAvanceBecameOrder', type: 'GET', dataSrc: 'data' }, - columns: userColumns, + columns: completedUserColumns, language: datatableLangFr }); diff --git a/app/Views/company/index.php b/app/Views/company/index.php index d1f6d756..66625864 100644 --- a/app/Views/company/index.php +++ b/app/Views/company/index.php @@ -34,7 +34,7 @@

    Gérer les informations sur l'entreprise

    -
    +
    @@ -48,6 +48,19 @@
    +
    + +
    + + Logo + + Logo par defaut + +
    + +

    Formats acceptes : JPG, PNG, GIF. Laissez vide pour garder le logo actuel.

    +
    +
    diff --git a/app/Views/dashboard.php b/app/Views/dashboard.php index 17df9e38..be021a21 100644 --- a/app/Views/dashboard.php +++ b/app/Views/dashboard.php @@ -311,6 +311,13 @@

    Résumé de Trésorerie

    +
    + + au + + + +
    @@ -320,7 +327,7 @@
    💰 Total Brut -
    +
    Ar
    Orders + Avances @@ -336,7 +343,7 @@
    💸 Décaissements -
    +
    Ar
    Espèce + MVOLA + Virement @@ -352,7 +359,7 @@
    ✅ Solde Net -
    +
    Ar
    Disponible en caisse @@ -371,10 +378,10 @@
    -
    +
    Ar
    -
    💵 Espèce Disponible
    +
    Espece Disponible
    @@ -384,10 +391,10 @@
    -
    +
    Ar
    -
    📱 MVOLA Disponible
    +
    MVOLA Disponible
    @@ -397,10 +404,10 @@
    -
    +
    Ar
    -
    🏦 Banque Disponible
    +
    Banque Disponible
    @@ -408,6 +415,48 @@
    + + +
    @@ -419,19 +468,19 @@
    -
    Ar
    +
    Ar
    MVOLA
    -
    Ar
    - ESPÈCE +
    Ar
    + ESPECE
    -
    Ar
    +
    Ar
    BANQUE
    diff --git a/app/Views/groups/create.php b/app/Views/groups/create.php index a39747e8..258f2e5f 100644 --- a/app/Views/groups/create.php +++ b/app/Views/groups/create.php @@ -149,28 +149,27 @@ Recouvrement - + - Décaissement - + Remise - - - - - + + + Validation commande @@ -181,11 +180,11 @@ Securité - - - + + - Rapports @@ -197,11 +196,11 @@ Avances - - - + + - Entreprise diff --git a/app/Views/groups/edit.php b/app/Views/groups/edit.php index 864914ab..935513b1 100644 --- a/app/Views/groups/edit.php +++ b/app/Views/groups/edit.php @@ -420,12 +420,6 @@ Recouvrement - > > + > Sortie caisse - > > + > Remise - > + - > + > > - - - > + - > - - - - Sécurité diff --git a/app/Views/orders/create.php b/app/Views/orders/create.php index f17ae976..11f28638 100644 --- a/app/Views/orders/create.php +++ b/app/Views/orders/create.php @@ -273,7 +273,30 @@ '' + ''; - // Add new row in the table + // Gérer la visibilité du bouton "+" selon le type de client + function toggleAddRowButton() { + var customerType = $("#customer_type").val(); + var rowCount = $("#product_info_table tbody tr").length; + + if (customerType === 'particulier' && rowCount >= 1) { + $("#add_row").hide(); + } else { + $("#add_row").show(); + } + } + + // Écouter le changement de type de client + $("#customer_type").on('change', function () { + var customerType = $(this).val(); + if (customerType === 'particulier') { + // Supprimer toutes les lignes sauf la première + $("#product_info_table tbody tr:gt(0)").remove(); + subAmount(); + } + toggleAddRowButton(); + }); + + // Add new row in the table $("#add_row").unbind('click').bind('click', function () { var table = $("#product_info_table"); var count_table_tbody_tr = $("#product_info_table tbody tr").length; @@ -285,7 +308,6 @@ dataType: 'json', success: function (response) { - // console.log(reponse.x); var html = '' + '' + ' Imprimer - + Retour
    diff --git a/app/Views/securite/index.php b/app/Views/securite/index.php index 55b340eb..608ce0e5 100644 --- a/app/Views/securite/index.php +++ b/app/Views/securite/index.php @@ -186,6 +186,33 @@
    + +
    +
    +
    +
    +

    + Commandes en attente de livraison +

    +
    +
    + + + + + + + + + + + +
    ImageUGSDésignationStatutAction
    +
    +
    +
    +
    +
    @@ -266,6 +293,38 @@
    + + +