You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
25 KiB
617 lines
25 KiB
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Models\Attributes;
|
|
use App\Models\Brands;
|
|
use App\Models\Category;
|
|
use App\Models\FourchettePrix;
|
|
use App\Models\Notification;
|
|
use App\Models\Products;
|
|
use App\Models\Stores;
|
|
use Config\Services;
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
|
|
|
|
class ProductCOntroller extends AdminController
|
|
{
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
helper(['form', 'url']);
|
|
}
|
|
|
|
private $pageTitle = 'Produits';
|
|
|
|
public function index()
|
|
{
|
|
$Stores = new Stores();
|
|
$this->verifyRole('viewProduct');
|
|
$data['page_title'] = $this->pageTitle;
|
|
$Product = new Products();
|
|
$data['motos'] = $Product->getActiveProductData();
|
|
$data['stores'] = $Stores->getActiveStore();
|
|
return $this->render_template('products/index', $data);
|
|
}
|
|
|
|
public function assign_store()
|
|
{
|
|
if (!$this->request->isAJAX()) {
|
|
$response = Services::response();
|
|
$response->setStatusCode(404, 'Page Not Found')->send();
|
|
exit;
|
|
}
|
|
|
|
$data = $this->request->getJSON(true);
|
|
|
|
if (!isset($data['product_id']) || !isset($data['store_id'])) {
|
|
return $this->response->setJSON([
|
|
'success' => false,
|
|
'message' => 'Paramètres manquants.'
|
|
])->setStatusCode(400);
|
|
}
|
|
|
|
$product_id = (int)$data['product_id'];
|
|
$store_id = (int)$data['store_id'];
|
|
|
|
$productsModel = new Products();
|
|
|
|
$result = $productsModel->assignToStore($product_id, $store_id);
|
|
|
|
if ($result) {
|
|
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.'
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function fetchProductData()
|
|
{
|
|
$result = ['data' => []];
|
|
$Products = new Products();
|
|
$Stores = new Stores();
|
|
|
|
function convertString($name)
|
|
{
|
|
return "$name";
|
|
}
|
|
|
|
$data = $Products->getProductData();
|
|
|
|
foreach ($data as $key => $value) {
|
|
// Gestion du nom du magasin
|
|
if ($value['store_id'] == 0) {
|
|
$store_name = "TOUS";
|
|
} else {
|
|
$store_info = $Stores->getStoresData($value['store_id']);
|
|
$store_name = $store_info && isset($store_info['name']) ? $store_info['name'] : "Inconnu";
|
|
}
|
|
|
|
// Disponibilité basée sur qty ET availability
|
|
$isInStock = ((int)$value['qty'] > 0);
|
|
$isAvailable = ((int)$value['availability'] === 1);
|
|
|
|
$isProductAvailable = $isInStock && $isAvailable;
|
|
|
|
$availability = $isProductAvailable ?
|
|
'<span class="label label-success">En stock</span>' :
|
|
'<span class="label label-danger">Rupture</span>';
|
|
|
|
// 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>';
|
|
}
|
|
|
|
if (in_array('deleteProduct', $this->permission ?? [])) {
|
|
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['id'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
|
|
}
|
|
|
|
if (in_array('updateProduct', $this->permission ?? [])) {
|
|
$buttons .= ' <a href="ventes/' . $value['id'] . '" class="btn btn-default"><i class="fa fa-image"></i></a>';
|
|
}
|
|
|
|
if (in_array('updateProduct', $this->permission ?? [])) {
|
|
$buttons .= ' <button class="btn btn-default" onclick="generateQrPdf(' . $value["id"] . ')"><i class="fa fa-qrcode"></i></button>';
|
|
}
|
|
|
|
if (in_array('viewProduct', $this->permission ?? [])) {
|
|
$buttons .= " <a href='/ventes/show/" . $value['id'] . "' class='btn btn-default'><i class='fa fa-eye'></i></a>";
|
|
}
|
|
|
|
if (in_array('assignStore', $this->permission ?? [])) {
|
|
$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'];
|
|
$imageHtml = $value['image'] ?
|
|
'<img src="' . base_url($imagePath) . '" width="50" height="50" class="img-thumbnail">' :
|
|
'<div class="no-image">Aucune image</div>';
|
|
|
|
$result['data'][$key] = [
|
|
$imageHtml, // Correction : utiliser $imageHtml au lieu de $value['image']
|
|
convertString($value['sku']),
|
|
$value['name'],
|
|
$value['prix_vente'],
|
|
$store_name,
|
|
$availability,
|
|
$buttons
|
|
];
|
|
}
|
|
|
|
return $this->response->setJSON($result);
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
$Products = new Products();
|
|
$Brands = new Brands();
|
|
$Category = new Category();
|
|
$Stores = new Stores();
|
|
$Notification = new NotificationController();
|
|
$Fourchette = new \App\Models\FourchettePrix();
|
|
$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()) {
|
|
|
|
$prix_min = (float)$this->request->getPost('price_min');
|
|
$prix_vente = (float)$this->request->getPost('price_vente');
|
|
|
|
// --- Vérification côté serveur ---
|
|
if ($prix_min > $prix_vente) {
|
|
session()->setFlashdata('errors', 'Le prix de vente ne peut pas être inférieur au prix minimal.');
|
|
return redirect()->to('/products/create')->withInput();
|
|
}
|
|
|
|
$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;
|
|
}
|
|
}
|
|
|
|
$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,
|
|
'prix_vente' => $prix_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' => 0,
|
|
'type' => $this->request->getPost('type')
|
|
];
|
|
|
|
$store_id1 = (int)$this->request->getPost('store');
|
|
|
|
$productId = $Products->insert($data);
|
|
if ($productId) {
|
|
$Fourchette->createFourchettePrix([
|
|
'product_id' => $productId,
|
|
'prix_minimal' => $prix_min,
|
|
]);
|
|
|
|
session()->setFlashdata('success', 'Créé avec succès');
|
|
$Notification->createNotification(
|
|
"Un nouveau Produit a été créé",
|
|
"COMMERCIALE",
|
|
$store_id1,
|
|
'product/'
|
|
);
|
|
|
|
return redirect()->to('/products');
|
|
} else {
|
|
session()->setFlashdata('errors', 'Erreur lors de la création du produit');
|
|
return redirect()->to('products/create')->withInput();
|
|
}
|
|
} 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()
|
|
{
|
|
$uploadPath = 'assets/images/product_image';
|
|
|
|
if (!is_dir($uploadPath)) {
|
|
mkdir($uploadPath, 0777, true);
|
|
}
|
|
|
|
$file = $this->request->getFile('product_image');
|
|
if ($file && $file->isValid() && !$file->hasMoved()) {
|
|
$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();
|
|
|
|
if ($file->move($uploadPath, $newName)) {
|
|
return $newName;
|
|
} else {
|
|
log_message('error', 'Erreur lors du déplacement du fichier');
|
|
return '';
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function update(int $id)
|
|
{
|
|
$Products = new Products();
|
|
$Stores = new Stores();
|
|
$Category = new Category();
|
|
$Brands = new Brands();
|
|
$FourchettePrix = new \App\Models\FourchettePrix(); // Modèle FourchettePrix
|
|
|
|
$this->verifyRole('updateProduct');
|
|
|
|
$validation = \Config\Services::validation();
|
|
$validation->setRules([
|
|
'nom_de_produit' => 'required',
|
|
'marque' => 'required',
|
|
]);
|
|
|
|
// Récupérer produit et prix minimal
|
|
$product_data = $Products->getProductData($id);
|
|
$prix_minimal_data = $FourchettePrix->where('product_id', $id)->first();
|
|
$prix_minimal = $prix_minimal_data['prix_minimal'] ?? '';
|
|
|
|
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,
|
|
'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'),
|
|
];
|
|
|
|
// ---- GESTION PRIX MINIMAL ----
|
|
$prix_minimal_saisi = (float)$this->request->getPost('price_min');
|
|
$prix_vente = (float)$this->request->getPost('price_vente');
|
|
|
|
// Vérification serveur : prix minimal <= prix de vente
|
|
if ($prix_minimal_saisi > 0 && $prix_vente < $prix_minimal_saisi) {
|
|
session()->setFlashdata('errors', 'Le prix de vente ne peut pas être inférieur au prix minimal.');
|
|
return redirect()->to('/products/update/' . $id)->withInput();
|
|
}
|
|
|
|
// Sauvegarde ou mise à jour du prix minimal
|
|
if ($prix_minimal_data) {
|
|
$FourchettePrix->update($prix_minimal_data['id'], [
|
|
'prix_minimal' => $prix_minimal_saisi
|
|
]);
|
|
} else {
|
|
$FourchettePrix->insert([
|
|
'product_id' => $id,
|
|
'prix_minimal' => $prix_minimal_saisi
|
|
]);
|
|
}
|
|
|
|
// Upload image si fournie
|
|
if ($this->request->getFile('product_image')->isValid()) {
|
|
$uploadImage = $this->uploadImage();
|
|
$Products->update($id, ['image' => $uploadImage]);
|
|
}
|
|
|
|
// Mise à jour du produit
|
|
if ($Products->updateProduct($data, $id)) {
|
|
session()->setFlashdata('success', 'Produit mis à jour avec succès.');
|
|
return redirect()->to('/products');
|
|
} else {
|
|
session()->setFlashdata('errors', 'Une erreur est survenue lors de la mise à jour.');
|
|
return redirect()->to('/products/update/' . $id);
|
|
}
|
|
|
|
} else {
|
|
// Affichage du formulaire
|
|
$data = [
|
|
'stores' => $Stores->getActiveStore(),
|
|
'validation' => $validation,
|
|
'page_title' => $this->pageTitle,
|
|
'product_data' => $product_data,
|
|
'categorie' => $Category->getActiveCategory(),
|
|
'marque' => $Brands->getActiveBrands(),
|
|
'prix_minimal' => $prix_minimal
|
|
];
|
|
|
|
return $this->render_template('products/editbackup', $data);
|
|
}
|
|
}
|
|
|
|
|
|
public function remove()
|
|
{
|
|
$this->verifyRole('deleteProduct');
|
|
$product_id = $this->request->getPost('product_id');
|
|
$response = [];
|
|
$Products = new Products();
|
|
|
|
if ($product_id) {
|
|
if ($Products->remove($product_id)) {
|
|
$response['success'] = true;
|
|
$response['messages'] = "Successfully removed";
|
|
} else {
|
|
$response['success'] = false;
|
|
$response['messages'] = "Error in the database while removing the product information";
|
|
}
|
|
} else {
|
|
$response['success'] = false;
|
|
$response['messages'] = "Refersh the page again!!";
|
|
}
|
|
return $this->response->setJSON($response);
|
|
}
|
|
|
|
public function createByExcel()
|
|
{
|
|
$this->verifyRole("createProduct");
|
|
|
|
try {
|
|
$file = $this->request->getFile('excel_product');
|
|
if (!$file || !$file->isValid()) {
|
|
return $this->response->setJSON([
|
|
'success' => false,
|
|
'messages' => "Fichier invalide ou non reçu"
|
|
]);
|
|
}
|
|
|
|
$ext = strtolower($file->getClientExtension());
|
|
if (!in_array($ext, ['xls', 'xlsx'])) {
|
|
return $this->response->setJSON([
|
|
'success' => false,
|
|
'messages' => "Seuls les fichiers Excel (.xls, .xlsx) sont acceptés"
|
|
]);
|
|
}
|
|
|
|
$spreadsheet = IOFactory::load($file->getTempName());
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
$rows = $sheet->toArray();
|
|
|
|
if (count($rows) <= 1) {
|
|
return $this->response->setJSON([
|
|
'success' => false,
|
|
'messages' => "Le fichier ne contient pas de données"
|
|
]);
|
|
}
|
|
|
|
$headers = array_shift($rows);
|
|
$headers = array_map('strtolower', $headers);
|
|
|
|
$columnMapping = [
|
|
'n° série' => 'sku',
|
|
'marque' => 'marque',
|
|
'désignation' => 'name',
|
|
'fournisseur' => 'info',
|
|
'date d\'arrivage' => 'date_arivage',
|
|
'n° moteur' => 'numero_de_moteur',
|
|
'châssis' => 'chasis',
|
|
'puissance' => 'puissance',
|
|
'clé' => 'cler',
|
|
'prix d\'achat' => 'price',
|
|
'prix ar' => 'prix_vente',
|
|
'catégories' => 'categorie_id',
|
|
'prix minimale' => 'prix_minimal',
|
|
'magasin' => 'store_id',
|
|
'disponibilité' => 'availability',
|
|
'état' => 'etats',
|
|
'pièce manquant' => 'infoManque',
|
|
'type' => 'type',
|
|
];
|
|
|
|
$ProductsModel = new Products();
|
|
$BrandsModel = new Brands();
|
|
$StoresModel = new Stores();
|
|
$CategoryModel = new Category();
|
|
|
|
$db = \Config\Database::connect();
|
|
$db->query("SET @IMPORT_MODE = 1");
|
|
|
|
$countInserted = 0;
|
|
$errors = [];
|
|
|
|
$db->transStart();
|
|
|
|
try {
|
|
foreach ($rows as $rowIndex => $row) {
|
|
if (empty(array_filter($row))) continue;
|
|
|
|
$data = [
|
|
'is_piece' => 0,
|
|
'product_sold' => 0,
|
|
'qty' => 1
|
|
];
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
$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' => $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()
|
|
]);
|
|
}
|
|
}
|
|
|
|
}
|