Browse Source

11092025

master
Sarobidy22 3 months ago
parent
commit
e9ed02267c
  1. 10
      app/Config/Routes.php
  2. 315
      app/Controllers/HistoriqueController.php
  3. 562
      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. 584
      app/Views/products/index.php
  11. 2
      app/Views/templates/header.php
  12. 25
      app/Views/templates/side_menubar.php
  13. 51
      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('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);
}
}

562
app/Controllers/ProductCOntroller.php

@ -18,11 +18,9 @@ class ProductCOntroller extends AdminController
public function __construct()
{
parent::__construct();
// Assuming permission is being set from a session
helper(['form', 'url']);
}
private $pageTitle = 'Produits';
public function index()
@ -38,16 +36,13 @@ class ProductCOntroller extends AdminController
public function assign_store()
{
// Vérifie que la requête est bien une requête AJAX
if (!$this->request->isAJAX()) {
$response = Services::response();
$response->setStatusCode(404, 'Page Not Found')->send();
exit;
}
// Récupère les données POST sous format JSON
$data = $this->request->getJSON(true); // Décodage en tableau associatif
$data = $this->request->getJSON(true);
if (!isset($data['product_id']) || !isset($data['store_id'])) {
return $this->response->setJSON([
@ -56,19 +51,23 @@ class ProductCOntroller extends AdminController
])->setStatusCode(400);
}
$product_id = $data['product_id'];
$store_id = $data['store_id'];
$product_id = (int)$data['product_id'];
$store_id = (int)$data['store_id'];
$productsModel = new Products();
// Appeler la méthode assignToStore pour mettre à jour la base de données
$result = $productsModel->assignToStore($product_id, $store_id);
// Répondre en JSON avec le résultat
if ($result) {
return $this->response->setJSON(['success' => true]);
return $this->response->setJSON([
'success' => true,
'message' => 'Produit assigné avec succès.'
]);
} 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";
}
// CORRECTION: Disponibilité basée sur qty ET availability (avec conversion explicite)
// Disponibilité basée sur qty ET availability
$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;
$availability = $isProductAvailable ?
'<span class="label label-success">En stock</span>' :
'<span class="label label-danger">Rupture</span>';
// Construction des boutons (inchangé)
// Construction des boutons
$buttons = '';
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>';
@ -128,10 +126,11 @@ class ProductCOntroller extends AdminController
}
if (in_array('assignStore', $this->permission ?? [])) {
$buttons .=
'<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">
<i class="fa fa-forward"></i>
</button>';
$buttons .= sprintf(
' <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>',
htmlspecialchars($store_name, ENT_QUOTES),
(int)$value["id"]
);
}
$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">' :
'<div class="no-image">Aucune image</div>';
// Préparer les données pour DataTables
$result['data'][$key] = [
$value['image'],
$imageHtml, // Correction : utiliser $imageHtml au lieu de $value['image']
convertString($value['sku']),
$value['name'],
$value['prix_vente'],
@ -153,192 +151,203 @@ class ProductCOntroller extends AdminController
return $this->response->setJSON($result);
}
public function create()
{
$Products = new Products();
$Brands = new Brands();
$Category = new Category();
$Stores = new Stores();
$Notification = new NotificationController();
$this->verifyRole('createProduct');
$data['page_title'] = $this->pageTitle;
// Validate form inputs
$validation = \Config\Services::validation();
$validation->setRules([
'nom_de_produit' => 'required',
'marque' => 'required',
'type' => 'required',
'numero_de_moteur' => 'required',
'prix' => 'required|numeric',
'price_vente' => 'required|numeric',
'puissance' => 'required',
'store' => 'required',
'availability' => 'required',
'price_min' => 'required|numeric',
]);
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');
$availability = ($availabilityValue === 1) ? 1 : 0;
public function create()
{
$Products = new Products();
$Brands = new Brands();
$Category = new Category();
$Stores = new Stores();
$Notification = new NotificationController();
$this->verifyRole('createProduct');
$data['page_title'] = $this->pageTitle;
$data = [
'name' => $this->request->getPost('nom_de_produit'),
'sku' => $this->request->getPost('numero_de_serie'),
'price' => $this->request->getPost('prix'),
'qty' => 1,
'image' => $upload_image,
'description' => $this->request->getPost('description'),
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'),
'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability, // Utilise la valeur convertie
'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'),
'categorie_id' => json_encode($this->request->getPost('categorie[]')),
'etats' => $this->request->getPost('etats'),
'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'),
'infoManque' => $this->request->getPost('infoManque'),
'product_sold' => $product_sold,
'type'=> $this->request->getPost('type')
];
$store_id1 = (int)$this->request->getPost('store');
// Insert data into the database
if ($Products->create($data)) {
$validation = \Config\Services::validation();
$validation->setRules([
'nom_de_produit' => 'required',
'marque' => 'required',
'type' => 'required',
'numero_de_moteur' => 'required',
'prix' => 'required|numeric',
'price_vente' => 'required|numeric',
'puissance' => 'required',
'store' => 'required',
'availability' => 'required',
'price_min' => 'required|numeric',
]);
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
$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');
$availability = ($availabilityValue === 1) ? 1 : 0;
$data = [
'product_id' => $Products->insertID(),
'prix_minimal' => $this->request->getPost('price_min'),
'name' => $this->request->getPost('nom_de_produit'),
'sku' => $this->request->getPost('numero_de_serie'),
'price' => $this->request->getPost('prix'),
'qty' => 1,
'image' => $upload_image,
'description' => $this->request->getPost('description'),
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'),
'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability,
'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'),
'categorie_id' => json_encode($this->request->getPost('categorie[]')),
'etats' => $this->request->getPost('etats'),
'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'),
'infoManque' => $this->request->getPost('infoManque'),
'product_sold' => $product_sold,
'type'=> $this->request->getPost('type')
];
$Fourchette = new FourchettePrix();
$Fourchette->createFourchettePrix($data);
session()->setFlashdata('success', 'Créé avec succès');
$Notification->createNotification("Un nouveau Produit a été crée", "COMMERCIALE",$store_id1,'product/');
return redirect()->to('/products');
$store_id1 = (int)$this->request->getPost('store');
$productId = $Products->insert($data);
if ($productId) {
$data_fourchette = [
'product_id' => $productId,
'prix_minimal' => $this->request->getPost('price_min'),
];
$Fourchette = new FourchettePrix();
$Fourchette->createFourchettePrix($data_fourchette);
session()->setFlashdata('success', 'Créé avec succès');
$Notification->createNotification("Un nouveau Produit a été crée", "COMMERCIALE",$store_id1,'product/');
return redirect()->to('/products');
} else {
session()->setFlashdata('errors', 'Error occurred while creating the product');
return redirect()->to('products/create');
}
} else {
session()->setFlashdata('errors', 'Error occurred while creating the product');
return redirect()->to('products/create');
$data = [
'stores' => $Stores->getActiveStore(),
'validation' => $validation,
'page_title' => $this->pageTitle,
'marque' => $Brands->getActiveBrands(),
'categorie' => $Category->getActiveCategory(),
];
return $this->render_template('products/create', $data);
}
} else {
$data = [
'stores' => $Stores->getActiveStore(),
'validation' => $validation,
'page_title' => $this->pageTitle,
'marque' => $Brands->getActiveBrands(),
'categorie' => $Category->getActiveCategory(),
];
return $this->render_template('products/create', $data);
}
}
private function uploadImage()
{
// Define the upload directory
$uploadPath = 'assets/images/product_image';
// Ensure the directory exists
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0777, true);
}
// Check if the file is uploaded via the form
$file = $this->request->getFile('product_image');
if ($file && $file->isValid() && !$file->hasMoved()) {
// Generate a unique file name
$allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$fileExtension = strtolower($file->getExtension());
if (!in_array($fileExtension, $allowedTypes)) {
log_message('error', 'Type de fichier non autorisé: ' . $fileExtension);
return '';
}
$newName = uniqid() . '.' . $file->getExtension();
// Move the file to the target directory
$file->move($uploadPath, $newName);
// Return the actual file name
return $newName;
if ($file->move($uploadPath, $newName)) {
return $newName;
} else {
log_message('error', 'Erreur lors du déplacement du fichier');
return '';
}
}
// If an error occurs, return the error message
return $file ? $file->getErrorString() : 'No file was uploaded.';
return '';
}
public function update(int $id)
{
$Products = new Products();
$Stores = new Stores();
$Category = new Category();
$this->verifyRole('updateProduct');
$data['page_title'] = $this->pageTitle;
$Brands = new Brands();
{
$Products = new Products();
$Stores = new Stores();
$Category = new Category();
$this->verifyRole('updateProduct');
$data['page_title'] = $this->pageTitle;
$Brands = new Brands();
// Validate form inputs
$validation = \Config\Services::validation();
$validation->setRules([
'nom_de_produit' => 'required',
'marque' => 'required',
]);
$validation = \Config\Services::validation();
$validation->setRules([
'nom_de_produit' => 'required',
'marque' => 'required',
]);
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');
$availability = ($availabilityValue === 1) ? 1 : 0;
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
$availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0;
$data = [
'name' => $this->request->getPost('nom_de_produit'),
'sku' => $this->request->getPost('numero_de_serie'),
'price' => $this->request->getPost('price'),
'qty' => 1,
'description' => $this->request->getPost('description'),
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'),
'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability, // Utilise la valeur convertie
'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'),
'categorie_id' => json_encode($this->request->getPost('categorie[]')),
'etats' => $this->request->getPost('etats'),
'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'),
'infoManque' => $this->request->getPost('infoManque'),
'type'=> $this->request->getPost('type'),
];
$data = [
'name' => $this->request->getPost('nom_de_produit'),
'sku' => $this->request->getPost('numero_de_serie'),
'price' => $this->request->getPost('price'),
'qty' => 1,
'description' => $this->request->getPost('description'),
'numero_de_moteur' => $this->request->getPost('numero_de_moteur'),
'marque' => $this->request->getPost('marque'),
'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability,
'prix_vente' => $this->request->getPost('price_vente'),
'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'),
'categorie_id' => json_encode($this->request->getPost('categorie[]')),
'etats' => $this->request->getPost('etats'),
'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'),
'infoManque' => $this->request->getPost('infoManque'),
'type'=> $this->request->getPost('type'),
];
// Check if a product image is uploaded
if ($this->request->getFile('product_image')->isValid()) {
$uploadImage = $this->uploadImage();
$uploadData = ['image' => $uploadImage];
$Products->update($id, $uploadData);
}
if ($this->request->getFile('product_image')->isValid()) {
$uploadImage = $this->uploadImage();
$uploadData = ['image' => $uploadImage];
$Products->update($id, $uploadData);
}
if ($Products->updateProduct($data, $id)) {
session()->setFlashdata('success', 'Successfully updated');
return redirect()->to('/products');
if ($Products->updateProduct($data, $id)) {
session()->setFlashdata('success', 'Successfully updated');
return redirect()->to('/products');
} else {
session()->setFlashdata('errors', 'Error occurred!!');
return redirect()->to('/products/update/' . $id);
}
} else {
session()->setFlashdata('errors', 'Error occurred!!');
return redirect()->to('/produtcs/update/' . $id);
}
} else {
$data = [
'stores' => $Stores->getActiveStore(),
'validation' => $validation,
'page_title' => $this->pageTitle,
'product_data' => $Products->getProductData($id),
'categorie' => $Category->getActiveCategory(),
'marque' => $Brands->getActiveBrands()
];
$data = [
'stores' => $Stores->getActiveStore(),
'validation' => $validation,
'page_title' => $this->pageTitle,
'product_data' => $Products->getProductData($id),
'categorie' => $Category->getActiveCategory(),
'marque' => $Brands->getActiveBrands()
];
return $this->render_template('products/editbackup', $data);
return $this->render_template('products/editbackup', $data);
}
}
}
public function remove()
{
$this->verifyRole('deleteProduct');
@ -358,14 +367,13 @@ public function create()
$response['success'] = false;
$response['messages'] = "Refersh the page again!!";
}
// Return JSON response
return $this->response->setJSON($response);
}
public function createByExcel()
{
$this->verifyRole("createProduct");
try {
$file = $this->request->getFile('excel_product');
if (!$file || !$file->isValid()) {
@ -394,11 +402,9 @@ public function create()
]);
}
// Récupérer les en-têtes
$headers = array_shift($rows);
$headers = array_map('strtolower', $headers);
// Mapping des colonnes Excel vers les champs de la base
$columnMapping = [
'n° série' => 'sku',
'marque' => 'marque',
@ -410,7 +416,7 @@ public function create()
'puissance' => 'puissance',
'clé' => 'cler',
'prix d\'achat' => 'price',
'prix ar' => 'prix_vente', // Correction du mapping
'prix ar' => 'prix_vente',
'catégories' => 'categorie_id',
'magasin' => 'store_id',
'disponibilité' => 'availability',
@ -424,99 +430,139 @@ public function create()
$StoresModel = new Stores();
$CategoryModel = new Category();
$db = \Config\Database::connect();
$db->query("SET @IMPORT_MODE = 1");
$countInserted = 0;
$errors = [];
foreach ($rows as $row) {
if (empty(array_filter($row))) continue; // Ignore les lignes vides
$db->transStart();
$data = [
'is_piece' => 0,
'product_sold' => 0,
'qty' => 1
];
try {
foreach ($rows as $rowIndex => $row) {
if (empty(array_filter($row))) continue;
$data = [
'is_piece' => 0,
'product_sold' => 0,
'qty' => 1
];
// Mapper chaque colonne
foreach ($headers as $index => $header) {
$header = trim($header);
if (isset($columnMapping[$header]) && isset($row[$index])) {
$field = $columnMapping[$header];
$value = trim($row[$index]);
// Traitements spécifiques pour certains champs
switch ($field) {
case 'marque':
// Chercher ou créer la marque
$brand = $BrandsModel->where('name', $value)->first();
if (!$brand) {
$brandId = $BrandsModel->insert(['name' => $value, 'active' => 1]);
} else {
$brandId = $brand['id'];
}
$data[$field] = $brandId;
break;
case 'store_id':
// Gestion du magasin
if ($value == 'TOUS') {
$data[$field] = 0;
} else {
$store = $StoresModel->where('name', $value)->first();
$data[$field] = $store ? $store['id'] : 0;
}
break;
case 'date_arivage':
// Convertir la date Excel en format MySQL
if (is_numeric($value)) {
$data[$field] = date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($value));
} else {
$data[$field] = date('Y-m-d', strtotime($value));
}
break;
case 'price':
case 'prix_vente': // Ajout de la gestion pour prix_vente
$cleanedValue = str_replace(['Ar', ' ', ','], '', $value);
$data[$field] = (float)$cleanedValue;
break;
case 'categorie_id':
// Gestion des catégories si nécessaire
if (!empty($value)) {
$category = $CategoryModel->where('name', $value)->first();
$data[$field] = $category ? $category['id'] : null;
}
break;
case 'availability':
// Convertir la disponibilité en booléen
$data[$field] = (strtolower($value) == 'oui' || $value == '1') ? 1 : 0;
break;
default:
$data[$field] = $value;
foreach ($headers as $index => $header) {
$header = trim($header);
if (isset($columnMapping[$header]) && isset($row[$index])) {
$field = $columnMapping[$header];
$value = trim($row[$index]);
switch ($field) {
case 'marque':
if (!empty($value)) {
$brand = $BrandsModel->where('name', $value)->first();
if (!$brand) {
$brandId = $BrandsModel->insert(['name' => $value, 'active' => 1]);
} else {
$brandId = $brand['id'];
}
$data[$field] = $brandId;
}
break;
case 'store_id':
if ($value == 'TOUS' || empty($value)) {
$data[$field] = 0;
} else {
$store = $StoresModel->where('name', $value)->first();
$data[$field] = $store ? $store['id'] : 0;
}
break;
case 'date_arivage':
if (!empty($value)) {
try {
if (is_numeric($value)) {
$data[$field] = date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($value));
} else {
$data[$field] = date('Y-m-d', strtotime($value));
}
} catch (\Exception $e) {
$data[$field] = date('Y-m-d');
}
}
break;
case 'price':
case 'prix_vente':
if (!empty($value)) {
$cleanedValue = str_replace(['Ar', ' ', ','], '', $value);
$data[$field] = (float)$cleanedValue;
}
break;
case 'categorie_id':
if (!empty($value)) {
$category = $CategoryModel->where('name', $value)->first();
$data[$field] = $category ? $category['id'] : null;
}
break;
case 'availability':
$data[$field] = (strtolower($value) == 'oui' || $value == '1') ? 1 : 0;
break;
default:
$data[$field] = $value;
}
}
}
}
// Insertion
if (!empty($data['name'])) {
if (empty($data['name'])) {
$errors[] = "Ligne " . ($rowIndex + 2) . ": Nom du produit manquant";
continue;
}
if ($ProductsModel->insert($data)) {
$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([
'success' => true,
'messages' => "$countInserted produits importés avec succès"
'success' => $countInserted > 0,
'messages' => $message,
'imported' => $countInserted,
'errors' => count($errors)
]);
} 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([
'success' => false,
'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

@ -326,6 +326,7 @@
</div><!-- /.modal -->
<?php endif; ?>
<script>
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;

2
app/Views/commercial/addImage.php

@ -44,7 +44,7 @@
</form>
<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>
</div>
</div>

6
app/Views/dashboard.php

@ -731,7 +731,7 @@
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;">
<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>
</div>
<div class="icon">
@ -744,7 +744,7 @@
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;">
<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>
</div>
<div class="icon">
@ -757,7 +757,7 @@
<!-- small box -->
<div class="small-box" style="background-color: #A9A9A9;">
<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>
</div>
<div class="icon">

17
app/Views/groups/edit.php

@ -78,7 +78,7 @@
</tr>
</thead>
<tbody>
<td>Commercial</td>
<td>Espace Commercial</td>
<td>-</td>
<td><input type="checkbox" name="permission[]" id="permission" value="updateCom" class="minimal"
<?php if ($serialize_permission) {
@ -603,6 +603,21 @@
<td> - </td>
<td>-</td>
</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>
<td>Paramètre</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>

584
app/Views/products/index.php

@ -31,6 +31,7 @@
<?php echo session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<?php if (in_array('createProduct', $user_permission)): ?>
<div class="d-flex gap-2 mb-3 align-items-center">
<a href="<?= base_url('products/create') ?>" class="btn btn-primary">
@ -49,25 +50,22 @@
>
<i class="far fa-file-excel"></i> Importer un fichier
</button>
<br>
<br>
<form
action="<?= base_url('products/importExcel') ?>"
method="post"
enctype="multipart/form-data"
class="d-inline-flex align-items-center"
>
<!-- Input masqué -->
<input
type="file"
name="excel_product"
id="excel_product"
accept=".xls,.xlsx"
hidden
required style="display: none;"
required
style="display: none;"
/>
<!-- Le vrai bouton -->
</form>
</div>
<?php endif; ?>
@ -75,67 +73,77 @@
<script>
document.addEventListener('DOMContentLoaded', function () {
const inputFile = document.getElementById('excel_product');
const form = inputFile.form;
inputFile.addEventListener('change', function () {
if (this.files.length) {
const formData = new FormData(form);
$.ajax({
url: "<?= base_url('products/createByExcel') ?>",
type: 'post',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
success: function(response) {
if (typeof manageTable !== 'undefined') {
manageTable.ajax.reload(null, false);
}
if (response.success === true) {
manageTable.ajax.reload(null, false);
$("#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> ' +
response.messages +
'</div>'
);
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
id.closest('.form-group')
.removeClass('has-error has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success');
const form = inputFile ? inputFile.form : null;
if (inputFile && form) {
inputFile.addEventListener('change', function () {
if (this.files.length) {
const formData = new FormData(form);
$.ajax({
url: "<?= base_url('products/createByExcel') ?>",
type: 'post',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
success: function(response) {
if (typeof manageTable !== 'undefined') {
manageTable.ajax.reload(null, false);
}
id.after(value);
});
} else {
if (response.success === true) {
$("#messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<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-exclamation-sign"></span></strong> ' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' +
response.messages +
'</div>'
);
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
id.closest('.form-group')
.removeClass('has-error has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success');
id.after(value);
});
} 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> ' +
response.messages +
'</div>'
);
}
}
},
error: function(xhr, status, 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>'
);
}
},
error: function(xhr, status, error) {
console.error("Erreur AJAX : ", error);
}
});
}
});
});
}
});
}
});
</script>
</script>
<div class="box">
<div class="box-header">
@ -157,7 +165,6 @@
<?php endif; ?>
</tr>
</thead>
</table>
</div>
<!-- /.box-body -->
@ -168,7 +175,6 @@
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
@ -192,8 +198,6 @@
<button type="submit" class="btn btn-primary">Oui</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
@ -240,7 +244,7 @@
</div>
</div>
<!-- remove brand modal -->
<!-- QR Code Multiple Modal -->
<div class="modal fade" tabindex="-1" role="dialog" id="qrMultipleModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
@ -265,81 +269,161 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div id="qrcode" 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/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script type="text/javascript">
var manageTable;
var base_url = "<?php echo base_url(); ?>";
$(".select_group").select2();
$(document).ready(function() {
$("#mainProductNav").addClass('active');
$("#manageProductNav").addClass('active');
// initialize the datatable
// Configuration DataTables en français
$.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"
}
}
});
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 DataTable
manageTable = $('#manageTable').DataTable({
'ajax': base_url + 'products/fetchProductData',
'order': [],
'columns': [
{
data: 0, // Colonne Image
render: function(data) {
return data; // Affiche le HTML brut (déjà formaté en PHP)
},
orderable: false // Désactive le tri sur cette colonne
}, // SKU
{ data: 1 }, // Nom
{ data: 2 }, // Quantité
{
data: 3, // Prix
render: function(data, type, row) {
if (type === 'display') {
// Format: "1 900 000 Ar"
return new Intl.NumberFormat('fr-FR').format(data) + ' Ar';
}
return data; // Valeur non formatée pour le tri/filtre
'ajax': base_url + 'products/fetchProductData',
'order': [],
'columns': [
{
data: 0, // Colonne Image
render: function(data) {
return data; // Affiche le HTML brut (déjà formaté en PHP)
},
orderable: false // Désactive le tri sur cette colonne
},
{ data: 1 }, // SKU
{ data: 2 }, // Nom
{
data: 3, // Prix
render: function(data, type, row) {
if (type === 'display') {
// Format: "1 900 000 Ar"
return new Intl.NumberFormat('fr-FR').format(data) + ' Ar';
}
return data; // Valeur non formatée pour le tri/filtre
}
},
{ data: 4 }, // Magasin
{ data: 5 }, // Disponibilité
{ data: 6 } // Actions
],
'columnDefs': [{
targets: 3,
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>'
);
}
});
}
},
{ data: 4 }, // Magasin
{ data: 5 }, // Disponibilité
{ data: 6 } // Actions
],
'columnDefs': [{
targets: 3,
className: 'text-right'
}]
});
});
// 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) {
const productUrl = `https://motorbike.c4m.mg/ventes/show/${productId}`;
@ -360,9 +444,7 @@
const qrImage = qrCanvas.toDataURL("image/png", 1.0); // Max quality
// Create PDF
const {
jsPDF
} = window.jspdf;
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({
orientation: "portrait",
unit: "mm",
@ -375,200 +457,158 @@
pdf.save(`QRCode_Product_${productId}.pdf`);
}
// Function to show the modal
// Function to show the QR multiple modal
function showQrMultipleModal() {
$('#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.getElementById("openModalBtn").addEventListener("click", function() {
showQrMultipleModal();
});
const openModalBtn = document.getElementById("openModalBtn");
if (openModalBtn) {
openModalBtn.addEventListener("click", function() {
showQrMultipleModal();
});
}
});
// Génération PDF pour multiples QR codes
const moto = document.getElementById('moto');
const buttontest = document.getElementById('prints')
let arrayvalue = [];
buttontest.addEventListener('click', async function() {
arrayvalue = Array.from(moto.selectedOptions).map(option => ({
id: option.value, // Value from <option value="...">
name: option.text // Label from <option>...</option>
}));
console.log(arrayvalue); // Debugging: Check selected values in console
const {
jsPDF
} = window.jspdf;
const doc = new jsPDF();
const qrSize = 50; // QR Code size
const marginX = 20; // Left margin
const marginY = 20; // Top margin
const spacingX = 65; // Space between QR codes horizontally
const spacingY = 80; // Space between QR codes vertically
let x = marginX;
let y = marginY;
let count = 0;
for (let i = 0; i < arrayvalue.length; i++) {
let moto = arrayvalue[i];
// Create a hidden div for the QR Code
let qrDiv = document.createElement("div");
document.getElementById("qrContainer").appendChild(qrDiv);
// Generate QR Code
let qrCode = new QRCode(qrDiv, {
text: `https://motorbike.c4m.mg/ventes/show/${moto.id}`,
width: qrSize,
height: qrSize
});
if (buttontest) {
buttontest.addEventListener('click', async function() {
arrayvalue = Array.from(moto.selectedOptions).map(option => ({
id: option.value, // Value from <option value="...">
name: option.text // Label from <option>...</option>
}));
console.log(arrayvalue); // Debugging: Check selected values in console
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const qrSize = 50; // QR Code size
const marginX = 20; // Left margin
const marginY = 20; // Top margin
const spacingX = 65; // Space between QR codes horizontally
const spacingY = 80; // Space between QR codes vertically
let x = marginX;
let y = marginY;
let count = 0;
for (let i = 0; i < arrayvalue.length; i++) {
let moto = arrayvalue[i];
// Create a hidden div for the QR Code
let qrDiv = document.createElement("div");
document.getElementById("qrContainer").appendChild(qrDiv);
// Generate QR Code
let qrCode = new QRCode(qrDiv, {
text: `https://motorbike.c4m.mg/ventes/show/${moto.id}`,
width: qrSize,
height: qrSize
});
// Wait for QR code to render
await new Promise(resolve => setTimeout(resolve, 500));
// Wait for QR code to render
await new Promise(resolve => setTimeout(resolve, 500));
// Convert QR code to Base64 Image
let canvas = qrDiv.querySelector("canvas");
if (!canvas) {
alert("Error: QR Code not generated!");
return;
}
let qrDataUrl = canvas.toDataURL("image/png");
// Convert QR code to Base64 Image
let canvas = qrDiv.querySelector("canvas");
if (!canvas) {
alert("Error: QR Code not generated!");
return;
}
let qrDataUrl = canvas.toDataURL("image/png");
// Draw text (Moto Name)
doc.text(moto.name, x + 5, y - 5);
// Draw text (Moto Name)
doc.text(moto.name, x + 5, y - 5);
// Draw QR Code
doc.addImage(qrDataUrl, "PNG", x, y, qrSize, qrSize);
// Draw QR Code
doc.addImage(qrDataUrl, "PNG", x, y, qrSize, qrSize);
// Remove QR code from the DOM
qrDiv.remove();
// Remove QR code from the DOM
qrDiv.remove();
// Update position for next QR code
x += spacingX;
count++;
// Update position for next QR code
x += spacingX;
count++;
// If 3 QR codes in a row, move to the next row
if (count % 3 === 0) {
x = marginX; // Reset X position
y += spacingY; // Move to next row
}
// If 3 QR codes in a row, move to the next row
if (count % 3 === 0) {
x = marginX; // Reset X position
y += spacingY; // Move to next row
}
// If 9 QR codes per page, add a new page
if (count % 9 === 0 && i !== arrayvalue.length - 1) {
doc.addPage();
x = marginX; // Reset X position
y = marginY; // Reset Y position
// If 9 QR codes per page, add a new page
if (count % 9 === 0 && i !== arrayvalue.length - 1) {
doc.addPage();
x = marginX; // Reset X position
y = marginY; // Reset Y position
}
}
}
// Save PDF
doc.save(`QRCode_Motos.pdf`);
$("#qrMultipleModal").modal('hide');
});
// Save PDF
doc.save(`QRCode_Motos.pdf`);
$("#qrMultipleModal").modal('hide');
});
}
// remove functions
// Remove functions avec délégation d'événements
function removeFunc(id) {
if (id) {
$("#removeForm").on('submit', function() {
$("#removeForm").off('submit').on('submit', function(e) {
e.preventDefault();
var form = $(this);
// remove the text-danger
$(".text-danger").remove();
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: {
product_id: id
},
data: { product_id: id },
dataType: 'json',
success: function(response) {
manageTable.ajax.reload(null, false);
if (response.success === true) {
$("#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>' + response.messages +
'</div>');
// hide the modal
$("#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> ' + response.messages +
'</div>'
);
$("#removeModal").modal('hide');
} else {
$("#messages").html('<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>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>');
$("#messages").html(
'<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>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>'
);
}
},
error: function(xhr, status, error) {
console.error('Error removing product:', 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 la suppression.' +
'</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({
product_id: productId,
store_id: storeId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
$("#assignStoreModal").modal("hide");
alert('Moto assigné avec succès !');
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>

2
app/Views/templates/header.php

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

25
app/Views/templates/side_menubar.php

@ -100,6 +100,31 @@
</li>
<?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 -->
<!-- fin espace commerciale -->

51
app/Views/users/index.php

@ -22,7 +22,7 @@
<?php echo session()->getFlashdata('success'); ?>
</div>
<?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>
<?php echo session()->getFlashdata('error'); ?>
</div>
@ -35,7 +35,7 @@
<?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 />
<?php endif; ?>
<div class="box">
@ -65,6 +65,7 @@
</div>
</div>
</section>
</div>
<!-- Remove Modal -->
<div class="modal fade" id="removeModal" tabindex="-1" role="dialog" aria-labelledby="removeModalLabel" aria-hidden="true">
@ -86,7 +87,7 @@
</div>
</div>
</div>
<!-- Profile Modal -->
<!-- Profile Modal -->
<div class="modal fade" id="profileModal" tabindex="-1" role="dialog" aria-labelledby="profileModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
@ -211,10 +212,6 @@
</div>
<script>
$(document).ready(function() {
$('#assignUserForm').on('submit', function (e) {
@ -231,7 +228,7 @@
dataType: 'json',
success: function (response) {
if (response.success) {
manageTable = $('#manageTable');
var manageTable = $('#manageTable');
manageTable.DataTable().ajax.reload(null, false);
$('#assignUserModal').modal('hide');
$('#messages').html(
@ -288,7 +285,7 @@
}
});
manageTable = $('#manageTable').DataTable({
var manageTable = $('#manageTable').DataTable({
'ajax': {
url: '<?= base_url('users/fetchUserData') ?>',
type: 'GET',
@ -324,7 +321,7 @@
removeUserId = button.data('id');
});
// Au clic sur “Supprimer” dans la modal
// Au clic sur "Supprimer" dans la modal
$('#confirmRemove').on('click', function() {
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
// Fonctions globales
@ -468,4 +433,4 @@
});
}
</script>
</script>
Loading…
Cancel
Save