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"; } // ✅ Utiliser la nouvelle méthode qui filtre automatiquement par rôle et store $data = $Products->getProductDataByRole(); // ✅ Debug : Logs pour vérifier le filtrage $session = session(); $user = $session->get('user'); log_message('debug', '=== fetchProductData ==='); log_message('debug', 'User: ' . ($user['username'] ?? 'N/A') . ' (Group: ' . ($user['group_name'] ?? 'N/A') . ', Store: ' . ($user['store_id'] ?? 'N/A') . ')'); log_message('debug', 'Products found: ' . count($data)); 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 ? 'En stock' : 'Rupture'; // Construction des boutons $buttons = ''; if (in_array('updateProduct', $this->permission ?? [])) { $buttons .= ''; } if (in_array('deleteProduct', $this->permission ?? [])) { $buttons .= ' '; } if (in_array('updateProduct', $this->permission ?? [])) { $buttons .= ' '; } if (in_array('updateProduct', $this->permission ?? [])) { $buttons .= ' '; } if (in_array('viewProduct', $this->permission ?? [])) { $buttons .= " "; } if (in_array('assignStore', $this->permission ?? [])) { $buttons .= sprintf( ' ', htmlspecialchars($store_name, ENT_QUOTES), (int)$value["id"] ); } $imagePath = 'assets/images/product_image/' . $value['image']; $imageHtml = $value['image'] ? '' : '
Aucune image
'; $result['data'][$key] = [ $imageHtml, 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, 'products/' ); 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() ]); } } }