Browse Source

03122025

master
Sarobidy22 2 months ago
parent
commit
cb703c30f6
  1. 13
      app/Config/Routes.php
  2. 62
      app/Controllers/Auth.php
  3. 143
      app/Controllers/AvanceController.php
  4. 2
      app/Controllers/Dashboard.php
  5. 52
      app/Controllers/GroupController.php
  6. 35
      app/Controllers/MecanicienController.php
  7. 2
      app/Controllers/ProductCOntroller.php
  8. 18
      app/Controllers/ReportController.php
  9. 68
      app/Models/Avance.php
  10. 41
      app/Models/Groups.php
  11. 35
      app/Models/Mecanicien.php
  12. 137
      app/Models/Orders.php
  13. 92
      app/Views/avances/avance.php
  14. 1960
      app/Views/dashboard.php
  15. 14
      app/Views/groups/delete.php
  16. 252
      app/Views/groups/index.php
  17. 51
      app/Views/login.php
  18. 3
      app/Views/orders/createbyid.php
  19. 6
      app/Views/reports/stockDetail.php
  20. 2382
      app/Views/templates/header.php
  21. 68
      app/Views/templates/header_menu.php
  22. 251
      app/Views/users/create.php

13
app/Config/Routes.php

@ -166,6 +166,15 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
/**
* route for the products
*/
$routes->get('/product', function() {
return redirect()->to('/products');
});
// Route pour /product/(:any) qui redirige vers /products/(:any)
$routes->get('/product/(:any)', function($segment) {
return redirect()->to('/products/'.$segment);
});
$routes->group('/products', function ($routes) {
$routes->get('/', [ProductCOntroller::class, 'index']);
$routes->get('fetchProductData', [ProductCOntroller::class, 'fetchProductData']);
@ -174,11 +183,11 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('update/(:num)', [ProductCOntroller::class, 'update']);
$routes->post('update/(:num)', [ProductCOntroller::class, 'update']);
$routes->post('remove', [ProductCOntroller::class, 'remove']);
$routes->get('generateqrcode/(:num)', [QrCodeCOntroller::class, 'generate']);
$routes->post('assign_store', [ProductCOntroller::class, 'assign_store']);
$routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']);
$routes->post('checkProductAvailability', [ProductCOntroller::class, 'checkProductAvailability']);
});
/**
* route for the orders
@ -331,6 +340,8 @@ $routes->group('/avances', function ($routes) {
// Route CRON (optionnel)
$routes->get('checkDeadlineAlerts', [AvanceController::class, 'checkDeadlineAlerts']);
});
// historique

62
app/Controllers/Auth.php

@ -46,32 +46,46 @@ class Auth extends AdminController
return $file ? $file->getErrorString() : 'No file was uploaded.';
}
/**
* function used to login
* @return \CodeIgniter\HTTP\RedirectResponse
*/
public function loginPost()
{
$email = $this->request->getPost('email');
$password = $this->request->getPost('password');
// Load the model and attempt login
$userModel = new Users();
$user = $userModel->attempt($email, $password);
if ($user) {
// Set user session
session()->set('user', $user);
// Redirect to dashboard
return redirect()->to('/');
}
// If login fails, redirect back with an error
return redirect()->to('/login')->with('error', 'Invalid email or password.');
/**
* function used to login
* @return \CodeIgniter\HTTP\RedirectResponse
*/
public function loginPost()
{
$email = $this->request->getPost('email');
$password = $this->request->getPost('password');
// Load the model and attempt login
$userModel = new Users();
$user = $userModel->attempt($email, $password);
if ($user) {
// Ajouter le nom complet et le rôle dans le tableau $user
$user['username'] = trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? ''));
$user['role'] = $user['group_name'] ?? 'Aucun groupe';
// Set user session (garde la structure originale qui fonctionne)
session()->set('user', $user);
// Ajouter aussi les clés individuelles pour le header
session()->set([
'user_id' => $user['id'],
'username' => $user['username'],
'role' => $user['role'],
'email' => $user['email'] ?? '',
'group_id' => $user['group_id'] ?? null,
'store_id' => $user['store_id'] ?? null,
'permission' => $user['permission'] ?? '',
'logged_in' => true
]);
// Redirect to dashboard
return redirect()->to('/');
}
// If login fails, redirect back with an error
return redirect()->to('/login')->with('error', 'Invalid email or password.');
}
public function logout()
{
session()->destroy();

143
app/Controllers/AvanceController.php

@ -563,6 +563,7 @@ public function fetchExpiredAvance()
$type_avance = $this->request->getPost('type_avance_edit');
// ✅ VALIDATION
$validation = \Config\Services::validation();
$baseRules = [
@ -602,11 +603,11 @@ public function fetchExpiredAvance()
]);
}
// ✅ VÉRIFICATIONS PERMISSIONS
$isAdmin = $this->isAdmin($users);
$isOwner = $users['id'] === $existingAvance['user_id'];
$isCaissier = $this->isCaissier($users);
// ✅ MODIFIÉ : Le caissier peut maintenant modifier toutes les avances
if (!$isAdmin && !$isOwner && !$isCaissier) {
return $this->response->setJSON([
'success' => false,
@ -614,6 +615,7 @@ public function fetchExpiredAvance()
]);
}
// ✅ GESTION DEADLINE
$current_deadline = $existingAvance['deadline'];
if ($type_avance !== $existingAvance['type_avance']) {
@ -626,6 +628,7 @@ public function fetchExpiredAvance()
$old_product_id = $existingAvance['product_id'];
// ✅ PRÉPARER LES DONNÉES
$data = [
'type_avance' => $type_avance,
'type_payment' => $this->request->getPost('type_payment_edit'),
@ -638,7 +641,8 @@ public function fetchExpiredAvance()
'avance_amount' => (float)$this->request->getPost('avance_amount_edit'),
'amount_due' => (float)$this->request->getPost('amount_due_edit')
];
// ✅ GESTION PRODUIT SELON TYPE
if ($type_avance === 'mere') {
$data['product_name'] = $this->request->getPost('product_name_text_edit');
$data['product_id'] = null;
@ -651,22 +655,61 @@ public function fetchExpiredAvance()
$data['commentaire'] = null;
}
if ($Avance->updateAvance($avance_id, $data)) {
// ✅ MISE À JOUR (qui déclenchera automatiquement la conversion si nécessaire)
$updateResult = $Avance->updateAvance($avance_id, $data);
if ($updateResult) {
// ✅ GESTION DES PRODUITS
if ($type_avance === 'terre') {
// Libérer l'ancien produit si changement
if ($old_product_id && $old_product_id !== $new_product_id) {
$Products->update($old_product_id, ['product_sold' => 0]);
}
// Marquer le nouveau produit comme vendu
if ($new_product_id) {
$Products->update($new_product_id, ['product_sold' => 1]);
}
} else {
// Si passage de terre à mer, libérer le produit
if ($old_product_id && $existingAvance['type_avance'] === 'terre') {
$Products->update($old_product_id, ['product_sold' => 0]);
}
}
// ✅ Notification simplifiée
// ✅ VÉRIFIER SI CONVERSION AUTOMATIQUE
$updatedAvance = $Avance->find($avance_id);
if ($updatedAvance && $updatedAvance['is_order'] == 1) {
// ✅ L'avance a été convertie automatiquement !
// Trouver l'ID de la commande créée
$db = \Config\Database::connect();
$order = $db->table('orders')
->select('id, bill_no')
->where('customer_name', $updatedAvance['customer_name'])
->where('customer_phone', $updatedAvance['customer_phone'])
->where('source', 'Avance convertie')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if ($order) {
log_message('info', "✅ Avance {$avance_id} convertie automatiquement en commande {$order['id']}");
return $this->response->setJSON([
'success' => true,
'messages' => '✅ Avance modifiée et convertie automatiquement en commande !',
'converted' => true,
'order_id' => $order['id'],
'bill_no' => $order['bill_no'],
'redirect_url' => site_url('orders/update/' . $order['id'])
]);
}
}
// ✅ NOTIFICATION (modification simple)
$Notification->createNotification(
'Une avance a été modifiée',
"Caissière",
@ -685,7 +728,7 @@ public function fetchExpiredAvance()
'messages' => 'Erreur lors de la modification de l\'avance'
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur modification avance: " . $e->getMessage());
return $this->response->setJSON([
@ -694,7 +737,7 @@ public function fetchExpiredAvance()
]);
}
}
public function removeAvance()
{
@ -2131,7 +2174,7 @@ public function payAvance()
]);
}
// Vérifier si déjà convertie
// Vérifier si déjà convertie
if ($avance['is_order'] == 1) {
return $this->response->setJSON([
'success' => false,
@ -2139,59 +2182,68 @@ public function payAvance()
]);
}
// Calcul nouveau montant dû
// Calcul nouveau montant dû
$amount_due = max(0, (float)$avance['amount_due'] - $montant_paye);
// ✅ Mise à jour avance
$avanceModel->update($avance_id, [
// ✅ Utiliser updateAvance() qui va automatiquement vérifier la conversion
$updateResult = $avanceModel->updateAvance($avance_id, [
'avance_amount' => (float)$avance['avance_amount'] + $montant_paye,
'amount_due' => $amount_due,
]);
if (!$updateResult) {
return $this->response->setJSON([
'success' => false,
'message' => 'Erreur lors de la mise à jour'
]);
}
log_message('info', "💰 Paiement {$montant_paye} Ar sur avance {$avance_id} (Type: {$avance['type_avance']})");
// ✅ CONVERSION si paiement complet
if ($amount_due <= 0) {
// ✅ Vérifier si l'avance a été convertie automatiquement
$updatedAvance = $avanceModel->find($avance_id);
if ($updatedAvance && $updatedAvance['is_order'] == 1) {
// ✅ Conversion automatique effectuée !
if ($avance['type_avance'] === 'terre') {
log_message('info', "🔄 Avance TERRE {$avance_id} complétée → Conversion en commande...");
$order_id = $avanceModel->convertToOrder($avance_id);
if ($order_id) {
log_message('info', "✅ Conversion réussie → Commande {$order_id}");
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance a été convertie en commande.',
'converted' => true,
'type' => 'terre',
'order_id' => $order_id,
'redirect_url' => site_url('orders/update/' . $order_id)
]);
} else {
log_message('error', "❌ Échec conversion avance {$avance_id}");
return $this->response->setJSON([
'success' => false,
'message' => '⚠️ Paiement enregistré mais erreur lors de la conversion. Contactez l\'administrateur.'
]);
}
} else {
// ✅ Avance MER complète
log_message('info', "✅ Avance MER {$avance_id} complétée (pas de conversion)");
// Trouver la commande
$db = \Config\Database::connect();
$order = $db->table('orders')
->select('id, bill_no')
->where('customer_name', $updatedAvance['customer_name'])
->where('customer_phone', $updatedAvance['customer_phone'])
->where('source', 'Avance convertie')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if ($order) {
log_message('info', "✅ Avance {$avance_id} convertie automatiquement en commande {$order['id']} après paiement");
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance MER est maintenant complète.',
'converted' => false,
'type' => 'mere'
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance a été automatiquement convertie en commande.',
'converted' => true,
'type' => 'terre',
'order_id' => $order['id'],
'bill_no' => $order['bill_no'],
'redirect_url' => site_url('orders/update/' . $order['id'])
]);
}
}
// ✅ Paiement partiel
// Paiement partiel ou avance MER complète
if ($amount_due <= 0 && $avance['type_avance'] === 'mere') {
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement effectué ! L\'avance MER est maintenant complète.',
'converted' => false,
'type' => 'mere'
]);
}
// Paiement partiel
return $this->response->setJSON([
'success' => true,
'message' => '✅ Paiement partiel enregistré',
@ -2200,6 +2252,7 @@ public function payAvance()
'type' => $avance['type_avance']
]);
}
/**
* ✅ Conversion manuelle (optionnel - pour forcer la conversion)
*/

2
app/Controllers/Dashboard.php

@ -21,7 +21,7 @@ class Dashboard extends AdminController
public function index()
{
// === 🔥 Récupérer l'utilisateur en premier ===
// Récupérer l'utilisateur en premier
$session = session();
$user_id = $session->get('user');

52
app/Controllers/GroupController.php

@ -138,37 +138,35 @@ class GroupController extends AdminController
}
public function delete(int $id = null)
{
$this->verifyRole('deleteGroup');
$data['page_title'] = $this->pageTitle;
$groupsModel = new Groups();
{
$this->verifyRole('deleteGroup');
$data['page_title'] = $this->pageTitle;
$groupsModel = new Groups();
if ($id) {
if ($this->request->getMethod() === 'post' && $this->request->getPost('confirm')) {
// Check if the group exists in the user group
$check = $groupsModel->existInUserGroup($id);
if ($check) {
session()->setFlashdata('error', 'Group exists in the users');
return redirect()->to('/groups');
} else {
// Delete group
if ($groupsModel->delete($id)) {
session()->setFlashdata('success', 'Successfully removed');
return redirect()->to('/groups');
} else {
session()->setFlashdata('error', 'Error occurred!!');
return redirect()->to("/groups/delete/{$id}");
}
}
} else {
// Show confirmation view
$data['id'] = $id;
return $this->render_template('groups/delete', $data);
}
if (!$id) {
session()->setFlashdata('error', 'Invalid Group ID!');
return redirect()->to('/groups');
}
// Vérifier si c'est une requête POST avec confirmation
if ($this->request->getMethod() === 'post' && $this->request->getPost('confirm')) {
// Supprimer d'abord toutes les associations dans user_group
$groupsModel->removeUsersFromGroup($id);
// Puis supprimer le groupe
if ($groupsModel->deleteGroup($id)) {
session()->setFlashdata('success', 'Rôle supprimé avec succès');
return redirect()->to('/groups');
} else {
session()->setFlashdata('error', 'Invalid Group ID!');
session()->setFlashdata('error', 'Une erreur est survenue lors de la suppression!');
return redirect()->to('/groups');
}
}
// Si ce n'est pas une requête POST, rediriger vers la liste
session()->setFlashdata('error', 'Action non autorisée!');
return redirect()->to('/groups');
}
}

35
app/Controllers/MecanicienController.php

@ -84,7 +84,7 @@ class MecanicienController extends AdminController
}
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
$produit = $repa['sku'];
$produit = $repa['name'] . ' (' . $repa['sku'] . ')';
// Status display
$status = strReparation($repa['reparation_statut']);
$username = $repa['username'];
@ -313,13 +313,22 @@ class MecanicienController extends AdminController
$session = session();
$users = $session->get('user');
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRE
$startDate = $this->request->getGet('startDate');
$endDate = $this->request->getGet('endDate');
$pvente = $this->request->getGet('pvente');
// Log pour débogage
log_message('debug', 'Filtres Mécanicien reçus - startDate: ' . $startDate . ', endDate: ' . $endDate . ', pvente: ' . $pvente);
$data['id'] = $users['id'];
$reparation = $Mecanicien->getReparation($data['id']);
$result = ['data' => []];
// Iterate through the data
if($users['group_name'] == "SuperAdmin" || $users['group_name'] == "Direction"){
// ✅ PASSER LES FILTRES AU MODÈLE
$reparation = $Mecanicien->getReparationWithFilters($data['id'], $startDate, $endDate, $pvente);
$result = ['data' => []];
if($users['group_name'] == "SuperAdmin" || $users['group_name'] == "Direction" || $users['group_name'] == "DAF"){
foreach ($reparation as $key => $repa) {
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
$produit = esc($repa['name']);
@ -328,6 +337,7 @@ class MecanicienController extends AdminController
$user_name = $first_name . ' ' . $last_name;
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
// Add the row data
$result['data'][$key] = [
$user_name,
@ -338,17 +348,15 @@ class MecanicienController extends AdminController
$date_fin,
];
}
return $this->response->setJSON($result);
}
else{
} else {
foreach ($reparation as $key => $repa) {
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
$produit = $repa['name'];
// Status display
$username = $repa['username'];
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
// Add the row data
$result['data'][$key] = [
$image,
@ -358,11 +366,8 @@ class MecanicienController extends AdminController
$date_fin,
];
}
// Return data in JSON format
return $this->response->setJSON($result);
}
// Iterate through the data
return $this->response->setJSON($result);
}
}

2
app/Controllers/ProductCOntroller.php

@ -247,7 +247,7 @@ class ProductCOntroller extends AdminController
"Un nouveau Produit a été créé",
"COMMERCIALE",
$store_id1,
'product/'
'products/'
);
return redirect()->to('/products');

18
app/Controllers/ReportController.php

@ -261,9 +261,18 @@ class ReportController extends AdminController
$session = session();
$users = $session->get('user');
// Pour Direction et Conseil : afficher TOUTES les performances
if ($users['group_name'] === "DAF" || $users['group_name'] === "Direction" ||$users['group_name'] === "SuperAdmin" ) {
$orderPaid = $Orders->getPerformanceByOrders();
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRE
$startDate = $this->request->getGet('startDate');
$endDate = $this->request->getGet('endDate');
$pvente = $this->request->getGet('pvente');
// ✅ CORRECTION : Bonne concaténation des chaînes
log_message('debug', 'Filtres Commercial reçus - startDate: ' . $startDate . ', endDate: ' . $endDate . ', pvente: ' . $pvente);
// Pour Direction et Conseil : afficher TOUTES les performances AVEC FILTRES
if ($users['group_name'] === "DAF" || $users['group_name'] === "Direction" || $users['group_name'] === "SuperAdmin") {
// ✅ PASSER LES FILTRES AU MODÈLE - UNIQUEMENT POUR L'ADMIN
$orderPaid = $Orders->getPerformanceByOrders($startDate, $endDate, $pvente);
foreach ($orderPaid as $key => $value) {
// Déterminer le prix de vente réel
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
@ -287,6 +296,7 @@ class ReportController extends AdminController
return $this->response->setJSON($result);
}
// ✅ POUR LES AUTRES RÔLES, GARDER L'ANCIENNE LOGIQUE SANS FILTRES
// Pour Cheffe d'Agence : performances de son magasin
if ($users['group_name'] === "Cheffe d'Agence") {
$orderPaid = $Orders->getPerformanceByOrders1();
@ -305,6 +315,7 @@ class ReportController extends AdminController
}
return $this->response->setJSON($result);
}
if ($users['group_name'] === "Caissière") {
$orderPaid = $Orders->getPerformanceByCaissier($users['id']);
@ -325,6 +336,7 @@ class ReportController extends AdminController
return $this->response->setJSON($result);
}
// Pour COMMERCIALE : uniquement ses propres ventes
if ($users['group_name'] === "COMMERCIALE") {
$orderPaid = $Orders->getPerformanceByOrders2();

68
app/Models/Avance.php

@ -40,6 +40,7 @@ class Avance extends Model {
log_message('error', 'ID invalide pour la mise à jour du recouvrement : ' . $id);
return false;
}
try {
if (!empty($data['type']) && !empty($data['avance_date'])) {
if (strtolower($data['type']) === 'avance sur terre') {
@ -48,12 +49,77 @@ class Avance extends Model {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +2 months'));
}
}
return $this->update($id, $data);
// ✅ Mettre à jour l'avance
$updateResult = $this->update($id, $data);
if (!$updateResult) {
return false;
}
// ✅ AJOUT CRITIQUE : Vérifier automatiquement si conversion nécessaire
$this->autoCheckAndConvert($id);
return true;
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la mise à jour de l\'avance : ' . $e->getMessage());
return false;
}
}
private function autoCheckAndConvert(int $avance_id)
{
try {
// Recharger l'avance fraîchement mise à jour
$avance = $this->find($avance_id);
if (!$avance) {
log_message('warning', "⚠️ Avance {$avance_id} introuvable pour vérification auto");
return false;
}
// ✅ Conditions de conversion automatique
$shouldConvert = (
$avance['type_avance'] === 'terre' && // C'est une avance sur terre
(float)$avance['amount_due'] <= 0.01 && // Montant dû = 0 (avec tolérance)
$avance['is_order'] == 0 && // Pas encore convertie
$avance['active'] == 1 // Encore active
);
if ($shouldConvert) {
log_message('info', "🔄 [AUTO-CHECK] Avance {$avance_id} complète détectée ! Conversion automatique...");
// ✅ Appeler la conversion
$order_id = $this->convertToOrder($avance_id);
if ($order_id) {
log_message('info', "✅ [AUTO-CHECK] Conversion réussie → Commande {$order_id}");
return $order_id;
} else {
log_message('error', "❌ [AUTO-CHECK] Échec conversion avance {$avance_id}");
return false;
}
} else {
// Log pour débogage
$reason = '';
if ($avance['type_avance'] !== 'terre') $reason .= 'type=' . $avance['type_avance'] . ' ';
if ((float)$avance['amount_due'] > 0.01) $reason .= 'reste=' . $avance['amount_due'] . ' ';
if ($avance['is_order'] == 1) $reason .= 'déjà_convertie ';
if ($avance['active'] == 0) $reason .= 'inactive ';
if (!empty($reason)) {
log_message('info', "ℹ️ [AUTO-CHECK] Avance {$avance_id} non éligible pour conversion : {$reason}");
}
}
return false;
} catch (\Exception $e) {
log_message('error', "❌ [AUTO-CHECK] Erreur vérification avance {$avance_id}: " . $e->getMessage());
return false;
}
}
public function getAllAvanceData(int $id=null) {
$session = session();

41
app/Models/Groups.php

@ -1,7 +1,5 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class Groups extends Model
@ -11,10 +9,9 @@ class Groups extends Model
* @var string
*/
protected $table = 'groups';
protected $primaryKey = 'id'; // Primary key of your table
protected $allowedFields = ['group_name', 'permission']; // Fields allowed for insert/update
protected $useTimestamps = false; // Set to true if your table has `created_at` and `updated_at` columns
protected $primaryKey = 'id';
protected $allowedFields = ['group_name', 'permission'];
protected $useTimestamps = false;
/**
* Get group data by groupId or all (excluding id = 1)
@ -24,10 +21,9 @@ class Groups extends Model
public function getGroupData($groupId = null)
{
if ($groupId) {
return $this->find($groupId); // Find by id
return $this->find($groupId);
}
return $this->where('id !=', 1)->findAll(); // Get all groups except where id = 1
return $this->where('id !=', 1)->findAll();
}
/**
@ -37,7 +33,7 @@ class Groups extends Model
*/
public function createGroup($data)
{
return $this->insert($data); // Insert data into the groups table
return $this->insert($data);
}
/**
@ -48,17 +44,17 @@ class Groups extends Model
*/
public function editGroup($data, $id)
{
return $this->update($id, $data); // Update group by id
return $this->update($id, $data);
}
/**
* Delete group by id
* @param mixed $id
* @return bool|\CodeIgniter\Database\BaseResult
* @return bool
*/
public function deleteGroup($id)
{
return $this->delete($id); // Delete group by id
return $this->delete($id);
}
/**
@ -71,6 +67,25 @@ class Groups extends Model
return $this->db->table('user_group')->where('group_id', $id)->countAllResults() > 0;
}
/**
* Count users in a specific group
* @param mixed $id
* @return int
*/
public function countUsersInGroup($id)
{
return $this->db->table('user_group')->where('group_id', $id)->countAllResults();
}
/**
* Remove all users from a specific group
* @param mixed $id
* @return bool
*/
public function removeUsersFromGroup($id)
{
return $this->db->table('user_group')->where('group_id', $id)->delete();
}
/**
* Get user group by userId
* @param mixed $userId

35
app/Models/Mecanicien.php

@ -75,5 +75,38 @@ class Mecanicien extends Model
->get()
->getRow();
}
public function getReparationWithFilters(int $id = null, $startDate = null, $endDate = null, $pvente = null)
{
$session = session();
$user = $session->get('user');
$builder = $this->select('reparations.reparation_id as reparationsID, reparations.user_id, reparations.reparation_statut, reparations.produit_id, reparations.reparation_observation, reparations.reparation_debut, reparations.reparation_fin, users.*, products.*, stores.name as store_name')
->join('users', 'reparations.user_id = users.id')
->join('products', 'reparations.produit_id = products.id')
->join('stores', 'products.store_id = stores.id', 'left'); // ✅ JOINTURE AVEC MAGASINS
// Filtre par utilisateur si pas admin
if ($user['group_name'] != "SuperAdmin" && $user['group_name'] != "Direction" && $user['group_name'] != "DAF") {
$builder->where('users.id', $id);
}
// ✅ APPLIQUER LES FILTRES PAR DATE
if (!empty($startDate) && !empty($endDate)) {
$builder->where('DATE(reparations.reparation_debut) >=', $startDate);
$builder->where('DATE(reparations.reparation_debut) <=', $endDate);
} elseif (!empty($startDate)) {
$builder->where('DATE(reparations.reparation_debut) >=', $startDate);
} elseif (!empty($endDate)) {
$builder->where('DATE(reparations.reparation_debut) <=', $endDate);
}
// ✅ FILTRE PAR POINT DE VENTE
if (!empty($pvente) && $pvente !== 'TOUS') {
$builder->where('stores.name', $pvente);
}
$builder->orderBy('reparations.reparation_debut', 'DESC');
return $builder->findAll();
}
}

137
app/Models/Orders.php

@ -110,8 +110,8 @@ class Orders extends Model
'orders.customer_phone',
'orders.customer_cin',
'orders.customer_address',
'orders.customer_type', // ✅ DÉJÀ PRÉSENT
'orders.source', // ✅ DÉJÀ PRÉSENT
'orders.customer_type',
'orders.source',
'orders.discount',
'orders.date_time',
'orders.gross_amount',
@ -367,12 +367,12 @@ class Orders extends Model
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
if($isAdmin) {
return $this->whereIn('paid_status', [1, 3]) // ← Ajoutez 3 ici
return $this->whereIn('paid_status', [1, 3]) // ✅ DÉJÀ BON !
->orderBy('id', 'DESC')
->findAll();
}
else {
return $this->whereIn('paid_status', [1, 3]) // ← Ajoutez 3 ici
return $this->whereIn('paid_status', [1, 3]) // ✅ DÉJÀ BON !
->where('store_id', $users['store_id'])
->orderBy('id', 'DESC')
->findAll();
@ -598,19 +598,34 @@ class Orders extends Model
return $order;
}
public function getPerformanceByOrders()
public function getPerformanceByOrders($startDate = null, $endDate = null, $pvente = null)
{
$Performance = $this->db->table('orders')
$builder = $this->db->table('orders')
->select('orders.id as orderid, orders.net_amount, orders.date_time as datevente, orders.discount, products.price, products.sku, products.prix_vente, products.name as motoname, stores.id as store_id, users.firstname, users.lastname, users.email')
->join('stores', 'orders.store_id = stores.id')
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'products.id = orders_item.product_id')
->join('users', 'users.id = orders.user_id')
->whereIn('orders.paid_status', [1, 3])
->get()
->getResultArray();
return $Performance;
->whereIn('orders.paid_status', [1, 3]);
// ✅ AJOUT : FILTRES PAR DATE
if (!empty($startDate) && !empty($endDate)) {
$builder->where('DATE(orders.date_time) >=', $startDate);
$builder->where('DATE(orders.date_time) <=', $endDate);
} elseif (!empty($startDate)) {
$builder->where('DATE(orders.date_time) >=', $startDate);
} elseif (!empty($endDate)) {
$builder->where('DATE(orders.date_time) <=', $endDate);
}
// ✅ AJOUT : FILTRE PAR POINT DE VENTE
if (!empty($pvente) && $pvente !== 'TOUS') {
$builder->where('stores.name', $pvente);
}
$builder->orderBy('orders.date_time', 'DESC');
return $builder->get()->getResultArray();
}
// for Adminan cheffe d'agence
@ -692,33 +707,46 @@ class Orders extends Model
public function getUserPerformanceToday(string $date)
{
$session = session();
$currentUser = $session->get('user');
$isAdmin = in_array($currentUser['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$users = $this->getUserList();
$results = [];
foreach ($users as $user) {
$summary = $this->db->table('orders')
$builder = $this->db->table('orders')
->select('COUNT(id) AS total_user_order, SUM(net_amount) AS total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time)', $date)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$builder->where('store_id', $currentUser['store_id']);
}
$summary = $builder->get()->getRow();
$ids = $this->db->table('orders')
$idsBuilder = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time)', $date)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$idsBuilder->where('store_id', $currentUser['store_id']);
}
$ids = $idsBuilder->get()->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
$results[] = [
'user_id' => $user->user_id,
'full_name' => $user->full_name,
'total_user_order' => $summary->total_user_order,
'total_prix_vente' => $summary->total_prix_vente,
'total_user_order' => $summary->total_user_order ?? 0,
'total_prix_vente' => $summary->total_prix_vente ?? 0,
'order_ids' => $orderIds,
];
}
@ -728,6 +756,10 @@ class Orders extends Model
public function getUserPerformanceByWeek(string $date = null)
{
$session = session();
$currentUser = $session->get('user');
$isAdmin = in_array($currentUser['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$users = $this->getUserList();
$results = [];
@ -740,24 +772,33 @@ class Orders extends Model
$endOfWeek = date('Y-m-d', strtotime('sunday this week', $timestamp));
foreach ($users as $user) {
$summary = $this->db->table('orders')
$builder = $this->db->table('orders')
->select('COUNT(id) AS total_user_order, SUM(net_amount) AS total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfWeek)
->where('DATE(date_time) <=', $endOfWeek)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$builder->where('store_id', $currentUser['store_id']);
}
$summary = $builder->get()->getRow();
$ids = $this->db->table('orders')
$idsBuilder = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfWeek)
->where('DATE(date_time) <=', $endOfWeek)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$idsBuilder->where('store_id', $currentUser['store_id']);
}
$ids = $idsBuilder->get()->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
$results[] = [
@ -771,7 +812,6 @@ class Orders extends Model
return $results;
}
public function checkMinimalPrice($product_id, $discount)
{
$fourchetteModel = new FourchettePrix(); // plus court car on a "use"
@ -787,6 +827,10 @@ public function checkMinimalPrice($product_id, $discount)
public function getUserPerformanceByMonth(string $month)
{
$session = session();
$currentUser = $session->get('user');
$isAdmin = in_array($currentUser['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$startOfMonth = date('Y-m-01', strtotime($month . '-01'));
$endOfMonth = date('Y-m-t', strtotime($month . '-01'));
@ -794,24 +838,33 @@ public function getUserPerformanceByMonth(string $month)
$results = [];
foreach ($users as $user) {
$orderData = $this->db->table('orders')
$builder = $this->db->table('orders')
->select('COUNT(id) as total_user_order, SUM(net_amount) as total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfMonth)
->where('DATE(date_time) <=', $endOfMonth)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$builder->where('store_id', $currentUser['store_id']);
}
$orderData = $builder->get()->getRow();
$ids = $this->db->table('orders')
$idsBuilder = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfMonth)
->where('DATE(date_time) <=', $endOfMonth)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
->whereIn('paid_status', [1, 3]);
// ✅ Filtre par magasin si pas admin
if (!$isAdmin && !empty($currentUser['store_id']) && $currentUser['store_id'] != 0) {
$idsBuilder->where('store_id', $currentUser['store_id']);
}
$ids = $idsBuilder->get()->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
$results[] = [
@ -866,4 +919,6 @@ public function getPerformanceByCaissier(int $caissierId, $startDate = null, $en
->getResultArray();
}
}

92
app/Views/avances/avance.php

@ -1205,7 +1205,7 @@ function editFunc(id) {
});
}
// ✅ AJOUTER cette nouvelle fonction juste après editFunc
// ✅ FONCTION AMÉLIORÉE pour remplir le modal d'édition
function populateEditModal(r, id) {
$('#avance_id_edit').val(r.avance_id || r.id || id);
@ -1228,24 +1228,19 @@ function populateEditModal(r, id) {
// ✅ Gérer le produit selon le type
if (r.type_avance === 'mere') {
// Pour avance MER : utiliser product_name (texte libre)
var productNameMer = r.product_name || '';
$('#product_name_text_edit').val(productNameMer);
} else if (r.type_avance === 'terre') {
// Pour avance TERRE : utiliser product_id (select)
var productId = r.product_id || r.id_product;
// ✅ Si le produit existe dans le select, le sélectionner
if (productId) {
setTimeout(function() {
$('#id_product_edit').val(productId);
// ✅ Vérifier si le produit existe dans le select
if ($('#id_product_edit option:selected').val() == productId) {
console.log('Produit trouvé et sélectionné:', productId);
} else {
console.warn('Produit non trouvé dans le select:', productId);
// ✅ Optionnel : Ajouter l'option si elle n'existe pas
var productNameFromDB = r.product_name_db || 'Produit #' + productId;
$('#id_product_edit').append(
$('<option></option>')
@ -1266,7 +1261,7 @@ function populateEditModal(r, id) {
$(this).off('shown.bs.modal');
});
// ✅ Gestionnaire de soumission (reste identique)
// ✅ GESTIONNAIRE DE SOUMISSION AMÉLIORÉ AVEC CONVERSION AUTOMATIQUE
$('#update_avance_form').off('submit').on('submit', function(e) {
e.preventDefault();
@ -1276,6 +1271,7 @@ function populateEditModal(r, id) {
var avance = unformatNumber($('#avance_amount_edit').val());
var brut = unformatNumber($('#gross_amount_edit').val());
// ✅ VALIDATION pour avance sur terre
if (typeAvance === 'terre') {
var minAvance = brutEdit * 0.5;
if (avance < minAvance) {
@ -1290,6 +1286,7 @@ function populateEditModal(r, id) {
}
}
// ✅ VALIDATION pour avance sur mer
if (typeAvance === 'mere') {
if (!$('#product_name_text_edit').val() || brut === 0 || avance === 0) {
Swal.fire({
@ -1318,15 +1315,38 @@ function populateEditModal(r, id) {
success: function(res) {
if (res.success === true) {
$('#updateModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Succès',
text: res.messages,
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
// ✅ NOUVEAU : Gestion de la conversion automatique
if (res.converted === true && res.redirect_url) {
Swal.fire({
icon: 'success',
title: '🎉 Conversion automatique !',
html: '<div style="text-align: left; padding: 10px;">' +
'<p style="margin: 10px 0;"><strong>✅ Avance modifiée avec succès !</strong></p>' +
'<p style="margin: 10px 0;">🔄 L\'avance sur terre étant complète, elle a été <strong>automatiquement convertie en commande</strong>.</p>' +
'<hr style="margin: 15px 0; border: none; border-top: 1px solid #ddd;">' +
'<p style="margin: 10px 0;"><strong>N° Commande :</strong> ' + (res.bill_no || 'N/A') + '</p>' +
'<p style="margin: 15px 0 5px 0; color: #666; font-style: italic;">Vous allez être redirigé vers la commande...</p>' +
'</div>',
timer: 4000,
timerProgressBar: true,
showConfirmButton: false,
allowOutsideClick: false
}).then(() => {
window.location.href = res.redirect_url;
});
} else {
// ✅ Modification simple sans conversion
Swal.fire({
icon: 'success',
title: 'Succès',
text: res.messages,
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
}
} else {
Swal.fire({
icon: 'error',
@ -1335,7 +1355,8 @@ function populateEditModal(r, id) {
});
}
},
error: function() {
error: function(xhr, status, error) {
console.error('Erreur AJAX:', error);
Swal.fire({
icon: 'error',
title: 'Erreur de connexion',
@ -1475,42 +1496,5 @@ $(document).ready(function() {
</script>
<style>
/* ✅ STYLES POUR LE BOUTON */
#btnProcessExpired {
transition: all 0.3s ease;
}
#btnProcessExpired:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.swal2-container .table {
margin: 0 auto;
text-align: left;
}
.swal2-container .table th {
background-color: #f8f9fa;
font-weight: bold;
text-align: center;
}
.swal2-container .table td {
vertical-align: middle;
}
/* Style pour les inputs formatés */
input.formatted-number {
text-align: right;
font-family: 'Courier New', monospace;
letter-spacing: 1px;
}
/* Style pour les cellules de tableau formatées */
.table td.number-cell {
text-align: right;
font-family: 'Courier New', monospace;
font-weight: bold;
}
</style>

1960
app/Views/dashboard.php

File diff suppressed because it is too large

14
app/Views/groups/delete.php

@ -1,7 +1,7 @@
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- <div class="content-wrapper"> -->
<!-- Content Header (Page header) -->
<section class="content-header">
<!-- <section class="content-header">
<h1>
Gérer les
<small>Roles</small>
@ -10,11 +10,11 @@
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Roles</li>
</ol>
</section>
</section> -->
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<!-- <div class="row">
<div class="col-md-12 col-xs-12">
<?php if (session()->getFlashdata('success')): ?>
@ -36,7 +36,7 @@
<a href="<?php echo base_url('groups') ?>" class="btn btn-warning">Retour</a>
</form>
</div>
</div> -->
<!-- col-md-12 -->
</div>
<!-- /.row -->
@ -47,10 +47,10 @@
</div>
<!-- /.content-wrapper -->
<!--
<script type="text/javascript">
$(document).ready(function() {
$("#mainUserNav").addClass('active');
$("#manageGroupNav").addClass('active');
});
</script>
</script> -->

252
app/Views/groups/index.php

@ -1,125 +1,155 @@
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Gérer les
<small>Rôles</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Rôles</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-12 col-xs-12">
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Gérer les
<small>Rôles</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Rôles</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-12 col-xs-12">
<?php if(session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('success'); ?>
</div>
<?php elseif(session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<?php if(session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('success'); ?>
</div>
<?php elseif(session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<?php if(in_array('createGroup', $user_permission)): ?>
<a href="<?php echo base_url('groups/create') ?>" class="btn btn-primary">Ajouter un Rôle</a>
<br /> <br />
<?php endif; ?>
<?php if(in_array('createGroup', $user_permission)): ?>
<a href="<?php echo base_url('groups/create') ?>" class="btn btn-primary">Ajouter un Rôle</a>
<br /> <br />
<?php endif; ?>
<div class="box">
<div class="box-header">
<h3 class="box-title">Gérer les Rôles</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<table id="groupTable" class="table table-bordered table-striped">
<thead>
<tr>
<th>Désignation</th>
<?php if(in_array('updateGroup', $user_permission) || in_array('deleteGroup', $user_permission)): ?>
<th>Action</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php if($groups_data): ?>
<?php foreach ($groups_data as $k => $v): ?>
<tr>
<td><?php echo $v['group_name']; ?></td>
<div class="box">
<div class="box-header">
<h3 class="box-title">Gérer les Rôles</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<table id="groupTable" class="table table-bordered table-striped">
<thead>
<tr>
<th>Désignation</th>
<?php if(in_array('updateGroup', $user_permission) || in_array('deleteGroup', $user_permission)): ?>
<th>Action</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
<?php if($groups_data): ?>
<?php foreach ($groups_data as $k => $v): ?>
<tr>
<td><?php echo $v['group_name']; ?></td>
<?php if(in_array('updateGroup', $user_permission) || in_array('deleteGroup', $user_permission)): ?>
<td>
<?php if(in_array('updateGroup', $user_permission)): ?>
<a href="<?php echo base_url('groups/edit/'.$v['id']) ?>" class="btn btn-default"><i class="fa fa-edit"></i></a>
<?php endif; ?>
<?php if(in_array('deleteGroup', $user_permission)): ?>
<a href="<?php echo base_url('groups/delete/'.$v['id']) ?>" class="btn btn-danger"><i class="fa fa-trash"></i></a>
<?php endif; ?>
</td>
<?php if(in_array('updateGroup', $user_permission) || in_array('deleteGroup', $user_permission)): ?>
<td>
<?php if(in_array('updateGroup', $user_permission)): ?>
<a href="<?php echo base_url('groups/edit/'.$v['id']) ?>" class="btn btn-default"><i class="fa fa-edit"></i></a>
<?php endif; ?>
<?php if(in_array('deleteGroup', $user_permission)): ?>
<button type="button" class="btn btn-danger" onclick="confirmDelete(<?php echo $v['id']; ?>, '<?php echo addslashes($v['group_name']); ?>')">
<i class="fa fa-trash"></i>
</button>
<?php endif; ?>
</tr>
<?php endforeach ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- /.box-body -->
</td>
<?php endif; ?>
</tr>
<?php endforeach ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- /.box -->
<!-- /.box-body -->
</div>
<!-- col-md-12 -->
<!-- /.box -->
</div>
<!-- /.row -->
<!-- col-md-12 -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</section>
<!-- /.content -->
<!-- Modal de confirmation de suppression -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="deleteModalLabel">Confirmation de suppression</h4>
</div>
<div class="modal-body">
<p>Voulez-vous vraiment supprimer le rôle <strong id="roleName"></strong> ?</p>
</div>
<div class="modal-footer">
<form id="deleteForm" method="post" style="display: inline;">
<button type="button" class="btn btn-warning" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-danger" name="confirm" value="Oui">
<i class="fa fa-trash"></i> Oui, supprimer
</button>
</form>
</div>
</div>
</div>
<!-- /.content-wrapper -->
</div>
<script type="text/javascript">
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
<script type="text/javascript">
$(document).ready(function() {
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
sSearch: "Rechercher&nbsp;:",
sLengthMenu: "Afficher _MENU_ &eacute;l&eacute;ments",
sInfo: "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
sInfoEmpty: "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ment",
sInfoFiltered: "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
sLoadingRecords: "Chargement en cours...",
sZeroRecords: "Aucun &eacute;l&eacute;ment &agrave; afficher",
sEmptyTable: "Aucune donn&eacute;e disponible dans le tableau",
oPaginate: {
sFirst: "Premier",
sPrevious: "Pr&eacute;c&eacute;dent",
sNext: "Suivant",
sLast: "Dernier"
},
oAria: {
sSortAscending: ": activer pour trier la colonne par ordre croissant",
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
}
}
});
$('#groupTable').DataTable();
$('#groupTable').DataTable();
$("#mainUserNav").addClass('active');
$("#manageGroupNav").addClass('active');
});
</script>
$("#mainUserNav").addClass('active');
$("#manageGroupNav").addClass('active');
});
// Fonction pour ouvrir le modal de confirmation
function confirmDelete(id, name) {
$('#roleName').text(name);
$('#deleteForm').attr('action', '<?php echo base_url('groups/delete/'); ?>' + id);
$('#deleteModal').modal('show');
}
</script>

51
app/Views/login.php

@ -34,7 +34,24 @@
overflow: hidden;
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
animation: fadeIn 0.8s ease;
position: relative;
backdrop-filter: blur(8px);
}
.login-card::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(135deg, #ffffffaa, #e0e7ffaa, #dbeafeaa);
background-size: 300% 300%;
animation: gradientFlow 10s ease infinite;
}
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Partie gauche : image moto */
.login-image {
@ -243,10 +260,43 @@
font-size: 3em;
}
}
/* Fond de vagues animées */
.animated-bg {
position: fixed;
inset: 0;
background: linear-gradient(to top, #cce7ff, #f8f9fa); /* bleu clair + blanc */
overflow: hidden;
z-index: -1;
}
/* Vague SVG */
.wave {
position: absolute;
bottom: 0;
left: 0;
width: 200%;
height: 100%;
background: url('data:image/svg+xml;utf8,\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">\
<path fill="%23cce7ff" fill-opacity="0.5" d="M0,160L60,165.3C120,171,240,181,360,176C480,171,600,149,720,138.7C840,128,960,128,1080,138.7C1200,149,1320,171,1380,181.3L1440,192L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path>\
</svg>') repeat-x;
animation: waveMove 12s linear infinite;
}
@keyframes waveMove {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
</style>
</head>
<body>
<div class="animated-bg">
<div class="wave"></div>
</div>
<!-- Overlay de chargement -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
@ -257,6 +307,7 @@
</div>
</div>
<div class="login-card">
<!-- Image + message -->

3
app/Views/orders/createbyid.php

@ -144,7 +144,6 @@
</div>
<br /> <br />
<!-- ✅ TABLEAU AVEC COLONNE PUISSANCE -->
<table class="table table-bordered" id="product_info_table">
<thead>
<tr>
@ -167,7 +166,7 @@
</select>
</td>
<!-- ✅ COLONNE PUISSANCE -->
<td>
<input type="number" name="puissance[]" id="puissance_1"
class="form-control" placeholder="CC"

6
app/Views/reports/stockDetail.php

@ -84,7 +84,7 @@
<div class="row mt-4">
<div class="col-md-12 col-lg-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-success text-white">
<div class="card-header bg-white text-primary font-weight-bold">
<h3 class="card-title m-0">🛒 Détails des produits vendus</h3>
</div>
<div class="card-body">
@ -103,7 +103,7 @@
<div class="col-md-12 col-lg-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-warning text-dark">
<div class="card-header bg-white text-primary font-weight-bold">
<h3 class="card-title m-0">📦 Détails des produits en stock</h3>
</div>
<div class="card-body">
@ -123,7 +123,7 @@
<div class="col-md-12 col-lg-12">
<div class="card shadow-sm border-0 rounded-3">
<div
class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
class="card-header bg-white text-primary font-weight-bold">
<h3 class="card-title m-0">📦 Exportation des produits vendus</h3>
</div>
<div class="card-body">

2382
app/Views/templates/header.php

File diff suppressed because it is too large

68
app/Views/templates/header_menu.php

@ -12,8 +12,10 @@
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Notifications -->
<ul class="navbar-nav" style="position: absolute; right: 15px; top: 50%; transform: translateY(-50%); margin: 0; padding: 0; list-style: none;">
<!-- Navbar Right Menu -->
<ul class="navbar-nav" style="position: absolute; right: 15px; top: 50%; transform: translateY(-50%); margin: 0; padding: 0; list-style: none; display: flex; align-items: center; gap: 20px;">
<!-- Notifications -->
<li class="nav-item dropdown" style="position: relative;">
<i class="fa fa-bell" id="notificationIcon" style="font-size: 20px; cursor: pointer; color:white;" data-toggle="dropdown"></i>
<span id="notificationCount" class="badge badge-warning navbar-badge"></span>
@ -30,6 +32,27 @@
<div class="dropdown-divider"></div>
</div>
</li>
<!-- User Info (sans dropdown) -->
<li class="nav-item" style="position: relative;">
<div style="color: white; display: flex; align-items: center; gap: 10px; padding: 5px 10px; background: rgba(255,255,255,0.1); border-radius: 20px;">
<i class="fa fa-user-circle" style="font-size: 24px;"></i>
<div style="display: flex; flex-direction: column; line-height: 1.2;">
<span style="font-size: 13px; font-weight: 600;">
<?php
$username = session()->get('username');
echo $username ? esc($username) : 'Utilisateur';
?>
</span>
<span style="font-size: 11px; opacity: 0.8;">
<?php
$role = session()->get('role');
echo $role ? esc($role) : 'Aucun groupe';
?>
</span>
</div>
</div>
</li>
</ul>
</nav>
</header>
@ -39,15 +62,13 @@
/* Notifications non lues */
.notification_item.unread {
font-weight: bold;
background-color: #f0f8ff; /* bleu clair */
background-color: #f0f8ff;
}
/* Icône bleue pour notifications non lues */
.icon-unread {
color: #007bff; /* bleu */
color: #007bff;
}
/* Style du bouton */
#markAllAsReadBtn {
white-space: nowrap;
}
@ -55,12 +76,33 @@
#markAllAsReadBtn:hover {
background-color: #0056b3;
}
/* Style pour le dropdown utilisateur */
.dropdown-item {
transition: background-color 0.2s;
}
.dropdown-item:hover {
background-color: #f5f5f5;
text-decoration: none;
}
.navbar-badge {
position: absolute;
top: -8px;
right: -8px;
padding: 3px 6px;
border-radius: 10px;
background: #f39c12;
color: white;
font-size: 10px;
}
</style>
<script>
function fetchNotifications() {
$.ajax({
url: '/notifications', // route NotificationController::getNotification
url: '/notifications',
type: 'GET',
dataType: 'json',
success: function(data) {
@ -68,15 +110,12 @@ function fetchNotifications() {
let notificationHTML = '';
data.forEach(notif => {
// Compter les notifications non lues
if (notif.is_read == 0) {
notificationCount++;
}
const href = '/' + notif.link.replace(/^\/+/, '');
const notifClass = notif.is_read == 0 ? "notification_item unread" : "notification_item";
// Icône uniquement pour non lu
const iconHTML = notif.is_read == 0 ? '<i class="fa fa-exclamation-circle icon-unread mr-9"></i>' : '';
notificationHTML += `
@ -93,11 +132,9 @@ function fetchNotifications() {
`;
});
// Injecter les notifications
$('#notificationList').html(notificationHTML);
$('#notificationHeader').text(data.length + ' Notifications');
// Badge pour non lues
if (notificationCount > 0) {
$('#notificationCount').text(notificationCount).show();
$('#markAllAsReadBtn').show();
@ -106,7 +143,6 @@ function fetchNotifications() {
$('#markAllAsReadBtn').hide();
}
// Ajouter l'événement clic pour marquer comme lu
const items = document.querySelectorAll('.notification_item');
items.forEach(item => {
item.addEventListener('click', () => {
@ -118,7 +154,6 @@ function fetchNotifications() {
.then(response => response.json())
.then(data => {
console.log("Notification marked as read:", data);
// l'icône disparaîtra au prochain fetch
})
.catch(error => console.error("Error:", error));
});
@ -131,7 +166,6 @@ function fetchNotifications() {
});
}
// Fonction pour marquer toutes les notifications comme lues
function markAllAsRead() {
fetch("<?= base_url('notifications/markAllAsRead') ?>", {
method: "POST",
@ -140,21 +174,17 @@ function markAllAsRead() {
.then(response => response.json())
.then(data => {
console.log("All notifications marked as read:", data);
// Rafraîchir les notifications immédiatement
fetchNotifications();
})
.catch(error => console.error("Error:", error));
}
// Attacher l'événement au bouton
$(document).on('click', '#markAllAsReadBtn', function(e) {
e.preventDefault();
e.stopPropagation();
markAllAsRead();
});
// Rafraîchir toutes les 10 secondes
setInterval(fetchNotifications, 10000);
// Premier chargement
fetchNotifications();
</script>

251
app/Views/users/create.php

@ -1,143 +1,135 @@
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Gérer les
<small>Utilisateurs</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Utilisateurs</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-12 col-xs-12">
<?php if(session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('success'); ?>
</div>
<?php elseif(session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<div class="box">
<div class="box-header">
<h3 class="box-title">Ajouter un utilisateur</h3>
</div>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Gérer les
<small>Utilisateurs</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Utilisateurs</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-12 col-xs-12">
<?php if(session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('success'); ?>
</div>
<?php elseif(session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<form role="form" action="<?php base_url('users/create') ?>" method="post">
<div class="box-body">
<div class="box">
<div class="box-header">
<h3 class="box-title">Ajouter un utilisateur</h3>
</div>
<form role="form" action="<?php base_url('users/create') ?>" method="post">
<div class="box-body">
<?php if (isset($validation)): ?>
<div class="alert alert-danger">
<ul>
<?php foreach ($validation->getErrors() as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php if (isset($validation)): ?>
<div class="alert alert-danger">
<ul>
<?php foreach ($validation->getErrors() as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="form-group">
<label for="groups">Rôle</label>
<select class="form-control" id="groups" name="groups">
<option value="">Sélectionnez un rôle</option>
<?php foreach ($group_data as $k => $v): ?>
<option value="<?php echo $v['id'] ?>"><?php echo $v['group_name'] ?></option>
<?php endforeach ?>
</select>
</div>
<div class="form-group">
<label for="username">Nom d'utilisateur</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Username" autocomplete="off">
</div>
<div class="form-group">
<label for="groups">Rôle</label>
<select class="form-control" id="groups" name="groups">
<option value="">Sélectionnez un rôle</option>
<?php foreach ($group_data as $k => $v): ?>
<option value="<?php echo $v['id'] ?>"><?php echo $v['group_name'] ?></option>
<?php endforeach ?>
</select>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Email" autocomplete="off">
</div>
<div class="form-group">
<label for="username">Nom d'utilisateur</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Username" autocomplete="off">
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password" autocomplete="off">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Email" autocomplete="off">
</div>
<div class="form-group">
<label for="cpassword">Confirmation mot de passe</label>
<input type="password" class="form-control" id="cpassword" name="cpassword" placeholder="Confirm Password" autocomplete="off">
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password" autocomplete="off">
</div>
<div class="form-group">
<label for="fname">Nom</label>
<input type="text" class="form-control" id="fname" name="fname" placeholder="First name" autocomplete="off">
</div>
<div class="form-group">
<label for="cpassword">Confirmation mot de passe</label>
<input type="password" class="form-control" id="cpassword" name="cpassword" placeholder="Confirm Password" autocomplete="off">
</div>
<div class="form-group">
<label for="lname">Prénom</label>
<input type="text" class="form-control" id="lname" name="lname" placeholder="Last name" autocomplete="off">
</div>
<div class="form-group">
<label for="fname">Nom</label>
<input type="text" class="form-control" id="fname" name="fname" placeholder="First name" autocomplete="off">
</div>
<div class="form-group">
<label for="phone">Téléphone</label>
<input type="text" class="form-control" id="phone" name="phone" placeholder="Phone" autocomplete="off">
</div>
<div class="form-group">
<label for="lname">Prénom</label>
<input type="text" class="form-control" id="lname" name="lname" placeholder="Last name" autocomplete="off">
</div>
<div class="form-group">
<label for="gender">Genre</label>
<div class="radio">
<label>
<input type="radio" name="gender" id="male" value="1">
Homme
</label>
<label>
<input type="radio" name="gender" id="female" value="2">
Femme
</label>
</div>
</div>
<div class="form-group">
<label for="phone">Téléphone</label>
<input type="text" class="form-control" id="phone" name="phone" placeholder="Phone" autocomplete="off">
</div>
<div class="form-group" id="store-container" style="display: none;">
<label for="store">Magasin</label>
<select class="form-control select_group" id="store" name="store">
<option value="0">Sélectionnez un magasin</option>
<?php foreach ($stores as $k => $v): ?>
<option value="<?php echo $v['id'] ?>"><?php echo $v['name'] ?></option>
<?php endforeach ?>
</select>
<div class="form-group">
<label for="gender">Genre</label>
<div class="radio">
<label>
<input type="radio" name="gender" id="male" value="1">
Homme
</label>
<label>
<input type="radio" name="gender" id="female" value="2">
Femme
</label>
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-primary">Enregistrer</button>
<a href="<?php echo base_url('users/') ?>" class="btn btn-warning">Retour</a>
<!-- Champ magasin toujours affiché -->
<div class="form-group" id="store-container">
<label for="store">Magasin</label>
<select class="form-control select_group" id="store" name="store">
<option value="0">Sélectionnez un magasin</option>
<?php foreach ($stores as $k => $v): ?>
<option value="<?php echo $v['id'] ?>"><?php echo $v['name'] ?></option>
<?php endforeach ?>
</select>
</div>
</form>
</div>
<!-- /.box -->
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Enregistrer</button>
<a href="<?php echo base_url('users/') ?>" class="btn btn-warning">Retour</a>
</div>
</form>
</div>
<!-- col-md-12 -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div>
</div>
</section>
</div>
<script type="text/javascript">
$(document).ready(function() {
@ -146,21 +138,6 @@
$("#mainUserNav").addClass('active');
$("#createUserNav").addClass('active');
$('#groups').on('change', function () {
// Get the selected option's text
let selectedText = $('#groups option:selected').text();
// Check if the selected text matches "commercial" (case insensitive)
if (selectedText.toLowerCase().includes('commercial')) {
// Show the store dropdown
$('#store-container').show();
} else {
// Hide the store dropdown
$('#store-container').hide();
}
});
// SUPPRIMÉ : plus aucune condition sur le rôle
});
</script>

Loading…
Cancel
Save