motorbike/app/Controllers/HistoriqueController.php
andrymodeste fe80b9c4f8 fix: corrections et améliorations du 01-04-2026
## Recouvrement
- Liste triée par date décroissante
- Pop-up de confirmation anti-doublons
- Affichage avec permission "Voir" seule

## Mise à jour produit
- Correction erreur "Column count doesn't match" (triggers MySQL corrigés)
- Formulaire corrigé (action, catégorie, champs null)
- Catégorie et date d'arrivage correctement préremplis
- Historique affiche le nom de l'utilisateur qui modifie

## Espace commercial
- Colonne "Disponibilité" ajoutée avec statut "En attente de livraison"
- Bouton panier caché pour les motos commandées
- Surbrillance jaune pour les motos en attente

## Notifications
- Caissière reçoit les notifications via notifCommande
- notifSortieCaisse réservé à Direction/Admin

## Avances
- Colonne "N° Série" ajoutée dans toutes les listes
- Compteurs sur les boutons Incomplètes/Complètes

## Facture / BL
- Total, Remise, Total à payer affichés
- "Ariary" ne se répète plus
- Prix individuel par moto
- Impression automatique : 1 produit = Facture, 2+ = BL
- Remise multiple : colonne product changée en TEXT

## Rapports
- Filtre par date dans le rapport stock
- Filtre par commercial et mécanicien dans les performances
- Correction rapport stock (GROUP BY marque)
- Liens absolus (correction erreur 404)

## Sidebar
- Marge en haut supprimée (production)
- Padding en bas ajouté pour scroll complet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:56:34 +02:00

317 lines
9.8 KiB
PHP
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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',
$row['user_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);
}
}