11092025
This commit is contained in:
parent
0bffbd1295
commit
e9ed02267c
@ -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
Normal file
315
app/Controllers/HistoriqueController.php
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
$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;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
$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)) {
|
||||
$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;
|
||||
|
||||
// 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]);
|
||||
$data = [
|
||||
'is_piece' => 0,
|
||||
'product_sold' => 0,
|
||||
'qty' => 1
|
||||
];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
// Insertion
|
||||
if (!empty($data['name'])) {
|
||||
if ($ProductsModel->insert($data)) {
|
||||
$countInserted++;
|
||||
}
|
||||
$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
Normal file
183
app/Models/Historique.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -326,6 +326,7 @@
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
var base_url = "<?= base_url() ?>", brutCreate = 0, brutEdit = 0;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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
Normal file
433
app/Views/historique/index.php
Normal file
@ -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">×</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">×</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 :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Affichage de l'élement 0 à 0 sur 0 élément",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun élément à afficher",
|
||||
sEmptyTable: "Aucune donnée disponible dans le tableau",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
},
|
||||
oAria: {
|
||||
sSortAscending: ": activer pour trier la colonne par ordre croissant",
|
||||
sSortDescending: ": activer pour trier la colonne par ordre dé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>
|
||||
@ -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;
|
||||
const form = inputFile ? inputFile.form : null;
|
||||
|
||||
inputFile.addEventListener('change', function () {
|
||||
if (this.files.length) {
|
||||
const formData = new FormData(form);
|
||||
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);
|
||||
}
|
||||
$.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">×</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');
|
||||
|
||||
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">×</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">×</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">×</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 :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Affichage de l'élement 0 à 0 sur 0 élément",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun élément à afficher",
|
||||
sEmptyTable: "Aucune donnée disponible dans le tableau",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
},
|
||||
oAria: {
|
||||
sSortAscending: ": activer pour trier la colonne par ordre croissant",
|
||||
sSortDescending: ": activer pour trier la colonne par ordre décroissant"
|
||||
}
|
||||
}
|
||||
});
|
||||
language: {
|
||||
sProcessing: "Traitement en cours...",
|
||||
sSearch: "Rechercher :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de l'élement _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Affichage de l'élement 0 à 0 sur 0 élément",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun élément à afficher",
|
||||
sEmptyTable: "Aucune donnée disponible dans le tableau",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
},
|
||||
oAria: {
|
||||
sSortAscending: ": activer pour trier la colonne par ordre croissant",
|
||||
sSortDescending: ": activer pour trier la colonne par ordre dé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">×</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">×</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">×</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>
|
||||
}));
|
||||
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
|
||||
console.log(arrayvalue); // Debugging: Check selected values in console
|
||||
|
||||
const {
|
||||
jsPDF
|
||||
} = window.jspdf;
|
||||
const doc = new jsPDF();
|
||||
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
|
||||
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;
|
||||
let x = marginX;
|
||||
let y = marginY;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < arrayvalue.length; i++) {
|
||||
let moto = arrayvalue[i];
|
||||
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);
|
||||
// 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
|
||||
});
|
||||
// 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">×</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">×</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">×</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">×</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">×</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>
|
||||
@ -106,6 +106,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="hold-transition skin-blue sidebar-mini">
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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">×</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…
Reference in New Issue
Block a user