Browse Source

11092025

master
Sarobidy22 3 months ago
parent
commit
e9ed02267c
  1. 10
      app/Config/Routes.php
  2. 315
      app/Controllers/HistoriqueController.php
  3. 188
      app/Controllers/ProductCOntroller.php
  4. 183
      app/Models/Historique.php
  5. 1
      app/Views/avances/avance.php
  6. 2
      app/Views/commercial/addImage.php
  7. 6
      app/Views/dashboard.php
  8. 17
      app/Views/groups/edit.php
  9. 433
      app/Views/historique/index.php
  10. 256
      app/Views/products/index.php
  11. 2
      app/Views/templates/header.php
  12. 25
      app/Views/templates/side_menubar.php
  13. 49
      app/Views/users/index.php

10
app/Config/Routes.php

@ -293,4 +293,14 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']); $routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
$routes->post('updateAvance/(:num)', [AvanceController::class, 'updateAvance']); $routes->post('updateAvance/(:num)', [AvanceController::class, 'updateAvance']);
}); });
// historique
$routes->group('historique', ['filter' => 'auth'], static function ($routes) {
$routes->get('/', 'HistoriqueController::index');
$routes->get('fetchHistoriqueData', 'HistoriqueController::fetchHistoriqueData');
$routes->get('export', 'HistoriqueController::export');
$routes->get('stats', 'HistoriqueController::stats'); // <-- ici
$routes->get('getStats', 'HistoriqueController::getStats'); // reste pour AJAX
$routes->post('clean', 'HistoriqueController::clean');
});
}); });

315
app/Controllers/HistoriqueController.php

@ -0,0 +1,315 @@
<?php
namespace App\Controllers;
use App\Models\Historique;
use App\Models\Products;
use App\Models\Stores;
class HistoriqueController extends AdminController
{
private $pageTitle = 'Historique des Mouvements';
public function __construct()
{
parent::__construct();
helper(['form', 'url']);
}
/**
* Page principale de l'historique
*/
public function index()
{
$this->verifyRole('viewCom');
$storesModel = new Stores();
$data['page_title'] = $this->pageTitle;
$data['stores'] = $storesModel->getActiveStore();
return $this->render_template('historique/index', $data);
}
/**
* Récupérer les données pour DataTables
*/
public function fetchHistoriqueData()
{
$historiqueModel = new Historique();
// Récupération des paramètres envoyés par DataTables
$draw = intval($this->request->getGet('draw'));
$start = intval($this->request->getGet('start'));
$length = intval($this->request->getGet('length'));
// Filtres personnalisés
$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')
];
// 1️⃣ Nombre total de lignes (sans filtre)
$recordsTotal = $historiqueModel->countAll();
// 2️⃣ Récupération des données filtrées
$allDataFiltered = $historiqueModel->getHistoriqueWithFilters($filters);
$recordsFiltered = count($allDataFiltered);
// 3️⃣ Pagination
$dataPaginated = array_slice($allDataFiltered, $start, $length);
// 4️⃣ Formatage pour DataTables
$data = [];
foreach ($dataPaginated as $row) {
$data[] = [
date('d/m/Y H:i:s', strtotime($row['created_at'])),
$row['product_name'] ?? 'N/A',
$row['sku'] ?? 'N/A',
$row['store_name'] ?? 'N/A',
$this->getActionBadge($row['action']),
$row['description'] ?? ''
];
}
// 5️⃣ Retour JSON
return $this->response->setJSON([
'draw' => $draw,
'recordsTotal' => $recordsTotal,
'recordsFiltered' => $recordsFiltered,
'data' => $data
]);
}
/**
* Historique spécifique d'un produit
*/
public function product($productId)
{
$this->verifyRole('viewCom');
$historiqueModel = new Historique();
$productsModel = new Products();
$product = $productsModel->find($productId);
if (!$product) {
session()->setFlashdata('error', 'Produit introuvable');
return redirect()->to('/historique');
}
$data['page_title'] = 'Historique - ' . $product['name'];
$data['product'] = $product;
$data['historique'] = $historiqueModel->getHistoriqueByProduct($productId);
return $this->render_template('historique/product', $data);
}
/**
* Enregistrer un mouvement d'entrée
*/
public function entrer()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(404);
}
$data = $this->request->getJSON(true);
if (!isset($data['product_id']) || !isset($data['store_id'])) {
return $this->response->setJSON([
'success' => false,
'message' => 'Paramètres manquants.'
]);
}
$productsModel = new Products();
$storesModel = new Stores();
$historiqueModel = new Historique();
$product = $productsModel->find($data['product_id']);
$store = $storesModel->find($data['store_id']);
if (!$product || !$store) {
return $this->response->setJSON([
'success' => false,
'message' => 'Produit ou magasin introuvable.'
]);
}
// Mettre à jour le produit
$updateData = [
'store_id' => $data['store_id'],
'availability' => 1
];
if ($productsModel->update($data['product_id'], $updateData)) {
// Enregistrer dans l'historique
$description = "Produit ajouté au magasin " . $store['name'] . " depuis TOUS";
$historiqueModel->logMovement(
'products',
'ENTRER',
$product['id'],
$product['name'],
$product['sku'],
$store['name'],
$description
);
return $this->response->setJSON(['success' => true]);
}
return $this->response->setJSON([
'success' => false,
'message' => 'Erreur lors de la mise à jour.'
]);
}
/**
* Enregistrer un mouvement de sortie
*/
public function sortie()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(404);
}
$data = $this->request->getJSON(true);
if (!isset($data['product_id'])) {
return $this->response->setJSON([
'success' => false,
'message' => 'ID produit manquant.'
]);
}
$productsModel = new Products();
$storesModel = new Stores();
$historiqueModel = new Historique();
$product = $productsModel->find($data['product_id']);
if (!$product) {
return $this->response->setJSON([
'success' => false,
'message' => 'Produit introuvable.'
]);
}
$currentStore = $storesModel->find($product['store_id']);
$currentStoreName = $currentStore ? $currentStore['name'] : 'TOUS';
// Mettre à jour le produit (retirer du magasin)
$updateData = [
'store_id' => 0, // TOUS
'availability' => 0 // Non disponible
];
if ($productsModel->update($data['product_id'], $updateData)) {
// Enregistrer dans l'historique
$description = "Produit retiré du magasin " . $currentStoreName . " vers TOUS";
$historiqueModel->logMovement(
'products',
'SORTIE',
$product['id'],
$product['name'],
$product['sku'],
'TOUS',
$description
);
return $this->response->setJSON(['success' => true]);
}
return $this->response->setJSON([
'success' => false,
'message' => 'Erreur lors de la mise à jour.'
]);
}
/**
* Exporter l'historique
*/
public function export()
{
$this->verifyRole('viewCom');
$historiqueModel = new Historique();
$filters = [
'action' => $this->request->getGet('action'),
'store_name' => $this->request->getGet('store_name'), // Utilise le nom du magasin
'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')
];
$csvData = $historiqueModel->exportHistorique($filters);
$filename = 'historique_' . date('Y-m-d_H-i-s') . '.csv';
return $this->response
->setHeader('Content-Type', 'text/csv')
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
->setBody($csvData);
}
/**
* Nettoyer l'historique ancien
*/
public function clean()
{
$this->verifyRole('updateCom');
$days = $this->request->getPost('days') ?? 365;
$historiqueModel = new Historique();
$deleted = $historiqueModel->cleanOldHistory($days);
if ($deleted) {
session()->setFlashdata('success', "Historique nettoyé ($deleted entrées supprimées)");
} else {
session()->setFlashdata('info', 'Aucune entrée à supprimer');
}
return redirect()->to('/historique');
}
/**
* Obtenir le badge HTML pour une action
*/
private function getActionBadge($action)
{
$badges = [
'CREATE' => '<span class="label label-success">Création</span>',
'UPDATE' => '<span class="label label-warning">Modification</span>',
'DELETE' => '<span class="label label-danger">Suppression</span>',
'ASSIGN_STORE' => '<span class="label label-info">Assignation</span>',
'ENTRER' => '<span class="label label-primary">Entrée</span>',
'SORTIE' => '<span class="label label-default">Sortie</span>',
'IMPORT' => '<span class="label label-success"><i class="fa fa-upload"></i> Import</span>'
];
return $badges[$action] ?? '<span class="label label-secondary">' . $action . '</span>';
}
/**
* API pour obtenir les statistiques
*/
public function getStats()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(404);
}
$historiqueModel = new Historique();
$stats = $historiqueModel->getHistoriqueStats();
return $this->response->setJSON($stats);
}
}

188
app/Controllers/ProductCOntroller.php

@ -18,11 +18,9 @@ class ProductCOntroller extends AdminController
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
// Assuming permission is being set from a session
helper(['form', 'url']); helper(['form', 'url']);
} }
private $pageTitle = 'Produits'; private $pageTitle = 'Produits';
public function index() public function index()
@ -38,16 +36,13 @@ class ProductCOntroller extends AdminController
public function assign_store() public function assign_store()
{ {
// Vérifie que la requête est bien une requête AJAX
if (!$this->request->isAJAX()) { if (!$this->request->isAJAX()) {
$response = Services::response(); $response = Services::response();
$response->setStatusCode(404, 'Page Not Found')->send(); $response->setStatusCode(404, 'Page Not Found')->send();
exit; exit;
} }
// Récupère les données POST sous format JSON $data = $this->request->getJSON(true);
$data = $this->request->getJSON(true); // Décodage en tableau associatif
if (!isset($data['product_id']) || !isset($data['store_id'])) { if (!isset($data['product_id']) || !isset($data['store_id'])) {
return $this->response->setJSON([ return $this->response->setJSON([
@ -56,19 +51,23 @@ class ProductCOntroller extends AdminController
])->setStatusCode(400); ])->setStatusCode(400);
} }
$product_id = $data['product_id']; $product_id = (int)$data['product_id'];
$store_id = $data['store_id']; $store_id = (int)$data['store_id'];
$productsModel = new Products(); $productsModel = new Products();
// Appeler la méthode assignToStore pour mettre à jour la base de données
$result = $productsModel->assignToStore($product_id, $store_id); $result = $productsModel->assignToStore($product_id, $store_id);
// Répondre en JSON avec le résultat
if ($result) { if ($result) {
return $this->response->setJSON(['success' => true]); return $this->response->setJSON([
'success' => true,
'message' => 'Produit assigné avec succès.'
]);
} else { } else {
return $this->response->setJSON(['success' => false, 'message' => 'Échec de la mise à jour.']); return $this->response->setJSON([
'success' => false,
'message' => 'Échec de la mise à jour.'
]);
} }
} }
@ -94,18 +93,17 @@ class ProductCOntroller extends AdminController
$store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu"; $store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu";
} }
// CORRECTION: Disponibilité basée sur qty ET availability (avec conversion explicite) // Disponibilité basée sur qty ET availability
$isInStock = ((int)$value['qty'] > 0); $isInStock = ((int)$value['qty'] > 0);
$isAvailable = ((int)$value['availability'] === 1); // Conversion explicite en entier et comparaison stricte $isAvailable = ((int)$value['availability'] === 1);
// Un produit est disponible s'il est en stock ET marqué comme disponible
$isProductAvailable = $isInStock && $isAvailable; $isProductAvailable = $isInStock && $isAvailable;
$availability = $isProductAvailable ? $availability = $isProductAvailable ?
'<span class="label label-success">En stock</span>' : '<span class="label label-success">En stock</span>' :
'<span class="label label-danger">Rupture</span>'; '<span class="label label-danger">Rupture</span>';
// Construction des boutons (inchangé) // Construction des boutons
$buttons = ''; $buttons = '';
if (in_array('updateProduct', $this->permission ?? [])) { if (in_array('updateProduct', $this->permission ?? [])) {
$buttons .= '<a href="' . base_url('products/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>'; $buttons .= '<a href="' . base_url('products/update/' . $value['id']) . '" class="btn btn-default"><i class="fa fa-pencil"></i></a>';
@ -128,10 +126,11 @@ class ProductCOntroller extends AdminController
} }
if (in_array('assignStore', $this->permission ?? [])) { if (in_array('assignStore', $this->permission ?? [])) {
$buttons .= $buttons .= sprintf(
'<button type="button" class="btn btn-info assignbtn" title="Assigner sur un magasin" data-magasin="' . $store_name . '" data-products-id="' . $value["id"] . '" data-toggle="modal" data-target="#assignStoreModal"> ' <button type="button" class="btn btn-info assignbtn" title="Assigner sur un magasin" data-magasin="%s" data-products-id="%d" data-toggle="modal" data-target="#assignStoreModal"><i class="fa fa-forward"></i></button>',
<i class="fa fa-forward"></i> htmlspecialchars($store_name, ENT_QUOTES),
</button>'; (int)$value["id"]
);
} }
$imagePath = 'assets/images/product_image/' . $value['image']; $imagePath = 'assets/images/product_image/' . $value['image'];
@ -139,9 +138,8 @@ class ProductCOntroller extends AdminController
'<img src="' . base_url($imagePath) . '" width="50" height="50" class="img-thumbnail">' : '<img src="' . base_url($imagePath) . '" width="50" height="50" class="img-thumbnail">' :
'<div class="no-image">Aucune image</div>'; '<div class="no-image">Aucune image</div>';
// Préparer les données pour DataTables
$result['data'][$key] = [ $result['data'][$key] = [
$value['image'], $imageHtml, // Correction : utiliser $imageHtml au lieu de $value['image']
convertString($value['sku']), convertString($value['sku']),
$value['name'], $value['name'],
$value['prix_vente'], $value['prix_vente'],
@ -154,9 +152,8 @@ class ProductCOntroller extends AdminController
return $this->response->setJSON($result); return $this->response->setJSON($result);
} }
public function create()
public function create() {
{
$Products = new Products(); $Products = new Products();
$Brands = new Brands(); $Brands = new Brands();
$Category = new Category(); $Category = new Category();
@ -165,7 +162,6 @@ public function create()
$this->verifyRole('createProduct'); $this->verifyRole('createProduct');
$data['page_title'] = $this->pageTitle; $data['page_title'] = $this->pageTitle;
// Validate form inputs
$validation = \Config\Services::validation(); $validation = \Config\Services::validation();
$validation->setRules([ $validation->setRules([
'nom_de_produit' => 'required', 'nom_de_produit' => 'required',
@ -181,7 +177,18 @@ public function create()
]); ]);
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) { if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
// Conversion de la disponibilité : 1 = disponible, 2 ou autre = 0 (non disponible)
$upload_image = '';
$file = $this->request->getFile('product_image');
if ($file && $file->isValid() && !$file->hasMoved()) {
$uploadResult = $this->uploadImage();
if ($uploadResult && !strpos($uploadResult, 'Error') && !strpos($uploadResult, 'No file')) {
$upload_image = $uploadResult;
}
}
$product_sold = 0;
$availabilityValue = (int)$this->request->getPost('availability'); $availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0; $availability = ($availabilityValue === 1) ? 1 : 0;
@ -196,7 +203,7 @@ public function create()
'marque' => $this->request->getPost('marque'), 'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'), 'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'), 'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability, // Utilise la valeur convertie 'availability' => $availability,
'prix_vente' => $this->request->getPost('price_vente'), 'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'), 'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'), 'puissance' => $this->request->getPost('puissance'),
@ -212,15 +219,15 @@ public function create()
$store_id1 = (int)$this->request->getPost('store'); $store_id1 = (int)$this->request->getPost('store');
// Insert data into the database $productId = $Products->insert($data);
if ($Products->create($data)) { if ($productId) {
$data = [ $data_fourchette = [
'product_id' => $Products->insertID(), 'product_id' => $productId,
'prix_minimal' => $this->request->getPost('price_min'), 'prix_minimal' => $this->request->getPost('price_min'),
]; ];
$Fourchette = new FourchettePrix(); $Fourchette = new FourchettePrix();
$Fourchette->createFourchettePrix($data); $Fourchette->createFourchettePrix($data_fourchette);
session()->setFlashdata('success', 'Créé avec succès'); session()->setFlashdata('success', 'Créé avec succès');
$Notification->createNotification("Un nouveau Produit a été crée", "COMMERCIALE",$store_id1,'product/'); $Notification->createNotification("Un nouveau Produit a été crée", "COMMERCIALE",$store_id1,'product/');
return redirect()->to('/products'); return redirect()->to('/products');
@ -239,36 +246,41 @@ public function create()
return $this->render_template('products/create', $data); return $this->render_template('products/create', $data);
} }
} }
private function uploadImage() private function uploadImage()
{ {
// Define the upload directory
$uploadPath = 'assets/images/product_image'; $uploadPath = 'assets/images/product_image';
// Ensure the directory exists
if (!is_dir($uploadPath)) { if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0777, true); mkdir($uploadPath, 0777, true);
} }
// Check if the file is uploaded via the form
$file = $this->request->getFile('product_image'); $file = $this->request->getFile('product_image');
if ($file && $file->isValid() && !$file->hasMoved()) { if ($file && $file->isValid() && !$file->hasMoved()) {
// Generate a unique file name $allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$newName = uniqid() . '.' . $file->getExtension(); $fileExtension = strtolower($file->getExtension());
// Move the file to the target directory if (!in_array($fileExtension, $allowedTypes)) {
$file->move($uploadPath, $newName); log_message('error', 'Type de fichier non autorisé: ' . $fileExtension);
return '';
}
$newName = uniqid() . '.' . $file->getExtension();
// Return the actual file name if ($file->move($uploadPath, $newName)) {
return $newName; return $newName;
} else {
log_message('error', 'Erreur lors du déplacement du fichier');
return '';
}
} }
// If an error occurs, return the error message return '';
return $file ? $file->getErrorString() : 'No file was uploaded.';
} }
public function update(int $id) public function update(int $id)
{ {
$Products = new Products(); $Products = new Products();
$Stores = new Stores(); $Stores = new Stores();
$Category = new Category(); $Category = new Category();
@ -276,7 +288,6 @@ public function create()
$data['page_title'] = $this->pageTitle; $data['page_title'] = $this->pageTitle;
$Brands = new Brands(); $Brands = new Brands();
// Validate form inputs
$validation = \Config\Services::validation(); $validation = \Config\Services::validation();
$validation->setRules([ $validation->setRules([
'nom_de_produit' => 'required', 'nom_de_produit' => 'required',
@ -284,7 +295,6 @@ public function create()
]); ]);
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) { if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
// Conversion de la disponibilité : 1 = disponible, 2 ou autre = 0 (non disponible)
$availabilityValue = (int)$this->request->getPost('availability'); $availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0; $availability = ($availabilityValue === 1) ? 1 : 0;
@ -298,7 +308,7 @@ public function create()
'marque' => $this->request->getPost('marque'), 'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'), 'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'), 'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability, // Utilise la valeur convertie 'availability' => $availability,
'prix_vente' => $this->request->getPost('price_vente'), 'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'), 'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'), 'puissance' => $this->request->getPost('puissance'),
@ -311,8 +321,6 @@ public function create()
'type'=> $this->request->getPost('type'), 'type'=> $this->request->getPost('type'),
]; ];
// Check if a product image is uploaded
if ($this->request->getFile('product_image')->isValid()) { if ($this->request->getFile('product_image')->isValid()) {
$uploadImage = $this->uploadImage(); $uploadImage = $this->uploadImage();
$uploadData = ['image' => $uploadImage]; $uploadData = ['image' => $uploadImage];
@ -324,7 +332,7 @@ public function create()
return redirect()->to('/products'); return redirect()->to('/products');
} else { } else {
session()->setFlashdata('errors', 'Error occurred!!'); session()->setFlashdata('errors', 'Error occurred!!');
return redirect()->to('/produtcs/update/' . $id); return redirect()->to('/products/update/' . $id);
} }
} else { } else {
$data = [ $data = [
@ -338,7 +346,8 @@ public function create()
return $this->render_template('products/editbackup', $data); return $this->render_template('products/editbackup', $data);
} }
} }
public function remove() public function remove()
{ {
$this->verifyRole('deleteProduct'); $this->verifyRole('deleteProduct');
@ -358,7 +367,6 @@ public function create()
$response['success'] = false; $response['success'] = false;
$response['messages'] = "Refersh the page again!!"; $response['messages'] = "Refersh the page again!!";
} }
// Return JSON response
return $this->response->setJSON($response); return $this->response->setJSON($response);
} }
@ -394,11 +402,9 @@ public function create()
]); ]);
} }
// Récupérer les en-têtes
$headers = array_shift($rows); $headers = array_shift($rows);
$headers = array_map('strtolower', $headers); $headers = array_map('strtolower', $headers);
// Mapping des colonnes Excel vers les champs de la base
$columnMapping = [ $columnMapping = [
'n° série' => 'sku', 'n° série' => 'sku',
'marque' => 'marque', 'marque' => 'marque',
@ -410,7 +416,7 @@ public function create()
'puissance' => 'puissance', 'puissance' => 'puissance',
'clé' => 'cler', 'clé' => 'cler',
'prix d\'achat' => 'price', 'prix d\'achat' => 'price',
'prix ar' => 'prix_vente', // Correction du mapping 'prix ar' => 'prix_vente',
'catégories' => 'categorie_id', 'catégories' => 'categorie_id',
'magasin' => 'store_id', 'magasin' => 'store_id',
'disponibilité' => 'availability', 'disponibilité' => 'availability',
@ -424,10 +430,17 @@ public function create()
$StoresModel = new Stores(); $StoresModel = new Stores();
$CategoryModel = new Category(); $CategoryModel = new Category();
$db = \Config\Database::connect();
$db->query("SET @IMPORT_MODE = 1");
$countInserted = 0; $countInserted = 0;
$errors = [];
$db->transStart();
foreach ($rows as $row) { try {
if (empty(array_filter($row))) continue; // Ignore les lignes vides foreach ($rows as $rowIndex => $row) {
if (empty(array_filter($row))) continue;
$data = [ $data = [
'is_piece' => 0, 'is_piece' => 0,
@ -435,17 +448,15 @@ public function create()
'qty' => 1 'qty' => 1
]; ];
// Mapper chaque colonne
foreach ($headers as $index => $header) { foreach ($headers as $index => $header) {
$header = trim($header); $header = trim($header);
if (isset($columnMapping[$header]) && isset($row[$index])) { if (isset($columnMapping[$header]) && isset($row[$index])) {
$field = $columnMapping[$header]; $field = $columnMapping[$header];
$value = trim($row[$index]); $value = trim($row[$index]);
// Traitements spécifiques pour certains champs
switch ($field) { switch ($field) {
case 'marque': case 'marque':
// Chercher ou créer la marque if (!empty($value)) {
$brand = $BrandsModel->where('name', $value)->first(); $brand = $BrandsModel->where('name', $value)->first();
if (!$brand) { if (!$brand) {
$brandId = $BrandsModel->insert(['name' => $value, 'active' => 1]); $brandId = $BrandsModel->insert(['name' => $value, 'active' => 1]);
@ -453,11 +464,11 @@ public function create()
$brandId = $brand['id']; $brandId = $brand['id'];
} }
$data[$field] = $brandId; $data[$field] = $brandId;
}
break; break;
case 'store_id': case 'store_id':
// Gestion du magasin if ($value == 'TOUS' || empty($value)) {
if ($value == 'TOUS') {
$data[$field] = 0; $data[$field] = 0;
} else { } else {
$store = $StoresModel->where('name', $value)->first(); $store = $StoresModel->where('name', $value)->first();
@ -466,22 +477,28 @@ public function create()
break; break;
case 'date_arivage': case 'date_arivage':
// Convertir la date Excel en format MySQL if (!empty($value)) {
try {
if (is_numeric($value)) { if (is_numeric($value)) {
$data[$field] = date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($value)); $data[$field] = date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($value));
} else { } else {
$data[$field] = date('Y-m-d', strtotime($value)); $data[$field] = date('Y-m-d', strtotime($value));
} }
} catch (\Exception $e) {
$data[$field] = date('Y-m-d');
}
}
break; break;
case 'price': case 'price':
case 'prix_vente': // Ajout de la gestion pour prix_vente case 'prix_vente':
if (!empty($value)) {
$cleanedValue = str_replace(['Ar', ' ', ','], '', $value); $cleanedValue = str_replace(['Ar', ' ', ','], '', $value);
$data[$field] = (float)$cleanedValue; $data[$field] = (float)$cleanedValue;
}
break; break;
case 'categorie_id': case 'categorie_id':
// Gestion des catégories si nécessaire
if (!empty($value)) { if (!empty($value)) {
$category = $CategoryModel->where('name', $value)->first(); $category = $CategoryModel->where('name', $value)->first();
$data[$field] = $category ? $category['id'] : null; $data[$field] = $category ? $category['id'] : null;
@ -489,7 +506,6 @@ public function create()
break; break;
case 'availability': case 'availability':
// Convertir la disponibilité en booléen
$data[$field] = (strtolower($value) == 'oui' || $value == '1') ? 1 : 0; $data[$field] = (strtolower($value) == 'oui' || $value == '1') ? 1 : 0;
break; break;
@ -499,20 +515,50 @@ public function create()
} }
} }
// Insertion if (empty($data['name'])) {
if (!empty($data['name'])) { $errors[] = "Ligne " . ($rowIndex + 2) . ": Nom du produit manquant";
continue;
}
if ($ProductsModel->insert($data)) { if ($ProductsModel->insert($data)) {
$countInserted++; $countInserted++;
} else {
$errors[] = "Ligne " . ($rowIndex + 2) . ": Erreur lors de l'insertion";
}
}
$db->transComplete();
} catch (\Exception $e) {
$db->transRollback();
throw $e;
} finally {
$db->query("SET @IMPORT_MODE = 0");
} }
$message = "$countInserted produits importés avec succès";
if (!empty($errors)) {
$message .= ". Erreurs: " . implode(', ', array_slice($errors, 0, 5));
if (count($errors) > 5) {
$message .= "... et " . (count($errors) - 5) . " autres erreurs";
} }
} }
return $this->response->setJSON([ return $this->response->setJSON([
'success' => true, 'success' => $countInserted > 0,
'messages' => "$countInserted produits importés avec succès" 'messages' => $message,
'imported' => $countInserted,
'errors' => count($errors)
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
try {
$db = \Config\Database::connect();
$db->query("SET @IMPORT_MODE = 0");
} catch (\Exception $ex) {
// Ignorer les erreurs de désactivation
}
return $this->response->setJSON([ return $this->response->setJSON([
'success' => false, 'success' => false,
'messages' => "Erreur lors de l'import: " . $e->getMessage() 'messages' => "Erreur lors de l'import: " . $e->getMessage()

183
app/Models/Historique.php

@ -0,0 +1,183 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class Historique extends Model
{
protected $table = 'historique';
protected $primaryKey = 'id';
protected $allowedFields = [
'table_name',
'action',
'row_id',
'product_name',
'sku',
'store_name',
'description',
'created_at'
];
protected $useTimestamps = false;
protected $createdField = 'created_at';
/**
* Récupérer tous les historiques avec pagination
*/
public function getHistoriqueData($limit = null, $offset = null)
{
$builder = $this->select('*')
->orderBy('created_at', 'DESC');
if ($limit !== null) {
$builder->limit($limit, $offset);
}
return $builder->get()->getResultArray();
}
/**
* Récupérer l'historique pour un produit spécifique
*/
public function getHistoriqueByProduct($productId)
{
return $this->where('row_id', $productId)
->where('table_name', 'products')
->orderBy('created_at', 'DESC')
->findAll();
}
/**
* Récupérer l'historique pour un magasin spécifique
*/
public function getHistoriqueByStore($storeName)
{
return $this->where('store_name', $storeName)
->orderBy('created_at', 'DESC')
->findAll();
}
/**
* Récupérer l'historique par type d'action
*/
public function getHistoriqueByAction($action)
{
return $this->where('action', $action)
->orderBy('created_at', 'DESC')
->findAll();
}
/**
* Récupérer les statistiques d'historique
*/
public function getHistoriqueStats()
{
$stats = [];
// Total des mouvements
$stats['total_mouvements'] = $this->countAll();
// Mouvements par action
$actions = ['CREATE', 'UPDATE', 'DELETE', 'ASSIGN_STORE', 'ENTRER', 'SORTIE'];
foreach ($actions as $action) {
$stats['mouvements_' . strtolower($action)] = $this->where('action', $action)->countAllResults();
}
// Mouvements aujourd'hui
$stats['mouvements_today'] = $this->where('DATE(created_at)', date('Y-m-d'))->countAllResults();
// Mouvements cette semaine
$stats['mouvements_week'] = $this->where('created_at >=', date('Y-m-d', strtotime('-7 days')))->countAllResults();
return $stats;
}
/**
* Enregistrer un mouvement dans l'historique
*/
public function logMovement($tableName, $action, $rowId, $productName, $sku, $storeName, $description = null)
{
$data = [
'table_name' => $tableName,
'action' => $action,
'row_id' => $rowId,
'product_name' => $productName,
'sku' => $sku,
'store_name' => $storeName,
'description' => $description,
'created_at' => date('Y-m-d H:i:s')
];
return $this->insert($data);
}
/**
* Nettoyer l'historique ancien (plus de X jours)
*/
public function cleanOldHistory($days = 365)
{
$cutoffDate = date('Y-m-d', strtotime("-{$days} days"));
return $this->where('created_at <', $cutoffDate)->delete();
}
/**
* Récupérer l'historique avec filtres
*
* @param array $filters Filtres pour la requête
* @return array
*/
public function getHistoriqueWithFilters($filters = [])
{
$builder = $this->select('*');
if (!empty($filters['action']) && $filters['action'] !== 'all') {
$builder->where('action', $filters['action']);
}
if (!empty($filters['store_name']) && $filters['store_name'] !== 'all') {
$builder->where('store_name', $filters['store_name']);
}
if (!empty($filters['product_name'])) {
$builder->like('product_name', $filters['product_name']);
}
if (!empty($filters['sku'])) {
$builder->like('sku', $filters['sku']);
}
if (!empty($filters['date_from'])) {
$builder->where('created_at >=', $filters['date_from'] . ' 00:00:00');
}
if (!empty($filters['date_to'])) {
$builder->where('created_at <=', $filters['date_to'] . ' 23:59:59');
}
return $builder->orderBy('created_at', 'DESC')->findAll();
}
/**
* Exporter l'historique en CSV
*/
public function exportHistorique($filters = [])
{
$data = $this->getHistoriqueWithFilters($filters);
$csvData = "ID,Table,Action,ID Produit,Nom Produit,SKU,Magasin,Description,Date/Heure\n";
foreach ($data as $row) {
$csvData .= '"' . $row['id'] . '",';
$csvData .= '"' . $row['table_name'] . '",';
$csvData .= '"' . $row['action'] . '",';
$csvData .= '"' . $row['row_id'] . '",';
$csvData .= '"' . str_replace('"', '""', $row['product_name']) . '",';
$csvData .= '"' . $row['sku'] . '",';
$csvData .= '"' . $row['store_name'] . '",';
$csvData .= '"' . str_replace('"', '""', $row['description'] ?? '') . '",';
$csvData .= '"' . $row['created_at'] . '"' . "\n";
}
return $csvData;
}
}

1
app/Views/avances/avance.php

@ -327,6 +327,7 @@
<?php endif; ?> <?php endif; ?>
<script> <script>
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0; var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;

2
app/Views/commercial/addImage.php

@ -44,7 +44,7 @@
</form> </form>
<a href="<?= base_url('/products') ?>" class="btn btn-outline-secondary"> <a href="<?= base_url('/products') ?>" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Retour <i class="fa fa-arrow-left"></i> Retour
</a> </a>
</div> </div>
</div> </div>

6
app/Views/dashboard.php

@ -731,7 +731,7 @@
<!-- small box --> <!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_mvola_final, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_mvola, 0, '.', ' '); ?>Ar</h2>
<p>Totale MVOLA</p> <p>Totale MVOLA</p>
</div> </div>
<div class="icon"> <div class="icon">
@ -744,7 +744,7 @@
<!-- small box --> <!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_espece_final, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_espece, 0, '.', ' '); ?>Ar</h2>
<p>Totale en espece</p> <p>Totale en espece</p>
</div> </div>
<div class="icon"> <div class="icon">
@ -757,7 +757,7 @@
<!-- small box --> <!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;"> <div class="small-box" style="background-color: #A9A9A9;">
<div class="inner"> <div class="inner">
<h2><?php echo number_format($total_virement_bancaire_final, 0, '.', ' '); ?>Ar</h2> <h2><?php echo number_format($total_virement_bancaire, 0, '.', ' '); ?>Ar</h2>
<p>Totale en banque</p> <p>Totale en banque</p>
</div> </div>
<div class="icon"> <div class="icon">

17
app/Views/groups/edit.php

@ -78,7 +78,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<td>Commercial</td> <td>Espace Commercial</td>
<td>-</td> <td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateCom" class="minimal" <td><input type="checkbox" name="permission[]" id="permission" value="updateCom" class="minimal"
<?php if ($serialize_permission) { <?php if ($serialize_permission) {
@ -603,6 +603,21 @@
<td> - </td> <td> - </td>
<td>-</td> <td>-</td>
</tr> </tr>
<tr>
<td>Historique</td>
<td> - </td>
<td> - </td>
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewhistorique"
<?php if ($serialize_permission) {
if (in_array('viewhistorique', $serialize_permission)) {
echo "checked";
}
} ?>></td>
<td> - </td>
<td>-</td>
</tr>
<tr> <tr>
<td>Paramètre</td> <td>Paramètre</td>
<td>-</td> <td>-</td>

433
app/Views/historique/index.php

@ -0,0 +1,433 @@
<div class="content-wrapper">
<section class="content-header">
<h1>
<i class="fa fa-history text-blue"></i> Gérer l'
<small>Historique</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
<li class="active">Historique</li>
</ol>
</section>
<section class="content">
<div class="row">
<div class="col-md-12 col-xs-12">
<div id="messages"></div>
<div class="box box-primary shadow-sm">
<div class="box-header with-border">
<h3 class="box-title"><i class="fa fa-list"></i> <?= $page_title ?></h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#filterModal">
<i class="fa fa-filter"></i> Filtrer
</button>
<button type="button" class="btn btn-sm btn-success" onclick="exportHistorique()">
<i class="fa fa-download"></i> Exporter
</button>
<!-- <button type="button" class="btn btn-info" onclick="showStats()">
<i class="fa fa-bar-chart"></i> Statistiques
</button> -->
</div>
</div>
<div class="box-body">
<div class="row" style="margin-bottom: 15px;">
<div class="col-md-4">
<div class="form-group">
<label><i class="fa fa-home"></i> Sélectionner un magasin</label>
<select class="form-control input-sm" id="store_top_filter" name="store_id">
<option value="all">Tous les magasins</option>
<?php foreach ($stores as $store): ?>
<option value="<?= $store['name'] ?>"><?= $store['name'] ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="row" style="margin-bottom:10px;">
<div class="col-md-12">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default btn-sm filter-quick" data-filter="today"><i class="fa fa-calendar-day"></i> Aujourd'hui</button>
<button type="button" class="btn btn-default btn-sm filter-quick" data-filter="week"><i class="fa fa-calendar-week"></i> Cette semaine</button>
<button type="button" class="btn btn-default btn-sm filter-quick" data-filter="month"><i class="fa fa-calendar"></i> Ce mois</button>
<button type="button" class="btn btn-default btn-sm filter-quick active" data-filter="all"><i class="fa fa-infinity"></i> Tout</button>
</div>
<div class="btn-group pull-right" role="group">
<button type="button" class="btn btn-default btn-sm action-filter active" data-action="all"><i class="fa fa-list"></i> Toutes</button>
<button type="button" class="btn btn-success btn-sm action-filter" data-action="CREATE"><i class="fa fa-plus-circle"></i> Création</button>
<button type="button" class="btn btn-warning btn-sm action-filter" data-action="UPDATE"><i class="fa fa-edit"></i> Modification</button>
<!-- <button type="button" class="btn btn-warning action-filter" data-action="ASSIGN_STORE">Assignation</button> -->
<button type="button" class="btn btn-primary btn-sm action-filter" data-action="ENTRER"><i class="fa fa-arrow-down"></i> Entrée</button>
<button type="button" class="btn btn-danger btn-sm action-filter" data-action="SORTIE"><i class="fa fa-arrow-up"></i> Sortie</button>
</div>
</div>
</div>
</div>
<div class="box-body">
<div class="table-responsive">
<table id="historiqueTable" class="table table-bordered table-striped table-hover nowrap" style="width:100%;">
<thead class="bg-light-blue">
<tr>
<th>Date</th>
<th>Produit</th>
<th>SKU</th>
<th>Magasin</th>
<th>Action</th>
<th>Description</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<!-- Loader -->
<div id="loading" style="display:none;text-align:center;margin:20px;">
<i class="fa fa-spinner fa-spin fa-2x text-blue"></i>
<p>Chargement des données...</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="modal fade" id="filterModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Filtres avancés</h4>
</div>
<div class="modal-body">
<form id="filterForm">
<input type="hidden" id="movement_type" name="movement_type" value="">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Date de début</label>
<input type="date" class="form-control" id="date_from" name="date_from">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Date de fin</label>
<input type="date" class="form-control" id="date_to" name="date_to">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Magasin</label>
<select class="form-control" id="store_filter" name="store_id">
<option value="all">Tous les magasins</option>
<option value="0">TOUS</option>
<?php foreach ($stores as $store): ?>
<option value="<?= $store['id'] ?>"><?= $store['name'] ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Action</label>
<select class="form-control" id="action_filter" name="action">
<option value="all">Toutes les actions</option>
<option value="CREATE">Création</option>
<option value="UPDATE">Modification</option>
<option value="DELETE">Suppression</option>
<option value="ASSIGN_STORE">Assignation</option>
<option value="ENTRER">Entrée</option>
<option value="SORTIE">Sortie</option>
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
<button type="button" class="btn btn-default" onclick="resetFilters()">Réinitialiser</button>
<button type="button" class="btn btn-primary" onclick="applyFilters()">Appliquer</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="statsModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Statistiques de l'historique</h4>
</div>
<div class="modal-body" id="statsContent">
</div>
</div>
</div>
</div>
<style>
.btn-group .btn.active {
font-weight: bold;
border: 1px solid #444;
}
.table td, .table th {
vertical-align: middle !important;
}
.bg-light-blue {
background: #3c8dbc;
color: white;
}
.box.shadow-sm {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
</style>
<script>
// Déclaration de la variable de table en dehors du document ready pour un accès global
var historiqueTable;
$(document).ready(function() {
// On s'assure que le fichier de langue est chargé et on étend les options par défaut de DataTables.
// Cette configuration est copiée de votre deuxième script pour un affichage de pagination uniforme.
$.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"
}
}
});
// Initialisation du DataTable
historiqueTable = $('#historiqueTable').DataTable({
"processing": true,
"serverSide": true,
"ajax": {
"url": "<?= base_url('historique/fetchHistoriqueData') ?>",
"type": "GET",
"data": function(d) {
d.store_name = $('#store_top_filter').val();
d.date_from = $('#date_from').val();
d.date_to = $('#date_to').val();
d.action = $('#action_filter').val();
d.movement_type = $('#movement_type').val();
},
"beforeSend": function() { $("#loading").show(); },
"complete": function() { $("#loading").hide(); }
},
"columns": [
{ "data": 0, "width": "15%" },
{ "data": 1, "width": "20%" },
{ "data": 2, "width": "10%" },
{ "data": 3, "width": "12%" },
{
"data": 4,
"width": "10%",
"render": function(data) {
let badgeClass = "badge bg-gray";
if (data === "CREATE") badgeClass = "badge bg-green";
else if (data === "UPDATE") badgeClass = "badge bg-yellow";
else if (data === "ENTRER") badgeClass = "badge bg-blue";
else if (data === "SORTIE") badgeClass = "badge bg-red";
return '<span class="'+badgeClass+'">'+data+'</span>';
}
},
{ "data": 5, "width": "33%" }
],
"order": [[0, "desc"]],
"pageLength": 25,
"lengthMenu": [
[5, 10, 20, 50, -1],
['5', '10', '20', '50', 'Tous']
],
"dom": 'Blfrtip',
"searching": false,
"buttons": [
'copy', 'csv', 'excel', 'pdf', 'print'
]
});
// Événement pour le nouveau champ de sélection de magasin
$('#store_top_filter').change(function() {
// Met à jour la valeur du filtre avancé pour la synchronisation
$('#store_filter').val('all');
historiqueTable.ajax.reload();
});
// S'assurer que le filtre avancé par magasin recharge le tableau
$('#store_filter').change(function() {
historiqueTable.ajax.reload();
});
// Gestion des filtres rapides (période)
$('.filter-quick').click(function() {
$('.filter-quick').removeClass('active');
$(this).addClass('active');
var filter = $(this).data('filter');
var today = new Date().toISOString().split('T')[0];
switch(filter) {
case 'today':
$('#date_from').val(today);
$('#date_to').val(today);
break;
case 'week':
var weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
$('#date_from').val(weekAgo.toISOString().split('T')[0]);
$('#date_to').val(today);
break;
case 'month':
var monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
$('#date_from').val(monthAgo.toISOString().split('T')[0]);
$('#date_to').val(today);
break;
case 'all':
$('#date_from').val('');
$('#date_to').val('');
break;
}
historiqueTable.ajax.reload();
});
// Gestion des filtres d'action rapides
$('.action-filter').click(function() {
$('.action-filter').removeClass('active');
$(this).addClass('active');
var action = $(this).data('action');
if (action === 'ENTRER' || action === 'SORTIE') {
$('#action_filter').val(action);
$('#movement_type').val(action.toLowerCase());
} else if (action === 'all') {
$('#action_filter').val('all');
$('#movement_type').val('');
} else {
$('#action_filter').val(action);
$('#movement_type').val('');
}
historiqueTable.ajax.reload();
});
});
function applyFilters() {
$('#filterModal').modal('hide');
// Mettre à jour le sélecteur rapide en fonction du filtre avancé
var selectedStoreId = $('#store_filter').val();
if (selectedStoreId === 'all') {
$('#store_top_filter').val('all');
} else {
// Trouver le nom du magasin correspondant à l'ID
var storeName = $('#store_filter option[value="' + selectedStoreId + '"]').text();
$('#store_top_filter').val(storeName);
}
historiqueTable.ajax.reload();
}
function resetFilters() {
$('#filterForm')[0].reset();
$('#store_filter').val('all');
$('#store_top_filter').val('all'); // Réinitialise aussi le champ supérieur
$('#action_filter').val('all');
$('#movement_type').val('');
$('.filter-quick, .action-filter').removeClass('active');
$('.filter-quick[data-filter="all"]').addClass('active');
$('.action-filter[data-action="all"]').addClass('active');
historiqueTable.ajax.reload();
}
function exportHistorique() {
var params = new URLSearchParams();
params.append('date_from', $('#date_from').val());
params.append('date_to', $('#date_to').val());
params.append('store_name', $('#store_top_filter').val()); // Utilise le nouveau champ
params.append('action', $('#action_filter').val());
params.append('movement_type', $('#movement_type').val());
window.location.href = '<?= base_url('historique/export') ?>?' + params.toString();
}
function showStats() {
$.ajax({
url: '<?= base_url('historique/getStats') ?>',
type: 'GET',
dataType: 'json',
success: function(data) {
var html = '<div class="row">';
html += '<div class="col-md-4">';
html += '<div class="info-box bg-aqua">';
html += '<span class="info-box-icon"><i class="fa fa-history"></i></span>';
html += '<div class="info-box-content">';
html += '<span class="info-box-text">Total événements</span>';
html += '<span class="info-box-number">' + data.total_mouvements + '</span>';
html += '</div></div></div>';
html += '<div class="col-md-8">';
html += '<h4>Répartition par action</h4>';
html += '<div class="row">';
var total = data.total_mouvements;
var actions = {
'CREATE': data.mouvements_create,
'UPDATE': data.mouvements_update,
'DELETE': data.mouvements_delete,
'ASSIGN_STORE': data.mouvements_assign_store,
'ENTRER': data.mouvements_entrer,
'SORTIE': data.mouvements_sortie,
};
for (var action in actions) {
var value = actions[action] || 0;
var percent = (total > 0) ? (value / total * 100).toFixed(2) : 0;
html += '<div class="col-md-6">';
html += '<small>' + action + '</small>';
html += '<div class="progress">';
html += '<div class="progress-bar" style="width: ' + percent + '%">';
html += value + ' (' + percent + '%)';
html += '</div></div></div>';
}
html += '</div></div></div>';
$('#statsContent').html(html);
$('#statsModal').modal('show');
},
error: function(xhr, status, error) {
// Remplacer l'alerte par un message plus élégant
$('#statsContent').html('<div class="alert alert-danger" role="alert">Erreur lors du chargement des statistiques.</div>');
$('#statsModal').modal('show');
}
});
}
</script>

256
app/Views/products/index.php

@ -31,6 +31,7 @@
<?php echo session()->getFlashdata('error'); ?> <?php echo session()->getFlashdata('error'); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if (in_array('createProduct', $user_permission)): ?> <?php if (in_array('createProduct', $user_permission)): ?>
<div class="d-flex gap-2 mb-3 align-items-center"> <div class="d-flex gap-2 mb-3 align-items-center">
<a href="<?= base_url('products/create') ?>" class="btn btn-primary"> <a href="<?= base_url('products/create') ?>" class="btn btn-primary">
@ -49,25 +50,22 @@
> >
<i class="far fa-file-excel"></i> Importer un fichier <i class="far fa-file-excel"></i> Importer un fichier
</button> </button>
<br>
<br>
<form <form
action="<?= base_url('products/importExcel') ?>" action="<?= base_url('products/importExcel') ?>"
method="post" method="post"
enctype="multipart/form-data" enctype="multipart/form-data"
class="d-inline-flex align-items-center" class="d-inline-flex align-items-center"
> >
<!-- Input masqué -->
<input <input
type="file" type="file"
name="excel_product" name="excel_product"
id="excel_product" id="excel_product"
accept=".xls,.xlsx" accept=".xls,.xlsx"
hidden hidden
required style="display: none;" required
style="display: none;"
/> />
<!-- Le vrai bouton -->
</form> </form>
</div> </div>
<?php endif; ?> <?php endif; ?>
@ -75,8 +73,9 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const inputFile = document.getElementById('excel_product'); const inputFile = document.getElementById('excel_product');
const form = inputFile.form; const form = inputFile ? inputFile.form : null;
if (inputFile && form) {
inputFile.addEventListener('change', function () { inputFile.addEventListener('change', function () {
if (this.files.length) { if (this.files.length) {
const formData = new FormData(form); const formData = new FormData(form);
@ -94,7 +93,6 @@
} }
if (response.success === true) { if (response.success === true) {
manageTable.ajax.reload(null, false);
$("#messages").html( $("#messages").html(
'<div class="alert alert-success alert-dismissible" role="alert">' + '<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
@ -130,10 +128,20 @@
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
console.error("Erreur AJAX : ", error); console.error("Erreur AJAX : ", error);
$("#messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' +
'Erreur lors de l\'upload du fichier' +
'</div>'
);
} }
}); });
} }
}); });
}
}); });
</script> </script>
@ -157,7 +165,6 @@
<?php endif; ?> <?php endif; ?>
</tr> </tr>
</thead> </thead>
</table> </table>
</div> </div>
<!-- /.box-body --> <!-- /.box-body -->
@ -168,7 +175,6 @@
</div> </div>
<!-- /.row --> <!-- /.row -->
</section> </section>
<!-- /.content --> <!-- /.content -->
</div> </div>
@ -192,8 +198,6 @@
<button type="submit" class="btn btn-primary">Oui</button> <button type="submit" class="btn btn-primary">Oui</button>
</div> </div>
</form> </form>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
@ -240,7 +244,7 @@
</div> </div>
</div> </div>
<!-- remove brand modal --> <!-- QR Code Multiple Modal -->
<div class="modal fade" tabindex="-1" role="dialog" id="qrMultipleModal"> <div class="modal fade" tabindex="-1" role="dialog" id="qrMultipleModal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
@ -265,23 +269,23 @@
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
<div id="qrcode" style="display: none;"></div> <div id="qrcode" style="display: none;"></div>
<div id="qrContainer" style="display: none;"></div> <div id="qrContainer" style="display: none;"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var manageTable; var manageTable;
var base_url = "<?php echo base_url(); ?>"; var base_url = "<?php echo base_url(); ?>";
$(".select_group").select2(); $(".select_group").select2();
$(document).ready(function() { $(document).ready(function() {
$("#mainProductNav").addClass('active'); $("#mainProductNav").addClass('active');
$("#manageProductNav").addClass('active'); $("#manageProductNav").addClass('active');
// initialize the datatable // Configuration DataTables en français
$.extend(true, $.fn.dataTable.defaults, { $.extend(true, $.fn.dataTable.defaults, {
language: { language: {
sProcessing: "Traitement en cours...", sProcessing: "Traitement en cours...",
@ -304,7 +308,9 @@
sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant" sSortDescending: ": activer pour trier la colonne par ordre d&eacute;croissant"
} }
} }
}); });
// Initialisation DataTable
manageTable = $('#manageTable').DataTable({ manageTable = $('#manageTable').DataTable({
'ajax': base_url + 'products/fetchProductData', 'ajax': base_url + 'products/fetchProductData',
'order': [], 'order': [],
@ -315,9 +321,9 @@
return data; // Affiche le HTML brut (déjà formaté en PHP) return data; // Affiche le HTML brut (déjà formaté en PHP)
}, },
orderable: false // Désactive le tri sur cette colonne orderable: false // Désactive le tri sur cette colonne
}, // SKU },
{ data: 1 }, // Nom { data: 1 }, // SKU
{ data: 2 }, // Quantité { data: 2 }, // Nom
{ {
data: 3, // Prix data: 3, // Prix
render: function(data, type, row) { render: function(data, type, row) {
@ -336,10 +342,88 @@
targets: 3, targets: 3,
className: 'text-right' className: 'text-right'
}] }]
}); });
// CORRECTION: Utilisation de la délégation d'événements pour les boutons
// Boutons d'assignation de magasin
$(document).on('click', '.assign-store-btn', function() {
const productId = $(this).data('products-id');
const storeId = $(this).data('store-id');
console.log('Product ID:', productId, 'Store ID:', storeId); // Debug
if (confirm('Êtes-vous sûr de vouloir assigner cette moto à ce magasin ?')) {
$.ajax({
url: base_url + 'products/assign_store',
type: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
data: JSON.stringify({
product_id: productId,
store_id: storeId
}),
dataType: 'json',
success: function(data) {
$("#assignStoreModal").modal("hide");
if (data.success) {
$("#messages").html(
'<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' +
(data.message || 'Moto assignée avec succès !') +
'</div>'
);
// Recharger seulement le DataTable au lieu de la page
manageTable.ajax.reload(null, false);
} else {
$("#messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' +
(data.message || 'Une erreur est survenue.') +
'</div>'
);
}
},
error: function(xhr, status, error) {
console.error('Error assigning store:', error);
$("#assignStoreModal").modal("hide");
$("#messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
'</button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' +
'Erreur de connexion lors de l\'assignation.' +
'</div>'
);
}
});
}
});
// Boutons d'ouverture du modal d'assignation
$(document).on('click', '.assignbtn', function() {
const storeName = $(this).data('magasin');
const productId = $(this).data('products-id');
console.log('Opening modal for product:', productId, 'Current store:', storeName); // Debug
$('#current_magasin').text(storeName);
$('.assign-store-btn').attr('data-products-id', productId);
});
}); });
// Fonction QR Code PDF pour un seul produit
async function generateQrPdf(productId) { async function generateQrPdf(productId) {
const productUrl = `https://motorbike.c4m.mg/ventes/show/${productId}`; const productUrl = `https://motorbike.c4m.mg/ventes/show/${productId}`;
@ -360,9 +444,7 @@
const qrImage = qrCanvas.toDataURL("image/png", 1.0); // Max quality const qrImage = qrCanvas.toDataURL("image/png", 1.0); // Max quality
// Create PDF // Create PDF
const { const { jsPDF } = window.jspdf;
jsPDF
} = window.jspdf;
const pdf = new jsPDF({ const pdf = new jsPDF({
orientation: "portrait", orientation: "portrait",
unit: "mm", unit: "mm",
@ -375,22 +457,27 @@
pdf.save(`QRCode_Product_${productId}.pdf`); pdf.save(`QRCode_Product_${productId}.pdf`);
} }
// Function to show the modal // Function to show the QR multiple modal
function showQrMultipleModal() { function showQrMultipleModal() {
$('#qrMultipleModal').modal('show'); $('#qrMultipleModal').modal('show');
} }
// Example: Trigger the modal when clicking a button // Event listener pour le bouton d'ouverture du modal QR
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
document.getElementById("openModalBtn").addEventListener("click", function() { const openModalBtn = document.getElementById("openModalBtn");
if (openModalBtn) {
openModalBtn.addEventListener("click", function() {
showQrMultipleModal(); showQrMultipleModal();
}); });
}
}); });
// Génération PDF pour multiples QR codes
const moto = document.getElementById('moto'); const moto = document.getElementById('moto');
const buttontest = document.getElementById('prints') const buttontest = document.getElementById('prints')
let arrayvalue = []; let arrayvalue = [];
if (buttontest) {
buttontest.addEventListener('click', async function() { buttontest.addEventListener('click', async function() {
arrayvalue = Array.from(moto.selectedOptions).map(option => ({ arrayvalue = Array.from(moto.selectedOptions).map(option => ({
id: option.value, // Value from <option value="..."> id: option.value, // Value from <option value="...">
@ -399,9 +486,7 @@
console.log(arrayvalue); // Debugging: Check selected values in console console.log(arrayvalue); // Debugging: Check selected values in console
const { const { jsPDF } = window.jspdf;
jsPDF
} = window.jspdf;
const doc = new jsPDF(); const doc = new jsPDF();
const qrSize = 50; // QR Code size const qrSize = 50; // QR Code size
@ -470,105 +555,60 @@
doc.save(`QRCode_Motos.pdf`); doc.save(`QRCode_Motos.pdf`);
$("#qrMultipleModal").modal('hide'); $("#qrMultipleModal").modal('hide');
}); });
}
// remove functions // Remove functions avec délégation d'événements
function removeFunc(id) { function removeFunc(id) {
if (id) { if (id) {
$("#removeForm").on('submit', function() { $("#removeForm").off('submit').on('submit', function(e) {
e.preventDefault();
var form = $(this); var form = $(this);
// remove the text-danger
$(".text-danger").remove(); $(".text-danger").remove();
$.ajax({ $.ajax({
url: form.attr('action'), url: form.attr('action'),
type: form.attr('method'), type: form.attr('method'),
data: { data: { product_id: id },
product_id: id
},
dataType: 'json', dataType: 'json',
success: function(response) { success: function(response) {
manageTable.ajax.reload(null, false); manageTable.ajax.reload(null, false);
if (response.success === true) { if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' + $("#messages").html(
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<div class="alert alert-success alert-dismissible" role="alert">' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'</div>'); '<span aria-hidden="true">&times;</span>' +
'</button>' +
// hide the modal '<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>'
);
$("#removeModal").modal('hide'); $("#removeModal").modal('hide');
} else { } else {
$("#messages").html(
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' + '<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages + '<span aria-hidden="true">&times;</span>' +
'</div>'); '</button>' +
} '<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
} '</div>'
}); );
return false;
});
}
} }
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
const assignButtons = document.querySelectorAll('.assign-store-btn');
const assignbtnopen = document.querySelectorAll('.assignbtn');
assignButtons.forEach(button => {
button.addEventListener('click', function() {
const productId = this.dataset.productsId;
const storeId = this.dataset.storeId;
// Display a confirmation dialog
if (confirm('Êtes-vous sûr de vouloir assigner ce moto à ce magasin ?')) {
// Send an AJAX request
fetch('<?= base_url("products/assign_store") ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
}, },
body: JSON.stringify({ error: function(xhr, status, error) {
product_id: productId, console.error('Error removing product:', error);
store_id: storeId $("#messages").html(
}) '<div class="alert alert-danger alert-dismissible" role="alert">' +
}) '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
.then(response => response.json()) '<span aria-hidden="true">&times;</span>' +
.then(data => { '</button>' +
if (data.success) { '<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' +
$("#assignStoreModal").modal("hide"); 'Erreur lors de la suppression.' +
alert('Moto assigné avec succès !'); '</div>'
window.location.reload(); );
} else {
alert('Une erreur est survenue.');
}
})
.catch(error => console.error('Error assigning store:', error));
} }
}); });
}); });
assignbtnopen.forEach(button => {
button.addEventListener('click', function() {
const storeName = this.dataset.magasin; // Récupère le magasin à partir de data-magasin
const productId = this.dataset.productsId;
const currentMagasinField = document.querySelector('#current_magasin'); // Cible le champ avec l'ID
if (currentMagasinField) { // Vérifie si le champ existe
currentMagasinField.innerHTML = storeName; // Met à jour la valeur
$(".assign-store-btn").attr("data-products-id", productId);
} else {
console.error("Element with ID 'current_magasin' not found.");
} }
}); }
});
}, 1000);
});
</script> </script>

2
app/Views/templates/header.php

@ -106,6 +106,8 @@
</head> </head>
<body class="hold-transition skin-blue sidebar-mini"> <body class="hold-transition skin-blue sidebar-mini">

25
app/Views/templates/side_menubar.php

@ -100,6 +100,31 @@
</li> </li>
<?php endif; ?> <?php endif; ?>
<!-- historique -->
<?php if (in_array('viewhistorique', $user_permission) || in_array('updatehistorique', $user_permission)): ?>
<li class="treeview" id="espaceMainMenu">
<a href="#">
<i class="class= fa fa-history"></i>
<span>Historique</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<!-- Lien principal vers l'index de l'historique -->
<li><a href="<?php echo base_url('historique') ?>"><i class="fa fa-circle"></i> Historiques des Produits</a></li>
<!-- Lien vers les statistiques (optionnel)
<li><a href="<?php echo base_url('historique/stats') ?>"><i class="fa fa-circle"></i> Statistiques</a></li> -->
</ul>
</li>
<?php endif; ?>
<!-- rapport statistique --> <!-- rapport statistique -->
<!-- fin espace commerciale --> <!-- fin espace commerciale -->

49
app/Views/users/index.php

@ -22,7 +22,7 @@
<?php echo session()->getFlashdata('success'); ?> <?php echo session()->getFlashdata('success'); ?>
</div> </div>
<?php elseif (session()->getFlashdata('error')): ?> <?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-error alert-dismissible" role="alert"> <div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<?php echo session()->getFlashdata('error'); ?> <?php echo session()->getFlashdata('error'); ?>
</div> </div>
@ -35,7 +35,7 @@
<?php if (in_array('createUser', $user_permission)): ?> <?php if (in_array('createUser', $user_permission)): ?>
<button class="btn btn-primary"id="create_user">Ajouter un utilisateur</button> <button class="btn btn-primary" id="create_user">Ajouter un utilisateur</button>
<br /> <br /> <br /> <br />
<?php endif; ?> <?php endif; ?>
<div class="box"> <div class="box">
@ -65,6 +65,7 @@
</div> </div>
</div> </div>
</section> </section>
</div>
<!-- Remove Modal --> <!-- Remove Modal -->
<div class="modal fade" id="removeModal" tabindex="-1" role="dialog" aria-labelledby="removeModalLabel" aria-hidden="true"> <div class="modal fade" id="removeModal" tabindex="-1" role="dialog" aria-labelledby="removeModalLabel" aria-hidden="true">
@ -86,7 +87,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Profile Modal -->
<!-- Profile Modal --> <!-- Profile Modal -->
<div class="modal fade" id="profileModal" tabindex="-1" role="dialog" aria-labelledby="profileModalLabel" aria-hidden="true"> <div class="modal fade" id="profileModal" tabindex="-1" role="dialog" aria-labelledby="profileModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">
@ -211,10 +212,6 @@
</div> </div>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#assignUserForm').on('submit', function (e) { $('#assignUserForm').on('submit', function (e) {
@ -231,7 +228,7 @@
dataType: 'json', dataType: 'json',
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
manageTable = $('#manageTable'); var manageTable = $('#manageTable');
manageTable.DataTable().ajax.reload(null, false); manageTable.DataTable().ajax.reload(null, false);
$('#assignUserModal').modal('hide'); $('#assignUserModal').modal('hide');
$('#messages').html( $('#messages').html(
@ -288,7 +285,7 @@
} }
}); });
manageTable = $('#manageTable').DataTable({ var manageTable = $('#manageTable').DataTable({
'ajax': { 'ajax': {
url: '<?= base_url('users/fetchUserData') ?>', url: '<?= base_url('users/fetchUserData') ?>',
type: 'GET', type: 'GET',
@ -324,7 +321,7 @@
removeUserId = button.data('id'); removeUserId = button.data('id');
}); });
// Au clic sur “Supprimer” dans la modal // Au clic sur "Supprimer" dans la modal
$('#confirmRemove').on('click', function() { $('#confirmRemove').on('click', function() {
if (!removeUserId) return; if (!removeUserId) return;
@ -362,40 +359,8 @@
); );
} }
}); });
function profileFunc(userId) {
$.ajax({
url: '<?= base_url('users/fetchProfile') ?>/' + userId,
type: 'GET',
success: function (response) {
if (response && response[0]) {
let data = response[0];
$('#p-username').text(data.user_data.username);
$('#p-email').text(data.user_data.email);
$('#p-firstname').text(data.user_data.firstname);
$('#p-lastname').text(data.user_data.lastname);
$('#p-gender').text(data.user_data.gender == 1 ? 'Homme' : 'Femme');
$('#p-phone').text(data.user_data.phone);
$('#p-role').html('<span class="label label-info">' + data.user_group.group_name + '</span>');
$('#p-store').text(data.store_name.name);
} else {
$('#profileContent').html('<div class="alert alert-warning">Aucune donnée trouvée</div>');
}
},
error: function (xhr, status, error) {
$('#profileContent').html(
'<div class="alert alert-danger">Erreur ' + xhr.status + ' : impossible de charger le profil.</div>'
);
}
});
}
}); });
}); // <-- fin de $(document).ready }); // <-- fin de $(document).ready
// Fonctions globales // Fonctions globales

Loading…
Cancel
Save