21122025
This commit is contained in:
parent
cb703c30f6
commit
b1ef2963b6
@ -217,18 +217,56 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
/**
|
||||
* route for the reports
|
||||
*/
|
||||
$routes->group('/reports', function ($routes) {
|
||||
|
||||
$routes->group('reports', function ($routes) {
|
||||
|
||||
$routes->get('/', [ReportController::class, 'index']);
|
||||
$routes->post('/', [ReportController::class, 'index']);
|
||||
|
||||
$routes->get('detail/stock', [ReportController::class, 'stockDetail']);
|
||||
$routes->get('detail/fetctData/(:num)', [ReportController::class, 'fetchProductSodled']);
|
||||
$routes->get('detail/fetctDataStock/(:num)', [ReportController::class, 'fetchProductStock']);
|
||||
$routes->get('detail/fetctDataStock2/(:num)', [ReportController::class, 'fetchProductStock2']);
|
||||
$routes->get('detail/performance', [ReportController::class, 'performancedetail']);
|
||||
|
||||
// Corrections fetct → fetch
|
||||
$routes->get('detail/fetchData/(:num)', [ReportController::class, 'fetchProductSold/$1']);
|
||||
$routes->get('detail/fetchDataStock/(:num)', [ReportController::class, 'fetchProductStock/$1']);
|
||||
$routes->get('detail/fetchDataStock2/(:num)', [ReportController::class, 'fetchProductStock2/$1']);
|
||||
|
||||
$routes->get('detail/performance', [ReportController::class, 'performanceDetail']);
|
||||
$routes->get('detail/fetchPerformances', [ReportController::class, 'fetchPerformances']);
|
||||
$routes->get('detail/fetchCaissierPerformances', [ReportController::class, 'fetchCaissierPerformances']);
|
||||
});
|
||||
|
||||
// Sécurité (correction du chemin et du contrôleur)
|
||||
$routes->get('detail/fetchSecuritePerformances', [ReportController::class, 'fetchSecuritePerformances']);
|
||||
$routes->get('detail/getSecuriteValidationDetails/(:num)', [ReportController::class, 'getSecuriteValidationDetails']);
|
||||
|
||||
$routes->get('securite-history', [ReportController::class, 'securiteHistory'], ['filter' => 'auth']);
|
||||
});
|
||||
|
||||
//autres encaissement
|
||||
$routes->group('encaissements', ['filter' => 'auth'], function($routes) {
|
||||
// Page principale
|
||||
$routes->get('/', 'AutresEncaissementsController::index');
|
||||
|
||||
// Créer un encaissement
|
||||
$routes->post('create', 'AutresEncaissementsController::create');
|
||||
|
||||
// Récupérer les données pour DataTables
|
||||
$routes->get('fetch', 'AutresEncaissementsController::fetchEncaissements');
|
||||
|
||||
// Détails d'un encaissement
|
||||
$routes->get('details/(:num)', 'AutresEncaissementsController::getDetails/$1');
|
||||
|
||||
// Récupérer un encaissement pour édition
|
||||
$routes->get('getEncaissement/(:num)', 'AutresEncaissementsController::getEncaissement/$1');
|
||||
|
||||
// Modifier un encaissement
|
||||
$routes->post('update/(:num)', 'AutresEncaissementsController::update/$1');
|
||||
|
||||
// Supprimer un encaissement
|
||||
$routes->post('delete/(:num)', 'AutresEncaissementsController::delete/$1');
|
||||
|
||||
// Statistiques
|
||||
$routes->get('statistics', 'AutresEncaissementsController::getStatistics');
|
||||
});
|
||||
|
||||
/**
|
||||
* route for the company
|
||||
@ -307,41 +345,48 @@ $routes->group('/sortieCaisse', function ($routes) {
|
||||
// avance
|
||||
// ✅ DANS app/Config/Routes.php
|
||||
|
||||
$routes->group('/avances', function ($routes) {
|
||||
$routes->group('avances', function ($routes) {
|
||||
// Page principale
|
||||
$routes->get('/', [AvanceController::class, 'index']);
|
||||
|
||||
// Routes pour récupérer les données (GET)
|
||||
// ✅ CORRECTION : Utiliser POST pour fetchPendingValidation (cohérent avec DataTables AJAX)
|
||||
$routes->get('fetchPendingValidation', [AvanceController::class, 'fetchPendingValidation']);
|
||||
$routes->post('fetchPendingValidation', [AvanceController::class, 'fetchPendingValidation']);
|
||||
|
||||
// GET : Récupération des données
|
||||
$routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']);
|
||||
$routes->get('fetchAvanceBecameOrder', [AvanceController::class, 'fetchAvanceBecameOrder']);
|
||||
$routes->get('fetchExpiredAvance', [AvanceController::class, 'fetchExpiredAvance']);
|
||||
|
||||
// Routes pour une avance spécifique
|
||||
// GET : Avance spécifique
|
||||
$routes->get('fetchSingleAvance/(:num)', [AvanceController::class, 'fetchSingleAvance/$1']);
|
||||
$routes->get('getInvoicePreview/(:num)', [AvanceController::class, 'getInvoicePreview/$1']);
|
||||
$routes->get('getFullInvoiceForPrint/(:num)', [AvanceController::class, 'getFullInvoiceForPrint/$1']);
|
||||
$routes->get('printInvoice/(:num)', [AvanceController::class, 'printInvoice/$1']);
|
||||
|
||||
// Routes POST pour modifications
|
||||
// POST : Création / Modifications
|
||||
$routes->post('createAvance', [AvanceController::class, 'createAvance']);
|
||||
$routes->post('updateAvance', [AvanceController::class, 'updateAvance']);
|
||||
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
|
||||
$routes->post('notifyPrintInvoice', [AvanceController::class, 'notifyPrintInvoice']);
|
||||
$routes->post('processExpiredAvances', [AvanceController::class, 'processExpiredAvances']);
|
||||
|
||||
// ✅ CORRECTION : Routes pour paiement et conversion
|
||||
// Paiement
|
||||
$routes->post('payAvance', [AvanceController::class, 'payAvance']);
|
||||
|
||||
// ✅ AJOUT : Routes GET ET POST pour la conversion manuelle
|
||||
// Conversion manuelle
|
||||
$routes->get('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']);
|
||||
$routes->post('checkAndConvertCompleted', [AvanceController::class, 'checkAndConvertCompleted']);
|
||||
|
||||
// Route pour forcer la conversion d'une avance spécifique
|
||||
// Forcer conversion
|
||||
$routes->get('forceConvertToOrder/(:num)', [AvanceController::class, 'forceConvertToOrder/$1']);
|
||||
|
||||
// Route CRON (optionnel)
|
||||
// CRON (alertes deadline)
|
||||
$routes->get('checkDeadlineAlerts', [AvanceController::class, 'checkDeadlineAlerts']);
|
||||
|
||||
|
||||
|
||||
// Validation d'avance
|
||||
$routes->get('validateAvance', [AvanceController::class, 'validateAvance']);
|
||||
$routes->post('validateAvance', [AvanceController::class, 'validateAvance']);
|
||||
});
|
||||
|
||||
// historique
|
||||
|
||||
571
app/Controllers/AutresEncaissementsController.php
Normal file
571
app/Controllers/AutresEncaissementsController.php
Normal file
@ -0,0 +1,571 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\AutresEncaissements;
|
||||
use App\Models\Stores;
|
||||
use App\Models\Users;
|
||||
|
||||
class AutresEncaissementsController extends AdminController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
private $pageTitle = 'Autres Encaissements';
|
||||
|
||||
/**
|
||||
* Page principale - Liste des encaissements
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->verifyRole('viewEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$data['page_title'] = $this->pageTitle;
|
||||
$data['user_role'] = $user['group_name'];
|
||||
$data['user_permission'] = $this->permission;
|
||||
|
||||
// Récupérer les magasins pour le filtre
|
||||
$storeModel = new Stores();
|
||||
$data['stores'] = $storeModel->getActiveStore();
|
||||
|
||||
return $this->render_template('autres_encaissements/index', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajouter un encaissement
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if ($this->request->getMethod() !== 'post') {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Méthode non autorisée'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->verifyRole('createEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
// Validation des données
|
||||
$validation = \Config\Services::validation();
|
||||
$validation->setRules([
|
||||
'type_encaissement' => 'required',
|
||||
'montant' => 'required|decimal',
|
||||
'mode_paiement' => 'required|in_list[Espèces,MVola,Virement Bancaire]',
|
||||
'commentaire' => 'permit_empty|string'
|
||||
]);
|
||||
|
||||
if (!$validation->withRequest($this->request)->run()) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => $validation->getErrors()
|
||||
]);
|
||||
}
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
$Notification = new NotificationController();
|
||||
|
||||
$typeEncaissement = $this->request->getPost('type_encaissement');
|
||||
$autreType = $this->request->getPost('autre_type');
|
||||
|
||||
// Si "Autre" est sélectionné, utiliser le champ texte
|
||||
$finalType = ($typeEncaissement === 'Autre' && !empty($autreType))
|
||||
? $autreType
|
||||
: $typeEncaissement;
|
||||
|
||||
$data = [
|
||||
'type_encaissement' => $finalType,
|
||||
'autre_type' => $autreType,
|
||||
'montant' => $this->request->getPost('montant'),
|
||||
'mode_paiement' => $this->request->getPost('mode_paiement'),
|
||||
'commentaire' => $this->request->getPost('commentaire'),
|
||||
'user_id' => $user['id'],
|
||||
'store_id' => $user['store_id']
|
||||
];
|
||||
|
||||
if ($encaissementId = $encaissementModel->insert($data)) {
|
||||
|
||||
// ✅ Récupérer tous les stores pour les notifications
|
||||
$db = \Config\Database::connect();
|
||||
$storesQuery = $db->table('stores')->select('id')->get();
|
||||
$allStores = $storesQuery->getResultArray();
|
||||
|
||||
$montantFormate = number_format((float)$this->request->getPost('montant'), 0, ',', ' ');
|
||||
|
||||
$notificationMessage = "Nouvel encaissement {$finalType} créé par {$user['firstname']} {$user['lastname']} - Montant: {$montantFormate} Ar";
|
||||
|
||||
// ✅ Envoyer notification à DAF, Direction et SuperAdmin de TOUS les stores
|
||||
foreach ($allStores as $store) {
|
||||
$storeId = (int)$store['id'];
|
||||
|
||||
// Notification pour DAF
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"DAF",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
// Notification pour Direction
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"Direction",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
// Notification pour SuperAdmin
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"SuperAdmin",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
}
|
||||
|
||||
log_message('info', "✅ Encaissement {$encaissementId} créé - Notifications envoyées à DAF/Direction/SuperAdmin de tous les stores");
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => 'Encaissement enregistré avec succès'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur lors de l\'enregistrement'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "Erreur création encaissement: " . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Une erreur interne est survenue: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier un encaissement
|
||||
*/
|
||||
public function update($id)
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('updateEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
$Notification = new NotificationController();
|
||||
|
||||
// Récupérer l'ancien encaissement pour comparaison
|
||||
$oldEncaissement = $encaissementModel->find($id);
|
||||
|
||||
if (!$oldEncaissement) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Encaissement non trouvé'
|
||||
]);
|
||||
}
|
||||
|
||||
$typeEncaissement = $this->request->getPost('type_encaissement');
|
||||
$autreType = $this->request->getPost('autre_type');
|
||||
|
||||
$finalType = ($typeEncaissement === 'Autre' && !empty($autreType))
|
||||
? $autreType
|
||||
: $typeEncaissement;
|
||||
|
||||
$data = [
|
||||
'type_encaissement' => $finalType,
|
||||
'autre_type' => $autreType,
|
||||
'montant' => $this->request->getPost('montant'),
|
||||
'mode_paiement' => $this->request->getPost('mode_paiement'),
|
||||
'commentaire' => $this->request->getPost('commentaire')
|
||||
];
|
||||
|
||||
if ($encaissementModel->update($id, $data)) {
|
||||
|
||||
// ✅ Envoyer notification de modification à tous les stores
|
||||
$db = \Config\Database::connect();
|
||||
$storesQuery = $db->table('stores')->select('id')->get();
|
||||
$allStores = $storesQuery->getResultArray();
|
||||
|
||||
$montantFormate = number_format((float)$this->request->getPost('montant'), 0, ',', ' ');
|
||||
$ancienMontant = number_format((float)$oldEncaissement['montant'], 0, ',', ' ');
|
||||
|
||||
$notificationMessage = "Encaissement {$finalType} modifié par {$user['firstname']} {$user['lastname']} - Ancien montant: {$ancienMontant} Ar → Nouveau: {$montantFormate} Ar";
|
||||
|
||||
foreach ($allStores as $store) {
|
||||
$storeId = (int)$store['id'];
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"DAF",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"Direction",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"SuperAdmin",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
}
|
||||
|
||||
log_message('info', "✅ Encaissement {$id} modifié - Notifications envoyées");
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => 'Encaissement modifié avec succès'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur lors de la modification'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur dans update(): ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur serveur: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer un encaissement
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('deleteEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
$Notification = new NotificationController();
|
||||
|
||||
// Récupérer l'encaissement avant suppression pour la notification
|
||||
$encaissement = $encaissementModel->find($id);
|
||||
|
||||
if (!$encaissement) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Encaissement non trouvé'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($encaissementModel->delete($id)) {
|
||||
|
||||
// ✅ Envoyer notification de suppression à tous les stores
|
||||
$db = \Config\Database::connect();
|
||||
$storesQuery = $db->table('stores')->select('id')->get();
|
||||
$allStores = $storesQuery->getResultArray();
|
||||
|
||||
$montantFormate = number_format((float)$encaissement['montant'], 0, ',', ' ');
|
||||
$type = $encaissement['type_encaissement'];
|
||||
|
||||
$notificationMessage = "⚠️ Encaissement {$type} supprimé par {$user['firstname']} {$user['lastname']} - Montant: {$montantFormate} Ar";
|
||||
|
||||
foreach ($allStores as $store) {
|
||||
$storeId = (int)$store['id'];
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"DAF",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"Direction",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$notificationMessage,
|
||||
"SuperAdmin",
|
||||
$storeId,
|
||||
'encaissements'
|
||||
);
|
||||
}
|
||||
|
||||
log_message('info', "✅ Encaissement {$id} supprimé - Notifications envoyées");
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => 'Encaissement supprimé avec succès'
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur lors de la suppression'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur dans delete(): ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur serveur: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les données pour DataTables
|
||||
*/
|
||||
public function fetchEncaissements()
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('viewEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
|
||||
// Récupérer les filtres
|
||||
$startDate = $this->request->getGet('startDate');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$storeId = $this->request->getGet('store_id');
|
||||
|
||||
// Si l'utilisateur n'est pas admin, limiter à son magasin
|
||||
if (!in_array($user['group_name'], ['SuperAdmin', 'DAF', 'Direction'])) {
|
||||
$storeId = $user['store_id'];
|
||||
}
|
||||
|
||||
$encaissements = $encaissementModel->getEncaissementsWithDetails($storeId);
|
||||
|
||||
// Filtrer par dates
|
||||
if ($startDate || $endDate) {
|
||||
$encaissements = array_filter($encaissements, function($item) use ($startDate, $endDate) {
|
||||
$itemDate = date('Y-m-d', strtotime($item['created_at']));
|
||||
|
||||
if ($startDate && $itemDate < $startDate) {
|
||||
return false;
|
||||
}
|
||||
if ($endDate && $itemDate > $endDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($encaissements as $item) {
|
||||
// Construire les boutons avec permissions
|
||||
$buttons = '';
|
||||
|
||||
if (in_array('viewEncaissement', $this->permission)) {
|
||||
$buttons .= '<button class="btn btn-info btn-sm" onclick="viewDetails(' . $item['id'] . ')">
|
||||
<i class="fa fa-eye"></i> Détails
|
||||
</button> ';
|
||||
}
|
||||
|
||||
if (in_array('updateEncaissement', $this->permission)) {
|
||||
$buttons .= '<button class="btn btn-warning btn-sm" onclick="editEncaissement(' . $item['id'] . ')">
|
||||
<i class="fa fa-edit"></i>
|
||||
</button> ';
|
||||
}
|
||||
|
||||
if (in_array('deleteEncaissement', $this->permission)) {
|
||||
$buttons .= '<button class="btn btn-danger btn-sm" onclick="deleteEncaissement(' . $item['id'] . ')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>';
|
||||
}
|
||||
|
||||
// ✅ Badge coloré pour le mode de paiement
|
||||
$badgeClass = match($item['mode_paiement']) {
|
||||
'Espèces' => 'success',
|
||||
'MVola' => 'warning',
|
||||
'Virement Bancaire' => 'info',
|
||||
default => 'default'
|
||||
};
|
||||
|
||||
$modePaiementBadge = '<span class="label label-' . $badgeClass . '">' . $item['mode_paiement'] . '</span>';
|
||||
|
||||
$result[] = [
|
||||
$item['id'],
|
||||
$item['type_encaissement'],
|
||||
number_format($item['montant'], 0, '.', ' ') . ' Ar',
|
||||
$modePaiementBadge, // ✅ NOUVELLE COLONNE
|
||||
$item['commentaire'] ?: 'Aucun',
|
||||
$item['user_name'] ?: 'N/A',
|
||||
$item['store_name'] ?: 'N/A',
|
||||
date('d/m/Y H:i', strtotime($item['created_at'])),
|
||||
$buttons
|
||||
];
|
||||
}
|
||||
|
||||
return $this->response->setJSON(['data' => $result]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur dans fetchEncaissements(): ' . $e->getMessage());
|
||||
return $this->response->setJSON(['data' => []]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les détails d'un encaissement
|
||||
*/
|
||||
public function getDetails($id)
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('viewEncaissement');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
$encaissement = $encaissementModel->getEncaissementById($id);
|
||||
|
||||
if ($encaissement) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'data' => $encaissement
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Encaissement non trouvé'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur dans getDetails(): ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Erreur serveur'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer un encaissement pour édition
|
||||
*/
|
||||
public function getEncaissement($id)
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('updateEncaissement');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
$encaissement = $encaissementModel->find($id);
|
||||
|
||||
if ($encaissement) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'data' => $encaissement
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Encaissement non trouvé'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur dans getEncaissement(): ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'message' => 'Erreur serveur'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les statistiques
|
||||
*/
|
||||
public function getStatistics()
|
||||
{
|
||||
try {
|
||||
$this->verifyRole('viewEncaissement');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$encaissementModel = new AutresEncaissements();
|
||||
|
||||
$startDate = $this->request->getGet('startDate');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$storeId = $this->request->getGet('store_id');
|
||||
|
||||
// ✅ LOG pour débogage
|
||||
log_message('info', "📊 getStatistics appelé - Dates: {$startDate} à {$endDate}, Store: {$storeId}");
|
||||
|
||||
// Si l'utilisateur n'est pas admin, limiter à son magasin
|
||||
if (!in_array($user['group_name'], ['SuperAdmin', 'DAF', 'Direction'])) {
|
||||
$storeId = $user['store_id'];
|
||||
}
|
||||
|
||||
// ✅ IMPORTANT : Convertir $storeId en NULL si vide
|
||||
$storeIdFilter = (!empty($storeId) && $storeId !== '') ? (int)$storeId : null;
|
||||
|
||||
// Récupérer les totaux
|
||||
$totalMontant = $encaissementModel->getTotalEncaissements($storeIdFilter, $startDate, $endDate);
|
||||
$totalCount = $encaissementModel->getTotalCount($storeIdFilter, $startDate, $endDate);
|
||||
$todayCount = $encaissementModel->getTodayCount($storeIdFilter);
|
||||
|
||||
// Récupérer les totaux par mode de paiement
|
||||
$totauxParMode = $encaissementModel->getTotalEncaissementsByMode($storeIdFilter, $startDate, $endDate);
|
||||
|
||||
// Statistiques par type
|
||||
$statsByType = $encaissementModel->getStatsByType($storeIdFilter, $startDate, $endDate);
|
||||
|
||||
// ✅ LOG pour vérifier les valeurs
|
||||
log_message('info', "✅ Résultats - Total: {$totalMontant}, Count: {$totalCount}, Today: {$todayCount}");
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'total_montant' => number_format($totalMontant, 0, ',', ' ') . ' Ar',
|
||||
'total_count' => $totalCount,
|
||||
'today_count' => $todayCount,
|
||||
'total_espece' => number_format($totauxParMode['total_espece'], 0, ',', ' ') . ' Ar',
|
||||
'total_mvola' => number_format($totauxParMode['total_mvola'], 0, ',', ' ') . ' Ar',
|
||||
'total_virement' => number_format($totauxParMode['total_virement'], 0, ',', ' ') . ' Ar',
|
||||
'stats_by_type' => $statsByType,
|
||||
// ✅ Ajouter ces infos pour débogage
|
||||
'debug' => [
|
||||
'store_id' => $storeIdFilter,
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', '❌ Erreur dans getStatistics(): ' . $e->getMessage());
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'total_montant' => '0 Ar',
|
||||
'total_count' => 0,
|
||||
'today_count' => 0,
|
||||
'total_espece' => '0 Ar',
|
||||
'total_mvola' => '0 Ar',
|
||||
'total_virement' => '0 Ar',
|
||||
'stats_by_type' => [],
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,10 +43,11 @@ class AvanceController extends AdminController
|
||||
{
|
||||
return in_array($user['group_name'], ['Caissière']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier la méthode buildActionButtons pour ajouter l'icône œil pour la Direction
|
||||
*/
|
||||
private function buildActionButtons($value, $isAdmin, $isOwner, $isCaissier = false)
|
||||
private function buildActionButtons($value, $isAdmin, $isOwner, $isCaissier = false, $isCommercial = false)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
@ -54,28 +55,58 @@ private function buildActionButtons($value, $isAdmin, $isOwner, $isCaissier = fa
|
||||
|
||||
$buttons = '';
|
||||
|
||||
// ✅ Bouton Voir pour Caissière (toujours visible)
|
||||
if ($isCaissier && in_array('viewAvance', $this->permission)) {
|
||||
$buttons .= '<button type="button" class="btn btn-info" onclick="viewFunc(' . $value['avance_id'] . ')" title="Voir et imprimer la facture" data-user-role="caissiere">'
|
||||
// ✅ Badge de statut validation (pour le commercial uniquement)
|
||||
if ($isCommercial && $value['validated'] == 0) {
|
||||
$buttons .= '<span class="badge badge-warning" style="margin-right: 5px;">En attente validation</span>';
|
||||
}
|
||||
|
||||
// ✅ BOUTON VOIR - Toujours visible pour tout le monde
|
||||
if (in_array('viewAvance', $this->permission)) {
|
||||
$title = $isCaissier ? 'Voir et imprimer la facture' : 'Voir la facture';
|
||||
$buttons .= '<button type="button" class="btn btn-info" onclick="viewFunc(' . $value['avance_id'] . ')" title="' . $title . '">'
|
||||
. '<i class="fa fa-eye"></i></button> ';
|
||||
}
|
||||
|
||||
// ✅ Bouton Voir pour Direction (toujours visible, sans impression)
|
||||
if ($isDirection && in_array('viewAvance', $this->permission) && !$isCaissier) {
|
||||
$buttons .= '<button type="button" class="btn btn-info" onclick="viewFunc(' . $value['avance_id'] . ')" title="Voir la facture" data-user-role="direction">'
|
||||
. '<i class="fa fa-eye"></i></button> ';
|
||||
// ✅ BOUTON MODIFIER
|
||||
// Commercial : peut modifier uniquement ses avances non validées
|
||||
// Caissière : peut modifier toutes les avances validées de son store
|
||||
// Admin : peut tout modifier
|
||||
if (in_array('updateAvance', $this->permission)) {
|
||||
$canEdit = false;
|
||||
|
||||
if ($isAdmin) {
|
||||
$canEdit = true; // Admin peut tout modifier
|
||||
} elseif ($isCommercial && $isOwner && $value['validated'] == 0) {
|
||||
$canEdit = true; // Commercial modifie ses avances non validées
|
||||
} elseif ($isCaissier && $value['validated'] == 1) {
|
||||
$canEdit = true; // Caissière modifie les avances validées
|
||||
}
|
||||
|
||||
if ($canEdit) {
|
||||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['avance_id'] . ')" title="Modifier">'
|
||||
. '<i class="fa fa-pencil"></i></button> ';
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ MODIFIÉ : Bouton Modifier - Le caissier peut maintenant modifier toutes les avances
|
||||
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner || $isCaissier)) {
|
||||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['avance_id'] . ')" title="Modifier">'
|
||||
. '<i class="fa fa-pencil"></i></button> ';
|
||||
}
|
||||
|
||||
// ✅ MODIFIÉ : Bouton Supprimer - Le caissier peut maintenant supprimer toutes les avances
|
||||
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner || $isCaissier)) {
|
||||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')" title="Supprimer">'
|
||||
. '<i class="fa fa-trash"></i></button> ';
|
||||
// ✅ BOUTON SUPPRIMER
|
||||
// Commercial : peut supprimer uniquement ses avances non validées
|
||||
// Caissière : peut supprimer toutes les avances (validées ou non) de son store
|
||||
// Admin : peut tout supprimer
|
||||
if (in_array('deleteAvance', $this->permission)) {
|
||||
$canDelete = false;
|
||||
|
||||
if ($isAdmin) {
|
||||
$canDelete = true; // Admin peut tout supprimer
|
||||
} elseif ($isCommercial && $isOwner && $value['validated'] == 0) {
|
||||
$canDelete = true; // Commercial supprime ses avances non validées
|
||||
} elseif ($isCaissier) {
|
||||
$canDelete = true; // Caissière peut tout supprimer dans son store
|
||||
}
|
||||
|
||||
if ($canDelete) {
|
||||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . ($value['product_id'] ?? 0) . ')" title="Supprimer">'
|
||||
. '<i class="fa fa-trash"></i></button> ';
|
||||
}
|
||||
}
|
||||
|
||||
return $buttons;
|
||||
@ -85,10 +116,14 @@ private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCai
|
||||
{
|
||||
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
|
||||
|
||||
// ✅ Afficher product_name si disponible, sinon récupérer depuis la BDD
|
||||
$productName = !empty($value['product_name'])
|
||||
? $value['product_name']
|
||||
: $product->getProductNameById($value['product_id']);
|
||||
// ✅ Gestion sécurisée du nom du produit
|
||||
if ($value['type_avance'] === 'mere') {
|
||||
$productName = $value['product_name'] ?? 'Produit sur mer';
|
||||
} else {
|
||||
$productName = !empty($value['product_name'])
|
||||
? $value['product_name']
|
||||
: $product->getProductNameById($value['product_id'] ?? 0);
|
||||
}
|
||||
|
||||
if ($isAdmin) {
|
||||
return [
|
||||
@ -134,8 +169,14 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
|
||||
foreach ($data as $key => $value) {
|
||||
$isOwner = $users['id'] === $value['user_id'];
|
||||
|
||||
// ✅ MODIFIÉ : Passer $isCaissier aux boutons d'action
|
||||
$buttons = $this->buildActionButtons($value, $isAdmin, $isOwner, $isCaissier);
|
||||
// ✅ Passer tous les indicateurs de rôle
|
||||
$buttons = $this->buildActionButtons(
|
||||
$value,
|
||||
$isAdmin,
|
||||
$isOwner,
|
||||
$isCaissier,
|
||||
$isCommerciale
|
||||
);
|
||||
|
||||
$row = $this->buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCaissier, $buttons);
|
||||
|
||||
@ -147,19 +188,169 @@ private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function fetchAvanceData()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getIncompleteAvances');
|
||||
}
|
||||
public function fetchAvanceData()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getIncompleteAvances');
|
||||
}
|
||||
|
||||
public function fetchAvanceBecameOrder()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getCompletedAvances');
|
||||
}
|
||||
public function fetchAvanceBecameOrder()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getCompletedAvances');
|
||||
}
|
||||
|
||||
public function fetchExpiredAvance()
|
||||
public function fetchExpiredAvance()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getAllAvanceData2');
|
||||
}
|
||||
|
||||
public function fetchPendingValidation()
|
||||
{
|
||||
$this->verifyRole('viewAvance');
|
||||
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
log_message('info', '🔍 fetchPendingValidation appelée');
|
||||
log_message('info', "🔍 Rôle utilisateur : {$users['group_name']}");
|
||||
|
||||
$isCaissier = $this->isCaissier($users);
|
||||
|
||||
if (!$isCaissier) {
|
||||
log_message('warning', "⚠️ Utilisateur {$users['id']} n'est pas caissière");
|
||||
return $this->response->setJSON(['data' => []]);
|
||||
}
|
||||
|
||||
log_message('info', "✅ Utilisateur {$users['id']} est caissière du store {$users['store_id']}");
|
||||
|
||||
$Avance = new Avance();
|
||||
$Products = new Products();
|
||||
|
||||
$pendingAvances = $Avance->getPendingValidationAvances($users['store_id']);
|
||||
|
||||
log_message('info', "🔍 Nombre d'avances en attente trouvées : " . count($pendingAvances));
|
||||
|
||||
$result = ['data' => []];
|
||||
|
||||
foreach ($pendingAvances as $avance) {
|
||||
// Gestion sécurisée du nom du produit
|
||||
if ($avance['type_avance'] === 'mere') {
|
||||
$productName = $avance['product_name'] ?? 'Produit sur mer';
|
||||
} else {
|
||||
$productName = $Products->getProductNameById($avance['product_id'] ?? 0);
|
||||
}
|
||||
|
||||
$date_time = date('d-m-Y h:i a', strtotime($avance['avance_date']));
|
||||
|
||||
// ✅ MODIFICATION : Boutons sans le bouton "Voir" (œil)
|
||||
$actions = '<button type="button" class="btn btn-success" onclick="validateAvanceFunc(' . $avance['avance_id'] . ')" title="Valider cette avance">' .
|
||||
'<i class="fa fa-check"></i> Valider</button> ';
|
||||
|
||||
// ✅ PAS de bouton "Voir" pour les avances non validées
|
||||
// $actions .= '<button type="button" class="btn btn-info" onclick="viewFunc(' . $avance['avance_id'] . ')" title="Voir">' .
|
||||
// '<i class="fa fa-eye"></i></button> ';
|
||||
|
||||
$actions .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $avance['avance_id'] . ',' . ($avance['product_id'] ?? 0) . ')" title="Refuser/Supprimer">' .
|
||||
'<i class="fa fa-times"></i> Refuser</button>';
|
||||
|
||||
$result['data'][] = [
|
||||
$avance['avance_id'],
|
||||
$avance['customer_name'],
|
||||
$avance['customer_phone'],
|
||||
$productName,
|
||||
number_format((int)$avance['gross_amount'], 0, ',', ' '),
|
||||
number_format((int)$avance['avance_amount'], 0, ',', ' '),
|
||||
$date_time,
|
||||
$actions // ✅ Sans le bouton "Voir"
|
||||
];
|
||||
}
|
||||
|
||||
log_message('info', "✅ Retour de " . count($result['data']) . " avances en attente");
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MÉTHODE VALIDATION (déjà correcte, on la garde)
|
||||
*/
|
||||
public function validateAvance()
|
||||
{
|
||||
return $this->fetchAvanceDataGeneric('getAllAvanceData2');
|
||||
$this->verifyRole('updateAvance');
|
||||
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
if (!$this->isCaissier($users)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Seule la caissière peut valider les avances'
|
||||
]);
|
||||
}
|
||||
|
||||
$avance_id = $this->request->getPost('avance_id');
|
||||
|
||||
if (!$avance_id) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'ID avance manquant'
|
||||
]);
|
||||
}
|
||||
|
||||
$Avance = new Avance();
|
||||
$avance = $Avance->find($avance_id);
|
||||
|
||||
if (!$avance) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Avance introuvable'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($avance['store_id'] !== $users['store_id']) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Vous ne pouvez valider que les avances de votre magasin'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($Avance->validateAvance($avance_id, $users['id'])) {
|
||||
|
||||
$Notification = new NotificationController();
|
||||
$customerName = $avance['customer_name'];
|
||||
$avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT);
|
||||
|
||||
// Notifier le commercial
|
||||
$Notification->createNotification(
|
||||
"✅ Votre avance N°{$avanceNumber} pour le client {$customerName} a été validée par la caissière",
|
||||
"COMMERCIALE",
|
||||
(int)$users['store_id'],
|
||||
'avances'
|
||||
);
|
||||
|
||||
// Notifier Direction/DAF
|
||||
$Notification->createNotification(
|
||||
"La caissière a validé l'avance N°{$avanceNumber} créée par un commercial",
|
||||
"Direction",
|
||||
(int)$users['store_id'],
|
||||
'avances'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
"La caissière a validé l'avance N°{$avanceNumber} créée par un commercial",
|
||||
"DAF",
|
||||
(int)$users['store_id'],
|
||||
'avances'
|
||||
);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => 'Avance validée avec succès ! Le montant a été ajouté à la caisse.'
|
||||
]);
|
||||
} else {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Erreur lors de la validation de l\'avance'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ use App\Models\Stores;
|
||||
use App\Models\Users;
|
||||
use App\Models\Recouvrement;
|
||||
use App\Models\SortieCaisse;
|
||||
use App\Models\AutresEncaissements; // ✅ AJOUT
|
||||
|
||||
class Dashboard extends AdminController
|
||||
{
|
||||
@ -38,6 +39,21 @@ class Dashboard extends AdminController
|
||||
$sortieCaisse = new SortieCaisse();
|
||||
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
|
||||
|
||||
// ✅ AJOUT : Récupérer les autres encaissements PAR MODE DE PAIEMENT
|
||||
$autresEncaissementsModel = new AutresEncaissements();
|
||||
|
||||
// Déterminer si l'utilisateur est admin pour filtrer par store
|
||||
$isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction', 'SuperAdmin']);
|
||||
$storeIdFilter = $isAdmin ? null : $user_id['store_id'];
|
||||
|
||||
// Récupérer les totaux des autres encaissements par mode de paiement
|
||||
$totauxAutresEncaissements = $autresEncaissementsModel->getTotalEncaissementsByMode($storeIdFilter);
|
||||
|
||||
$total_autres_enc_espece = $totauxAutresEncaissements['total_espece'];
|
||||
$total_autres_enc_mvola = $totauxAutresEncaissements['total_mvola'];
|
||||
$total_autres_enc_virement = $totauxAutresEncaissements['total_virement'];
|
||||
$total_autres_encaissements = $total_autres_enc_espece + $total_autres_enc_mvola + $total_autres_enc_virement;
|
||||
|
||||
// === EXTRACTION DES SORTIES PAR MODE DE PAIEMENT ===
|
||||
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
|
||||
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
|
||||
@ -69,11 +85,11 @@ class Dashboard extends AdminController
|
||||
$es_avances = isset($paymentDataAvance->total_espece) ? (float) $paymentDataAvance->total_espece : 0;
|
||||
$vb_avances = isset($paymentDataAvance->total_virement_bancaire) ? (float) $paymentDataAvance->total_virement_bancaire : 0;
|
||||
|
||||
// === COMBINAISON ORDERS + AVANCES (BRUT = CE QUE LA CAISSIÈRE A ENCAISSÉ) ===
|
||||
$total_mvola_brut = $mv1_orders + $mv2_orders + $mv_avances;
|
||||
$total_espece_brut = $es1_orders + $es2_orders + $es_avances;
|
||||
$total_vb_brut = $vb1_orders + $vb2_orders + $vb_avances;
|
||||
$total_brut = $total_orders + $total_avances;
|
||||
// === COMBINAISON ORDERS + AVANCES + AUTRES ENCAISSEMENTS (BRUT) ===
|
||||
$total_mvola_brut = $mv1_orders + $mv2_orders + $mv_avances + $total_autres_enc_mvola;
|
||||
$total_espece_brut = $es1_orders + $es2_orders + $es_avances + $total_autres_enc_espece;
|
||||
$total_vb_brut = $vb1_orders + $vb2_orders + $vb_avances + $total_autres_enc_virement;
|
||||
$total_brut = $total_orders + $total_avances + $total_autres_encaissements;
|
||||
|
||||
// === AJUSTEMENTS AVEC RECOUVREMENTS ET SORTIES ===
|
||||
$total_mvola_final = $total_mvola_brut - $me - $mb + $bm - $total_sortie_mvola;
|
||||
@ -102,6 +118,12 @@ class Dashboard extends AdminController
|
||||
'total_orders_only' => $total_orders,
|
||||
'total_avances' => $total_avances,
|
||||
|
||||
// ✅ AJOUT : Détails des autres encaissements
|
||||
'total_autres_encaissements' => $total_autres_encaissements,
|
||||
'total_autres_enc_espece' => $total_autres_enc_espece,
|
||||
'total_autres_enc_mvola' => $total_autres_enc_mvola,
|
||||
'total_autres_enc_virement' => $total_autres_enc_virement,
|
||||
|
||||
// ✅ Détails des sorties
|
||||
'total_sorties' => $total_sortie_global,
|
||||
'total_sortie_espece' => $total_sortie_espece,
|
||||
@ -125,7 +147,7 @@ class Dashboard extends AdminController
|
||||
'total_espece_orders' => $es1_orders + $es2_orders,
|
||||
'total_vb_orders' => $vb1_orders + $vb2_orders,
|
||||
|
||||
// ✅ Montants bruts
|
||||
// ✅ Montants bruts (AVEC autres encaissements)
|
||||
'total_brut' => $total_brut,
|
||||
'total_mvola_brut' => $total_mvola_brut,
|
||||
'total_espece_brut' => $total_espece_brut,
|
||||
@ -136,8 +158,6 @@ class Dashboard extends AdminController
|
||||
$data['total_products'] = $productModel->countProductsByUserStore();
|
||||
|
||||
// === ✅ Récupérer le nom du store pour l'affichage ===
|
||||
$isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction','SuperAdmin']);
|
||||
|
||||
if (!$isAdmin && !empty($user_id['store_id']) && $user_id['store_id'] != 0) {
|
||||
$store = $storeModel->getStoresData($user_id['store_id']);
|
||||
$data['store_name'] = $store['name'] ?? 'Votre magasin';
|
||||
@ -236,8 +256,6 @@ class Dashboard extends AdminController
|
||||
// ✅ AJOUT POUR CAISSIER : Passer les données de performance
|
||||
if ($user_id['group_name'] == "Caissière") {
|
||||
$data['isCaissier'] = true;
|
||||
// Pas besoin de données supplémentaires car fetchCaissierPerformances
|
||||
// récupère déjà les données via AJAX
|
||||
}
|
||||
|
||||
if ($user_id['group_name'] == "MECANICIEN") {
|
||||
|
||||
@ -5,6 +5,7 @@ namespace App\Controllers;
|
||||
use App\Models\Mecanicien;
|
||||
use App\Models\Products;
|
||||
use App\Models\Users;
|
||||
use App\Models\stores;
|
||||
|
||||
class MecanicienController extends AdminController
|
||||
{
|
||||
@ -21,141 +22,100 @@ class MecanicienController extends AdminController
|
||||
|
||||
$session = session();
|
||||
$user_id = $session->get('user');
|
||||
// if($user_id CONTAINS MECANICIEN)
|
||||
// is mecanicien true
|
||||
$data['id'] = $user_id['id'];
|
||||
$data['user_role'] = $user_id['group_name'];
|
||||
|
||||
$Products = new Products();
|
||||
$Users = new Users();
|
||||
$Stores = new Stores();
|
||||
|
||||
$data['moto'] = $Products->getActiveProductData();
|
||||
$data['users'] = $Users->getUsers();
|
||||
$data['stores'] = $Stores->getActiveStore();
|
||||
|
||||
return $this->render_template('mecanicien/index', $data);
|
||||
}
|
||||
|
||||
public function fetchmecanicienSingle($id)
|
||||
{
|
||||
// die(var_dump($id));
|
||||
if ($id) {
|
||||
$Mecanicien = new Mecanicien();
|
||||
|
||||
$data = $Mecanicien->getReparationSingle($id);
|
||||
echo json_encode($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MODIFIÉ : Utilise mecanic_id au lieu de store_id
|
||||
*/
|
||||
public function fetchMecanicien()
|
||||
{
|
||||
$Mecanicien = new Mecanicien();
|
||||
$session = session();
|
||||
$user_id = $session->get('user');
|
||||
|
||||
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRE
|
||||
$startDate = $this->request->getGet('startDate');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$mecanicId = $this->request->getGet('mecanic_id'); // ✅ CHANGÉ: store_id → mecanic_id
|
||||
|
||||
log_message('debug', '=== FILTRES RÉPARATIONS ===');
|
||||
log_message('debug', 'startDate: ' . ($startDate ?? 'vide'));
|
||||
log_message('debug', 'endDate: ' . ($endDate ?? 'vide'));
|
||||
log_message('debug', 'mecanic_id: ' . ($mecanicId ?? 'vide'));
|
||||
|
||||
$data['id'] = $user_id['id'];
|
||||
$reparation = $Mecanicien->getReparation($data['id']);
|
||||
|
||||
// ✅ UTILISER LA MÉTHODE AVEC FILTRES (mecanicId au lieu de storeId)
|
||||
$reparation = $Mecanicien->getReparationWithFilters($data['id'], $startDate, $endDate, $mecanicId);
|
||||
|
||||
$result = ['data' => []];
|
||||
|
||||
function strReparation($repastatus)
|
||||
{
|
||||
$reparation = '';
|
||||
if ($repastatus == 1) {
|
||||
$reparation = 'En cours de réparation';
|
||||
} else if ($repastatus == 2) {
|
||||
$reparation = 'Réparer';
|
||||
} else {
|
||||
$reparation = 'Non réparer';
|
||||
}
|
||||
|
||||
return $reparation;
|
||||
}
|
||||
|
||||
// Iterate through the data
|
||||
|
||||
foreach ($reparation as $key => $repa) {
|
||||
// Action buttons
|
||||
$buttons = '';
|
||||
// dd($repa['reparationsID']);
|
||||
// Check permissions for updating the store
|
||||
|
||||
if (in_array('updateMecanicien', $this->permission)) {
|
||||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $repa['reparationsID'] . ')" data-toggle="modal" data-target="#editModal"><i class="fa fa-pencil"></i></button>';
|
||||
}
|
||||
|
||||
// Check permissions for deleting the store
|
||||
|
||||
if (in_array('deleteMecanicien', $this->permission)) {
|
||||
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $repa['reparationsID'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
|
||||
$buttons .= ' <button type="button" class="btn btn-default" onclick="removeFunc(' . $repa['reparationsID'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
|
||||
}
|
||||
|
||||
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
|
||||
$image = '<img src="' . base_url('assets/images/products/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$produit = $repa['name'] . ' (' . $repa['sku'] . ')';
|
||||
// Status display
|
||||
$status = strReparation($repa['reparation_statut']);
|
||||
$username = $repa['username'];
|
||||
|
||||
|
||||
$status = '';
|
||||
if ($repa['reparation_statut'] == 1) {
|
||||
$status = '<span class="label label-warning">En cours</span>';
|
||||
} elseif ($repa['reparation_statut'] == 2) {
|
||||
$status = '<span class="label label-success">Réparer</span>';
|
||||
} else {
|
||||
$status = '<span class="label label-danger">Non réparer</span>';
|
||||
}
|
||||
|
||||
$username = $repa['firstname'] . ' ' . $repa['lastname'];
|
||||
$observation = $repa['reparation_observation'];
|
||||
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
|
||||
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
|
||||
// Add the row data
|
||||
|
||||
$storeName = $repa['store_name'] ?? 'N/A';
|
||||
|
||||
$result['data'][$key] = [
|
||||
$image,
|
||||
$produit,
|
||||
$username,
|
||||
$status,
|
||||
$username, // Index 2 - Mécanicien
|
||||
$storeName, // Index 3 - Magasin
|
||||
$status, // Index 4 - Statut (utilisé pour les stats)
|
||||
$observation,
|
||||
$date_debut,
|
||||
$date_fin,
|
||||
$buttons
|
||||
];
|
||||
}
|
||||
|
||||
// Return data in JSON format
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function fetchMecanicien_1(int $id)
|
||||
{
|
||||
$Mecanicien = new Mecanicien();
|
||||
|
||||
$reparation = $Mecanicien->getReparation($id);
|
||||
$result = ['data' => []];
|
||||
|
||||
// die(var_dump($reparation));
|
||||
// Iterate through the data
|
||||
foreach ($reparation as $key => $repa) {
|
||||
// Action buttons
|
||||
$buttons = '';
|
||||
// dd($repa['reparationsID']);
|
||||
// Check permissions for updating the store
|
||||
if (in_array('updateMecanicien', $this->permission)) {
|
||||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $repa['reparationsID'] . ')" data-toggle="modal" data-target="#editModal"><i class="fa fa-pencil"></i></button>';
|
||||
}
|
||||
|
||||
// Check permissions for deleting the store
|
||||
if (in_array('deleteMecanicien', $this->permission)) {
|
||||
$buttons .= ' <button type="button" class="btn btn-danger" onclick="removeFunc(' . $repa['reparationsID'] . ')" data-toggle="modal" data-target="#removeModal"><i class="fa fa-trash"></i></button>';
|
||||
}
|
||||
|
||||
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$produit = $repa['name'];
|
||||
// Status display
|
||||
$status = $repa['reparation_statut'];
|
||||
$username = $repa['username'];
|
||||
|
||||
$observation = $repa['reparation_observation'];
|
||||
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
|
||||
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
|
||||
|
||||
// Add the row data
|
||||
$result['data'][$key] = [
|
||||
$image,
|
||||
$produit,
|
||||
$username,
|
||||
$status,
|
||||
$observation,
|
||||
$date_debut,
|
||||
$date_fin,
|
||||
$buttons
|
||||
];
|
||||
}
|
||||
|
||||
// Return data in JSON format
|
||||
|
||||
log_message('debug', '📊 Nombre de réparations trouvées: ' . count($result['data']));
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
@ -164,7 +124,7 @@ class MecanicienController extends AdminController
|
||||
$this->verifyRole('createMecanicien');
|
||||
$response = [];
|
||||
$data = [];
|
||||
|
||||
|
||||
$validation = \Config\Services::validation();
|
||||
$validation->setRules([
|
||||
'motos' => 'required',
|
||||
@ -174,7 +134,7 @@ class MecanicienController extends AdminController
|
||||
'date_debut' => 'required',
|
||||
'date_fin' => 'required',
|
||||
]);
|
||||
|
||||
|
||||
$validationData = [
|
||||
'motos' => $this->request->getPost('motos'),
|
||||
'mecano' => $this->request->getPost('mecano'),
|
||||
@ -183,10 +143,12 @@ class MecanicienController extends AdminController
|
||||
'date_debut' => $this->request->getPost('date_debut'),
|
||||
'date_fin' => $this->request->getPost('date_fin'),
|
||||
];
|
||||
|
||||
// Run validation
|
||||
|
||||
if ($validation->run($validationData)) {
|
||||
// // Prepare data
|
||||
$productModel = new \App\Models\Products();
|
||||
$product = $productModel->find($this->request->getPost('motos'));
|
||||
$storeId = $product['store_id'] ?? null;
|
||||
|
||||
$data = [
|
||||
'user_id' => $this->request->getPost('mecano'),
|
||||
'produit_id' => $this->request->getPost('motos'),
|
||||
@ -194,9 +156,9 @@ class MecanicienController extends AdminController
|
||||
'reparation_statut' => $this->request->getPost('statut'),
|
||||
'reparation_debut' => $this->request->getPost('date_debut'),
|
||||
'reparation_fin' => $this->request->getPost('date_fin'),
|
||||
'store_id' => $storeId,
|
||||
];
|
||||
|
||||
// Load the model and create the store
|
||||
|
||||
$Mecanicien = new Mecanicien();
|
||||
if ($Mecanicien->createRepation($data)) {
|
||||
$response['success'] = true;
|
||||
@ -206,11 +168,10 @@ class MecanicienController extends AdminController
|
||||
$response['messages'] = 'Erreur de base de données';
|
||||
}
|
||||
} else {
|
||||
// Validation failed, return error messages
|
||||
$response['success'] = false;
|
||||
$response['messages'] = $validation->getErrors();
|
||||
}
|
||||
|
||||
|
||||
return $this->response->setJSON($response);
|
||||
}
|
||||
|
||||
@ -229,7 +190,7 @@ class MecanicienController extends AdminController
|
||||
$response['messages'] = "Supprimé avec succès";
|
||||
} else {
|
||||
$response['success'] = false;
|
||||
$response['messages'] = "Erreur dans la base de données lors de la suppression des informations sur la marque";
|
||||
$response['messages'] = "Erreur dans la base de données lors de la suppression";
|
||||
}
|
||||
} else {
|
||||
$response['success'] = false;
|
||||
@ -239,42 +200,38 @@ class MecanicienController extends AdminController
|
||||
return $this->response->setJSON($response);
|
||||
}
|
||||
|
||||
|
||||
public function update(int $id)
|
||||
{
|
||||
$this->verifyRole('updateMecanicien');
|
||||
$response = [];
|
||||
|
||||
|
||||
if ($id) {
|
||||
// Set validation rules
|
||||
$validation = \Config\Services::validation();
|
||||
|
||||
|
||||
$validation->setRules([
|
||||
'motos_edit' => 'required',
|
||||
'motos' => 'required',
|
||||
'mecano' => 'required',
|
||||
'statut_edit' => 'required',
|
||||
'observation_edit' => 'required',
|
||||
'date_debut_edit' => 'required',
|
||||
'date_fin_edit' => 'required',
|
||||
'statut' => 'required',
|
||||
'observation' => 'required',
|
||||
'date_debut' => 'required',
|
||||
'date_fin' => 'required',
|
||||
]);
|
||||
$statutList = [
|
||||
"1" => "En cours de réparation",
|
||||
"2" => "Reparé",
|
||||
"3" => "Non reparé"
|
||||
];
|
||||
$statut = $this->request->getPost('statut');
|
||||
|
||||
$validationData = [
|
||||
'motos_edit' => $this->request->getPost('motos'),
|
||||
'motos' => $this->request->getPost('motos'),
|
||||
'mecano' => $this->request->getPost('mecano'),
|
||||
'statut_edit' => $statutList[$statut],
|
||||
'observation_edit' => $this->request->getPost('observation'),
|
||||
'date_debut_edit' => $this->request->getPost('date_debut'),
|
||||
'date_fin_edit' => $this->request->getPost('date_fin'),
|
||||
'statut' => $this->request->getPost('statut'),
|
||||
'observation' => $this->request->getPost('observation'),
|
||||
'date_debut' => $this->request->getPost('date_debut'),
|
||||
'date_fin' => $this->request->getPost('date_fin'),
|
||||
];
|
||||
|
||||
|
||||
$Mecanicien = new Mecanicien();
|
||||
|
||||
if ($validation->run($validationData)) {
|
||||
$productModel = new \App\Models\Products();
|
||||
$product = $productModel->find($this->request->getPost('motos'));
|
||||
$storeId = $product['store_id'] ?? null;
|
||||
|
||||
$data = [
|
||||
'user_id' => $this->request->getPost('mecano'),
|
||||
@ -283,10 +240,9 @@ class MecanicienController extends AdminController
|
||||
'reparation_observation' => $this->request->getPost('observation'),
|
||||
'reparation_debut' => $this->request->getPost('date_debut'),
|
||||
'reparation_fin' => $this->request->getPost('date_fin'),
|
||||
'store_id' => $storeId,
|
||||
];
|
||||
// echo '<pre>';
|
||||
// die(var_dump($data));
|
||||
|
||||
|
||||
if ($Mecanicien->updateReparation($data, $id)) {
|
||||
$response['success'] = true;
|
||||
$response['messages'] = 'Mise à jour réussie';
|
||||
@ -295,7 +251,6 @@ class MecanicienController extends AdminController
|
||||
$response['messages'] = 'Erreur dans la base de données';
|
||||
}
|
||||
} else {
|
||||
// Validation failed, return error messages
|
||||
$response['success'] = false;
|
||||
$response['messages'] = $validation->getErrors();
|
||||
}
|
||||
@ -303,10 +258,13 @@ class MecanicienController extends AdminController
|
||||
$response['success'] = false;
|
||||
$response['messages'] = 'Erreur, veuillez actualiser la page à nouveau !!';
|
||||
}
|
||||
|
||||
|
||||
return $this->response->setJSON($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MODIFIÉ : Utilise mecanic_id au lieu de pvente
|
||||
*/
|
||||
public function fetchMecanicienPerformances()
|
||||
{
|
||||
$Mecanicien = new Mecanicien();
|
||||
@ -316,58 +274,63 @@ class MecanicienController extends AdminController
|
||||
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRE
|
||||
$startDate = $this->request->getGet('startDate');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$pvente = $this->request->getGet('pvente');
|
||||
$mecanicId = $this->request->getGet('mecanic_id'); // ✅ CHANGÉ: pvente → mecanic_id
|
||||
|
||||
// Log pour débogage
|
||||
log_message('debug', 'Filtres Mécanicien reçus - startDate: ' . $startDate . ', endDate: ' . $endDate . ', pvente: ' . $pvente);
|
||||
log_message('debug', '=== FILTRES PERFORMANCES MÉCANICIEN ===');
|
||||
log_message('debug', 'startDate: ' . ($startDate ?? 'vide'));
|
||||
log_message('debug', 'endDate: ' . ($endDate ?? 'vide'));
|
||||
log_message('debug', 'mecanic_id: ' . ($mecanicId ?? 'vide'));
|
||||
|
||||
$data['id'] = $users['id'];
|
||||
|
||||
// ✅ PASSER LES FILTRES AU MODÈLE
|
||||
$reparation = $Mecanicien->getReparationWithFilters($data['id'], $startDate, $endDate, $pvente);
|
||||
// ✅ PASSER LES FILTRES AU MODÈLE (mecanicId au lieu de pvente)
|
||||
$reparation = $Mecanicien->getReparationWithFilters($data['id'], $startDate, $endDate, $mecanicId);
|
||||
|
||||
$result = ['data' => []];
|
||||
|
||||
if($users['group_name'] == "SuperAdmin" || $users['group_name'] == "Direction" || $users['group_name'] == "DAF"){
|
||||
foreach ($reparation as $key => $repa) {
|
||||
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$produit = esc($repa['name']);
|
||||
$first_name = esc($repa['firstname']);
|
||||
$last_name = esc($repa['lastname']);
|
||||
$image = '<img src="' . base_url('assets/images/products/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$produit = esc($repa['name']);
|
||||
$first_name = esc($repa['firstname']);
|
||||
$last_name = esc($repa['lastname']);
|
||||
$user_name = $first_name . ' ' . $last_name;
|
||||
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
|
||||
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
|
||||
$storeName = $repa['store_name'] ?? 'N/A';
|
||||
|
||||
// Add the row data
|
||||
$result['data'][$key] = [
|
||||
$user_name,
|
||||
$image,
|
||||
$produit,
|
||||
$repa['sku'],
|
||||
$storeName,
|
||||
$date_debut,
|
||||
$date_fin,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
foreach ($reparation as $key => $repa) {
|
||||
$image = '<img src="' . base_url('assets/images/product_image/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$image = '<img src="' . base_url('assets/images/products/' . $repa['image']) . '" alt="' . $repa['name'] . '" class="img-circle" width="50" height="50" />';
|
||||
$produit = $repa['name'];
|
||||
$username = $repa['username'];
|
||||
|
||||
$date_debut = date("d/m/Y", strtotime($repa['reparation_debut']));
|
||||
$date_fin = date("d/m/Y", strtotime($repa['reparation_fin']));
|
||||
$storeName = $repa['store_name'] ?? 'N/A';
|
||||
|
||||
// Add the row data
|
||||
$result['data'][$key] = [
|
||||
$image,
|
||||
$produit,
|
||||
$repa['sku'],
|
||||
$storeName,
|
||||
$date_debut,
|
||||
$date_fin,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
log_message('debug', '📊 Nombre de réparations (performances) trouvées: ' . count($result['data']));
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,9 @@ use App\Models\Stores;
|
||||
use App\Models\Reports;
|
||||
use App\Models\Products;
|
||||
use App\Models\OrderItems;
|
||||
use App\Models\Brands;
|
||||
use App\Models\Users;
|
||||
|
||||
|
||||
class ReportController extends AdminController
|
||||
{
|
||||
@ -35,6 +38,7 @@ class ReportController extends AdminController
|
||||
$Store = new Stores();
|
||||
$parking_data = $Reports->getOrderData($today_year);
|
||||
$data['report_years'] = $Reports->getOrderYear();
|
||||
|
||||
|
||||
// Process the parking data and calculate total amounts
|
||||
$final_parking_data = [];
|
||||
@ -403,4 +407,237 @@ class ReportController extends AdminController
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
public function fetchSecuritePerformances()
|
||||
{
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
// ✅ RÉCUPÉRER LES PARAMÈTRES DE FILTRAGE
|
||||
$startDate = $this->request->getGet('startDate');
|
||||
$endDate = $this->request->getGet('endDate');
|
||||
$storeFilter = $this->request->getGet('store_id');
|
||||
|
||||
// ✅ DEBUG
|
||||
log_message('debug', '=== FILTRES REÇUS ===');
|
||||
log_message('debug', 'startDate: ' . ($startDate ?? 'vide'));
|
||||
log_message('debug', 'endDate: ' . ($endDate ?? 'vide'));
|
||||
log_message('debug', 'store_id: ' . ($storeFilter ?? 'vide'));
|
||||
|
||||
// Charger les modèles
|
||||
$orderModel = new \App\Models\Orders();
|
||||
$orderItemModel = new \App\Models\OrderItems();
|
||||
$productModel = new \App\Models\Products();
|
||||
$brandModel = new \App\Models\Brands();
|
||||
$userModel = new \App\Models\Users();
|
||||
$storeModel = new \App\Models\Stores();
|
||||
|
||||
// Récupérer le store de l'utilisateur connecté
|
||||
$userStore = null;
|
||||
if (in_array($user['group_name'], ['SECURITE', 'Cheffe d\'Agence'])) {
|
||||
$currentUser = $userModel->find($user['id']);
|
||||
$userStore = $currentUser['store_id'] ?? null;
|
||||
}
|
||||
|
||||
// ✅ CONSTRUCTION DE LA REQUÊTE AVEC FILTRES
|
||||
$builder = $orderModel->builder();
|
||||
$builder->select('
|
||||
orders.id,
|
||||
orders.bill_no,
|
||||
orders.customer_name,
|
||||
orders.customer_phone,
|
||||
orders.customer_address,
|
||||
orders.customer_cin,
|
||||
orders.delivered_at,
|
||||
orders.store_id,
|
||||
CONCAT(deliverer.firstname, " ", deliverer.lastname) as agent_name,
|
||||
deliverer.id as agent_id
|
||||
')
|
||||
->join('users as deliverer', 'deliverer.id = orders.delivered_by', 'left')
|
||||
->where('orders.paid_status', 3)
|
||||
->where('orders.delivered_by IS NOT NULL', null, false);
|
||||
|
||||
// ✅ FILTRER PAR STORE
|
||||
// Si l'utilisateur a un store assigné, on filtre automatiquement par ce store
|
||||
if ($userStore) {
|
||||
$builder->where('orders.store_id', $userStore);
|
||||
log_message('debug', '✅ Filtre userStore appliqué: ' . $userStore);
|
||||
}
|
||||
// Sinon, si un store est sélectionné dans le filtre, on l'applique
|
||||
elseif (!empty($storeFilter)) {
|
||||
$builder->where('orders.store_id', $storeFilter);
|
||||
log_message('debug', '✅ Filtre storeFilter appliqué: ' . $storeFilter);
|
||||
}
|
||||
|
||||
// ✅ FILTRAGE PAR DATE DE DÉBUT
|
||||
if (!empty($startDate)) {
|
||||
$builder->where('DATE(orders.delivered_at) >=', $startDate);
|
||||
log_message('debug', '✅ Filtre startDate appliqué: ' . $startDate);
|
||||
}
|
||||
|
||||
// ✅ FILTRAGE PAR DATE DE FIN
|
||||
if (!empty($endDate)) {
|
||||
$builder->where('DATE(orders.delivered_at) <=', $endDate);
|
||||
log_message('debug', '✅ Filtre endDate appliqué: ' . $endDate);
|
||||
}
|
||||
|
||||
$builder->orderBy('orders.delivered_at', 'DESC');
|
||||
|
||||
// ✅ DEBUG : Afficher la requête SQL
|
||||
$sql = $builder->getCompiledSelect(false);
|
||||
log_message('debug', '📋 SQL GÉNÉRÉ: ' . $sql);
|
||||
|
||||
$orders = $builder->get()->getResultArray();
|
||||
|
||||
log_message('debug', '📊 Nombre de commandes trouvées: ' . count($orders));
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$orderItems = $orderItemModel->where('order_id', $order['id'])->findAll();
|
||||
|
||||
foreach ($orderItems as $item) {
|
||||
$qty = isset($item['qty']) ? (int)$item['qty'] : 1;
|
||||
$product = $productModel->find($item['product_id']);
|
||||
|
||||
if ($product) {
|
||||
$brand = $brandModel->find($product['marque']);
|
||||
$brandName = $brand['name'] ?? 'Aucune marque';
|
||||
|
||||
$store = $storeModel->find($order['store_id']);
|
||||
$storeName = $store['name'] ?? 'N/A';
|
||||
|
||||
$agentName = $order['agent_name'] ?? 'N/A';
|
||||
$imageUrl = base_url('assets/images/products/' . $product['image']);
|
||||
|
||||
$validationDate = $order['delivered_at'] ?
|
||||
date('d/m/Y H:i', strtotime($order['delivered_at'])) :
|
||||
'N/A';
|
||||
|
||||
$result[] = [
|
||||
$imageUrl, // 0 - Image
|
||||
$order['bill_no'] ?? 'N/A', // 1 - N° Facture
|
||||
$product['name'], // 2 - Désignation
|
||||
$product['sku'], // 3 - UGS
|
||||
$brandName, // 4 - Marque
|
||||
$order['customer_name'], // 5 - Client
|
||||
$agentName, // 6 - Agent Sécurité
|
||||
$storeName, // 7 - Magasin
|
||||
$validationDate, // 8 - Date Validation
|
||||
'<span class="badge badge-success"><i class="fa fa-check"></i> VALIDÉE</span>', // 9
|
||||
$qty, // 10 - Quantité
|
||||
$product['sku'] ?? 'N/A', // 11
|
||||
$order['customer_phone'] ?? 'N/A', // 12
|
||||
$order['customer_address'] ?? 'N/A', // 13
|
||||
$order['customer_cin'] ?? 'N/A', // 14
|
||||
$product['numero_de_moteur'] ?? 'N/A', // 15
|
||||
$product['chasis'] ?? 'N/A' // 16
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_message('debug', '✅ Nombre de lignes retournées: ' . count($result));
|
||||
|
||||
return $this->response->setJSON(['data' => $result]);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MÉTHODE POUR RÉCUPÉRER LES DÉTAILS D'UNE VALIDATION
|
||||
// ============================================
|
||||
public function getSecuriteValidationDetails($orderId)
|
||||
{
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$orderModel = new \App\Models\Orders();
|
||||
$orderItemModel = new \App\Models\OrderItems();
|
||||
$productModel = new \App\Models\Products();
|
||||
$brandModel = new \App\Models\Brands();
|
||||
$storeModel = new \App\Models\Stores();
|
||||
$userModel = new \App\Models\Users();
|
||||
|
||||
$builder = $orderModel->builder();
|
||||
$builder->select('
|
||||
orders.*,
|
||||
CONCAT(deliverer.firstname, " ", deliverer.lastname) as agent_name
|
||||
')
|
||||
->join('users as deliverer', 'deliverer.id = orders.delivered_by', 'left')
|
||||
->where('orders.id', $orderId);
|
||||
|
||||
$order = $builder->get()->getRowArray();
|
||||
|
||||
if (!$order) {
|
||||
return $this->response->setJSON(['error' => 'Commande non trouvée']);
|
||||
}
|
||||
|
||||
// Récupérer le magasin
|
||||
$store = $storeModel->find($order['store_id']);
|
||||
$storeName = $store['name'] ?? 'N/A';
|
||||
|
||||
// Récupérer le premier item
|
||||
$orderItem = $orderItemModel->where('order_id', $orderId)->first();
|
||||
$product = null;
|
||||
$brandName = 'Aucune marque';
|
||||
$imageUrl = '';
|
||||
$qty = 1;
|
||||
|
||||
if ($orderItem) {
|
||||
// ✅ RÉCUPÉRER LA QUANTITÉ
|
||||
$qty = isset($orderItem['qty']) ? (int)$orderItem['qty'] : 1;
|
||||
|
||||
$product = $productModel->find($orderItem['product_id']);
|
||||
if ($product) {
|
||||
$brand = $brandModel->find($product['marque']);
|
||||
$brandName = $brand['name'] ?? 'Aucune marque';
|
||||
$imageUrl = base_url('assets/images/products/' . $product['image']);
|
||||
}
|
||||
}
|
||||
|
||||
$agentName = $order['agent_name'] ?? 'N/A';
|
||||
$validationDate = $order['delivered_at'] ?
|
||||
date('d/m/Y H:i', strtotime($order['delivered_at'])) :
|
||||
'N/A';
|
||||
|
||||
$result = [
|
||||
$imageUrl, // 0
|
||||
$order['bill_no'] ?? 'N/A', // 1
|
||||
$product['name'] ?? 'N/A', // 2
|
||||
$product['sku'] ?? 'N/A', // 3
|
||||
$brandName, // 4
|
||||
$order['customer_name'], // 5
|
||||
$agentName, // 6
|
||||
$storeName, // 7
|
||||
$validationDate, // 8
|
||||
'<span class="badge badge-success"><i class="fa fa-check"></i> VALIDÉE</span>', // 9
|
||||
$qty, // 10 - ✅ QUANTITÉ
|
||||
$product['sku'] ?? 'N/A', // 11
|
||||
$order['customer_phone'] ?? 'N/A', // 12
|
||||
$order['customer_address'] ?? 'N/A', // 13
|
||||
$order['customer_cin'] ?? 'N/A', // 14
|
||||
$product['numero_de_moteur'] ?? 'N/A', // 15
|
||||
$product['chasis'] ?? 'N/A' // 16
|
||||
];
|
||||
|
||||
return $this->response->setJSON($result);
|
||||
}
|
||||
|
||||
public function securiteHistory()
|
||||
{
|
||||
$this->verifyRole('viewReports');
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$data['page_title'] = 'Historique des Validations Sécurité';
|
||||
$data['user_role'] = $user['group_name']; // ✅ AJOUT DE LA VARIABLE user_role
|
||||
|
||||
// ✅ RÉCUPÉRER TOUS LES MAGASINS ACTIFS (comme dans AutresEncaissements)
|
||||
$storeModel = new \App\Models\Stores();
|
||||
$data['stores'] = $storeModel->getActiveStore();
|
||||
|
||||
log_message('debug', '📋 Nombre de magasins trouvés: ' . count($data['stores']));
|
||||
log_message('debug', '👤 Rôle utilisateur: ' . $user['group_name']);
|
||||
|
||||
return $this->render_template('reports/securite_history', $data);
|
||||
}
|
||||
}
|
||||
@ -17,14 +17,23 @@ class SecuriteController extends AdminController
|
||||
|
||||
private $pageTitle = 'Validation sortie motos';
|
||||
|
||||
public function index()
|
||||
public function index() // ou validateSecurite() ou autre nom
|
||||
{
|
||||
$this->verifyRole('viewSecurite');
|
||||
$data['page_title'] = $this->pageTitle;
|
||||
$this->verifyRole('viewSecurite'); // ou autre permission
|
||||
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
$data['page_title'] = 'Validation Sécurité';
|
||||
$data['user_role'] = $user['group_name']; // ✅ AJOUTER CETTE LIGNE
|
||||
$data['user_permission'] = $this->permission;
|
||||
|
||||
// ✅ RÉCUPÉRER LES MAGASINS
|
||||
$storeModel = new \App\Models\Stores();
|
||||
$data['stores'] = $storeModel->getActiveStore();
|
||||
|
||||
return $this->render_template('securite/index', $data);
|
||||
}
|
||||
|
||||
public function fetchSecuriteData()
|
||||
{
|
||||
$securiteModel = new Securite();
|
||||
|
||||
@ -415,7 +415,6 @@ class SortieCaisseController extends AdminController
|
||||
*/
|
||||
public function markAsPaid($id_sortie)
|
||||
{
|
||||
// Vérifier que l'utilisateur est une caissière
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
@ -447,7 +446,114 @@ public function markAsPaid($id_sortie)
|
||||
]);
|
||||
}
|
||||
|
||||
// Mettre à jour le statut
|
||||
// ✅ NOUVEAU: VÉRIFICATION DES FONDS DISPONIBLES
|
||||
$orders = new Orders();
|
||||
$Recouvrement = new Recouvrement();
|
||||
|
||||
$paymentData = $orders->getPaymentModes();
|
||||
$totalRecouvrement = $Recouvrement->getTotalRecouvrements();
|
||||
$total_sortie_caisse = $SortieCaisse->getTotalSortieCaisse();
|
||||
|
||||
// EXTRACTION DES TOTAUX (uniquement les décaissements déjà payés)
|
||||
$total_sortie_espece = 0;
|
||||
$total_sortie_mvola = 0;
|
||||
$total_sortie_virement = 0;
|
||||
|
||||
if (is_object($total_sortie_caisse)) {
|
||||
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
|
||||
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
|
||||
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
|
||||
}
|
||||
|
||||
// Recouvrements
|
||||
$total_recouvrement_me = 0;
|
||||
$total_recouvrement_be = 0;
|
||||
$total_recouvrement_bm = 0;
|
||||
$total_recouvrement_mb = 0;
|
||||
|
||||
if (is_object($totalRecouvrement)) {
|
||||
$total_recouvrement_me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
|
||||
$total_recouvrement_be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
|
||||
$total_recouvrement_bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
|
||||
$total_recouvrement_mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
|
||||
}
|
||||
|
||||
// Orders
|
||||
$total_espece1 = 0;
|
||||
$total_espece2 = 0;
|
||||
$total_mvola1 = 0;
|
||||
$total_mvola2 = 0;
|
||||
$total_virement1 = 0;
|
||||
$total_virement2 = 0;
|
||||
|
||||
if (is_object($paymentData)) {
|
||||
$total_espece1 = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0;
|
||||
$total_espece2 = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0;
|
||||
$total_mvola1 = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0;
|
||||
$total_mvola2 = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0;
|
||||
$total_virement1 = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0;
|
||||
$total_virement2 = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0;
|
||||
}
|
||||
|
||||
// CALCUL DES SOLDES DISPONIBLES
|
||||
$total_espece_disponible = $total_espece1 +
|
||||
$total_espece2 +
|
||||
$total_recouvrement_me +
|
||||
$total_recouvrement_be -
|
||||
$total_sortie_espece;
|
||||
|
||||
$total_mvola_disponible = $total_mvola1 +
|
||||
$total_mvola2 -
|
||||
$total_recouvrement_me -
|
||||
$total_recouvrement_mb +
|
||||
$total_recouvrement_bm -
|
||||
$total_sortie_mvola;
|
||||
|
||||
$total_virement_disponible = $total_virement1 +
|
||||
$total_virement2 -
|
||||
$total_recouvrement_be -
|
||||
$total_recouvrement_bm +
|
||||
$total_recouvrement_mb -
|
||||
$total_sortie_virement;
|
||||
|
||||
// Vérifier selon le mode de paiement
|
||||
$montant_retire = (float) $decaissement['montant_retire'];
|
||||
$mode_paiement = $decaissement['mode_paiement'];
|
||||
$fonds_disponible = 0;
|
||||
$mode_paiement_label = '';
|
||||
|
||||
switch ($mode_paiement) {
|
||||
case 'En espèce':
|
||||
$fonds_disponible = $total_espece_disponible;
|
||||
$mode_paiement_label = 'en espèce';
|
||||
break;
|
||||
|
||||
case 'MVOLA':
|
||||
$fonds_disponible = $total_mvola_disponible;
|
||||
$mode_paiement_label = 'MVOLA';
|
||||
break;
|
||||
|
||||
case 'Virement Bancaire':
|
||||
$fonds_disponible = $total_virement_disponible;
|
||||
$mode_paiement_label = 'virement bancaire';
|
||||
break;
|
||||
}
|
||||
|
||||
// ✅ VÉRIFICATION: Fonds suffisants ?
|
||||
if ($montant_retire > $fonds_disponible) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => '❌ Paiement impossible — fonds ' . $mode_paiement_label . ' insuffisants.<br>' .
|
||||
'<strong>Disponible:</strong> ' . number_format($fonds_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'<strong>Demandé:</strong> ' . number_format($montant_retire, 0, ',', ' ') . ' Ar<br><br>' .
|
||||
'<em>Soldes actuels:</em><br>' .
|
||||
'• Espèce: ' . number_format($total_espece_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'• MVOLA: ' . number_format($total_mvola_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'• Virement: ' . number_format($total_virement_disponible, 0, ',', ' ') . ' Ar'
|
||||
]);
|
||||
}
|
||||
|
||||
// ✅ Fonds suffisants, on procède au paiement
|
||||
$data = [
|
||||
'statut' => 'Payé',
|
||||
'date_paiement_effectif' => date('Y-m-d H:i:s')
|
||||
@ -456,60 +562,39 @@ public function markAsPaid($id_sortie)
|
||||
$result = $SortieCaisse->updateSortieCaisse($id_sortie, $data);
|
||||
|
||||
if ($result) {
|
||||
// ✅ Créer une notification pour TOUS les DAF, Direction et SuperAdmin
|
||||
// Créer une notification pour TOUS les DAF, Direction et SuperAdmin
|
||||
try {
|
||||
if (class_exists('App\Controllers\NotificationController')) {
|
||||
$Notification = new NotificationController();
|
||||
|
||||
$montant = number_format($decaissement['montant_retire'], 0, ',', ' ');
|
||||
$message = "💰 Décaissement payé - " . $montant . " Ar<br>" .
|
||||
$message = "💰 Décaissement payé - " . $montant . " Ar (" . $mode_paiement . ")<br>" .
|
||||
"Motif: " . $decaissement['motif'] . "<br>" .
|
||||
"Caissière: " . $users['firstname'] . ' ' . $users['lastname'] . "<br>" .
|
||||
"Store: " . $this->returnStoreName($decaissement['store_id']);
|
||||
"Store: " . $this->returnStoreName($decaissement['store_id']) . "<br>" .
|
||||
"Nouveau solde " . $mode_paiement_label . ": " . number_format($fonds_disponible - $montant_retire, 0, ',', ' ') . " Ar";
|
||||
|
||||
// ✅ Récupérer TOUS les stores
|
||||
$Stores = new Stores();
|
||||
$allStores = $Stores->getActiveStore();
|
||||
|
||||
// ✅ Notifier Direction, DAF et SuperAdmin de TOUS les stores
|
||||
if (is_array($allStores) && count($allStores) > 0) {
|
||||
foreach ($allStores as $store) {
|
||||
// Notifier Direction
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"Direction",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
|
||||
// Notifier DAF
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"DAF",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
|
||||
// Notifier SuperAdmin
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"SuperAdmin",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
$Notification->createNotification($message, "Direction", (int)$store['id'], 'sortieCaisse');
|
||||
$Notification->createNotification($message, "DAF", (int)$store['id'], 'sortieCaisse');
|
||||
$Notification->createNotification($message, "SuperAdmin", (int)$store['id'], 'sortieCaisse');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Logger l'erreur mais continuer
|
||||
log_message('error', 'Erreur notification markAsPaid: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => '✅ Décaissement marqué comme <strong>PAYÉ</strong><br>' .
|
||||
'Tous les Direction, DAF et SuperAdmin ont été notifiés.<br>' .
|
||||
'Montant: ' . number_format($decaissement['montant_retire'], 0, ',', ' ') . ' Ar'
|
||||
'Montant: ' . number_format($decaissement['montant_retire'], 0, ',', ' ') . ' Ar (' . $mode_paiement . ')<br>' .
|
||||
'Nouveau solde ' . $mode_paiement_label . ': ' . number_format($fonds_disponible - $montant_retire, 0, ',', ' ') . ' Ar<br>' .
|
||||
'<em>Tous les Direction, DAF et SuperAdmin ont été notifiés.</em>'
|
||||
]);
|
||||
|
||||
} else {
|
||||
@ -676,117 +761,8 @@ public function markAsPaid($id_sortie)
|
||||
]);
|
||||
}
|
||||
|
||||
// RÉCUPÉRATION DES DONNÉES FINANCIÈRES
|
||||
$orders = new Orders();
|
||||
$Recouvrement = new Recouvrement();
|
||||
$sortieCaisse = new SortieCaisse();
|
||||
|
||||
$paymentData = $orders->getPaymentModes();
|
||||
$totalRecouvrement = $Recouvrement->getTotalRecouvrements();
|
||||
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
|
||||
|
||||
// EXTRACTION DES TOTAUX DES SORTIES PAR MODE DE PAIEMENT
|
||||
$total_sortie_espece = 0;
|
||||
$total_sortie_mvola = 0;
|
||||
$total_sortie_virement = 0;
|
||||
|
||||
if (is_object($total_sortie_caisse)) {
|
||||
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
|
||||
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
|
||||
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
|
||||
}
|
||||
|
||||
// Recouvrements
|
||||
$total_recouvrement_me = 0;
|
||||
$total_recouvrement_be = 0;
|
||||
$total_recouvrement_bm = 0;
|
||||
$total_recouvrement_mb = 0;
|
||||
|
||||
if (is_object($totalRecouvrement)) {
|
||||
$total_recouvrement_me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
|
||||
$total_recouvrement_be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
|
||||
$total_recouvrement_bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
|
||||
$total_recouvrement_mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
|
||||
}
|
||||
|
||||
// Orders
|
||||
$total_espece1 = 0;
|
||||
$total_espece2 = 0;
|
||||
$total_mvola1 = 0;
|
||||
$total_mvola2 = 0;
|
||||
$total_virement1 = 0;
|
||||
$total_virement2 = 0;
|
||||
|
||||
if (is_object($paymentData)) {
|
||||
$total_espece1 = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0;
|
||||
$total_espece2 = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0;
|
||||
$total_mvola1 = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0;
|
||||
$total_mvola2 = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0;
|
||||
$total_virement1 = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0;
|
||||
$total_virement2 = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0;
|
||||
}
|
||||
|
||||
// CALCUL DES SOLDES DISPONIBLES PAR MODE DE PAIEMENT
|
||||
$total_espece_disponible = $total_espece1 +
|
||||
$total_espece2 +
|
||||
$total_recouvrement_me +
|
||||
$total_recouvrement_be -
|
||||
$total_sortie_espece;
|
||||
|
||||
$total_mvola_disponible = $total_mvola1 +
|
||||
$total_mvola2 -
|
||||
$total_recouvrement_me -
|
||||
$total_recouvrement_mb +
|
||||
$total_recouvrement_bm -
|
||||
$total_sortie_mvola;
|
||||
|
||||
$total_virement_disponible = $total_virement1 +
|
||||
$total_virement2 -
|
||||
$total_recouvrement_be -
|
||||
$total_recouvrement_bm +
|
||||
$total_recouvrement_mb -
|
||||
$total_sortie_virement;
|
||||
|
||||
// VÉRIFICATION SELON LE MODE DE PAIEMENT CHOISI
|
||||
$fonds_disponible = 0;
|
||||
$mode_paiement_label = '';
|
||||
|
||||
switch ($mode_paiement) {
|
||||
case 'En espèce':
|
||||
$fonds_disponible = $total_espece_disponible;
|
||||
$mode_paiement_label = 'en espèce';
|
||||
break;
|
||||
|
||||
case 'MVOLA':
|
||||
$fonds_disponible = $total_mvola_disponible;
|
||||
$mode_paiement_label = 'MVOLA';
|
||||
break;
|
||||
|
||||
case 'Virement Bancaire':
|
||||
$fonds_disponible = $total_virement_disponible;
|
||||
$mode_paiement_label = 'virement bancaire';
|
||||
break;
|
||||
|
||||
default:
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Mode de paiement invalide'
|
||||
]);
|
||||
}
|
||||
|
||||
// Vérification des fonds
|
||||
if ($montant_retire > $fonds_disponible) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Décaissement échoué — fonds ' . $mode_paiement_label . ' insuffisants.<br>' .
|
||||
'<strong>Disponible:</strong> ' . number_format($fonds_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'<strong>Demandé:</strong> ' . number_format($montant_retire, 0, ',', ' ') . ' Ar<br><br>' .
|
||||
'<em>Soldes actuels:</em><br>' .
|
||||
'• Espèce: ' . number_format($total_espece_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'• MVOLA: ' . number_format($total_mvola_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'• Virement: ' . number_format($total_virement_disponible, 0, ',', ' ') . ' Ar'
|
||||
]);
|
||||
}
|
||||
// ✅ SUPPRESSION: Plus besoin de vérifier les fonds disponibles à la création
|
||||
// La vérification se fera au moment du paiement effectif par la caissière
|
||||
|
||||
// PRÉPARATION DES DONNÉES
|
||||
$motif = $this->request->getPost('motif_select');
|
||||
@ -870,7 +846,7 @@ public function markAsPaid($id_sortie)
|
||||
$result = $model->addSortieCaisse($data);
|
||||
|
||||
if ($result) {
|
||||
// ✅ Notification pour TOUS les Direction, DAF et SuperAdmin
|
||||
// Notification pour TOUS les Direction, DAF et SuperAdmin
|
||||
try {
|
||||
if (class_exists('App\Controllers\NotificationController')) {
|
||||
$Notification = new NotificationController();
|
||||
@ -880,47 +856,26 @@ public function markAsPaid($id_sortie)
|
||||
"Store: " . $this->returnStoreName($user['store_id']) . "<br>" .
|
||||
"Demandeur: " . $user['firstname'] . ' ' . $user['lastname'];
|
||||
|
||||
// ✅ Récupérer TOUS les stores
|
||||
$Stores = new Stores();
|
||||
$allStores = $Stores->getActiveStore();
|
||||
|
||||
// ✅ Notifier Direction, DAF et SuperAdmin de TOUS les stores
|
||||
if (is_array($allStores) && count($allStores) > 0) {
|
||||
foreach ($allStores as $store) {
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"Direction",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"DAF",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
|
||||
$Notification->createNotification(
|
||||
$message,
|
||||
"SuperAdmin",
|
||||
(int)$store['id'],
|
||||
'sortieCaisse'
|
||||
);
|
||||
$Notification->createNotification($message, "Direction", (int)$store['id'], 'sortieCaisse');
|
||||
$Notification->createNotification($message, "DAF", (int)$store['id'], 'sortieCaisse');
|
||||
$Notification->createNotification($message, "SuperAdmin", (int)$store['id'], 'sortieCaisse');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur notification createSortieCaisse: ' . $e->getMessage());
|
||||
// Continue même si la notification échoue
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
'messages' => 'Décaissement de ' . number_format($montant_retire, 0, ',', ' ') . ' Ar créé avec succès<br>' .
|
||||
'Mode de paiement: ' . $mode_paiement . '<br>' .
|
||||
'Nouveau solde ' . $mode_paiement_label . ': ' . number_format($fonds_disponible - $montant_retire, 0, ',', ' ') . ' Ar<br>' .
|
||||
'<em>Notification envoyée à tous les Direction, DAF et SuperAdmin</em>'
|
||||
'<em>En attente de validation</em>'
|
||||
]);
|
||||
|
||||
} else {
|
||||
@ -952,8 +907,6 @@ public function markAsPaid($id_sortie)
|
||||
'nom_edit' => 'required',
|
||||
'fonction_edit' => 'required',
|
||||
'date_demande_edit' => 'required'
|
||||
// Suppression des règles de validation pour les champs qui seront en hidden
|
||||
// La preuve d'achat devient facultative
|
||||
]);
|
||||
|
||||
if (!$validation->withRequest($this->request)->run()) {
|
||||
@ -967,7 +920,6 @@ public function markAsPaid($id_sortie)
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
// Récupérer le décaissement actuel
|
||||
$sortieCaisse = new SortieCaisse();
|
||||
$currentSortie = $sortieCaisse->getSortieCaisseSingle($id_sortie);
|
||||
|
||||
@ -978,14 +930,18 @@ public function markAsPaid($id_sortie)
|
||||
]);
|
||||
}
|
||||
|
||||
// Nettoyage et conversion du montant
|
||||
// ✅ EMPÊCHER LA MODIFICATION SI DÉJÀ PAYÉ
|
||||
if ($currentSortie['statut'] === 'Payé') {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Impossible de modifier un décaissement déjà payé.'
|
||||
]);
|
||||
}
|
||||
|
||||
$montant_retire_raw = $this->request->getPost('montant_retire_edit');
|
||||
$montant_retire = (float) str_replace([' ', ','], ['', '.'], $montant_retire_raw);
|
||||
|
||||
// Récupérer le motif (disabled dans le form, donc on le récupère du champ hidden)
|
||||
$motif = $this->request->getPost('motif_select_edit') ?: $this->request->getPost('motif_select_edit_hidden');
|
||||
|
||||
// Récupérer le mode de paiement (maintenant éditable, donc directement du formulaire)
|
||||
$mode_paiement = $this->request->getPost('mode_paiement_edit');
|
||||
|
||||
if ($montant_retire <= 0) {
|
||||
@ -995,131 +951,9 @@ public function markAsPaid($id_sortie)
|
||||
]);
|
||||
}
|
||||
|
||||
// RÉCUPÉRATION DES DONNÉES FINANCIÈRES
|
||||
$orders = new Orders();
|
||||
$Recouvrement = new Recouvrement();
|
||||
|
||||
$paymentData = $orders->getPaymentModes();
|
||||
$totalRecouvrement = $Recouvrement->getTotalRecouvrements();
|
||||
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
|
||||
// ✅ SUPPRESSION: Plus de vérification des fonds à la modification
|
||||
// La vérification se fera uniquement au moment du paiement
|
||||
|
||||
// EXTRACTION DES TOTAUX DES SORTIES PAR MODE DE PAIEMENT
|
||||
$total_sortie_espece = 0;
|
||||
$total_sortie_mvola = 0;
|
||||
$total_sortie_virement = 0;
|
||||
|
||||
if (is_object($total_sortie_caisse)) {
|
||||
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
|
||||
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
|
||||
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
|
||||
}
|
||||
|
||||
// IMPORTANT : Ajouter le montant actuel du décaissement aux sorties
|
||||
// pour avoir le solde réel avant modification
|
||||
if ($currentSortie['statut'] === 'Valider') {
|
||||
switch ($currentSortie['mode_paiement']) {
|
||||
case 'En espèce':
|
||||
$total_sortie_espece -= (float) $currentSortie['montant_retire'];
|
||||
break;
|
||||
case 'MVOLA':
|
||||
$total_sortie_mvola -= (float) $currentSortie['montant_retire'];
|
||||
break;
|
||||
case 'Virement Bancaire':
|
||||
$total_sortie_virement -= (float) $currentSortie['montant_retire'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Recouvrements
|
||||
$total_recouvrement_me = 0;
|
||||
$total_recouvrement_be = 0;
|
||||
$total_recouvrement_bm = 0;
|
||||
$total_recouvrement_mb = 0;
|
||||
|
||||
if (is_object($totalRecouvrement)) {
|
||||
$total_recouvrement_me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
|
||||
$total_recouvrement_be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
|
||||
$total_recouvrement_bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
|
||||
$total_recouvrement_mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
|
||||
}
|
||||
|
||||
// Orders
|
||||
$total_espece1 = 0;
|
||||
$total_espece2 = 0;
|
||||
$total_mvola1 = 0;
|
||||
$total_mvola2 = 0;
|
||||
$total_virement1 = 0;
|
||||
$total_virement2 = 0;
|
||||
|
||||
if (is_object($paymentData)) {
|
||||
$total_espece1 = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0;
|
||||
$total_espece2 = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0;
|
||||
$total_mvola1 = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0;
|
||||
$total_mvola2 = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0;
|
||||
$total_virement1 = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0;
|
||||
$total_virement2 = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0;
|
||||
}
|
||||
|
||||
// CALCUL DES SOLDES DISPONIBLES
|
||||
$total_espece_disponible = $total_espece1 +
|
||||
$total_espece2 +
|
||||
$total_recouvrement_me +
|
||||
$total_recouvrement_be -
|
||||
$total_sortie_espece;
|
||||
|
||||
$total_mvola_disponible = $total_mvola1 +
|
||||
$total_mvola2 -
|
||||
$total_recouvrement_me -
|
||||
$total_recouvrement_mb +
|
||||
$total_recouvrement_bm -
|
||||
$total_sortie_mvola;
|
||||
|
||||
$total_virement_disponible = $total_virement1 +
|
||||
$total_virement2 -
|
||||
$total_recouvrement_be -
|
||||
$total_recouvrement_bm +
|
||||
$total_recouvrement_mb -
|
||||
$total_sortie_virement;
|
||||
|
||||
// VÉRIFICATION SELON LE MODE DE PAIEMENT CHOISI
|
||||
$fonds_disponible = 0;
|
||||
$mode_paiement_label = '';
|
||||
|
||||
switch ($mode_paiement) {
|
||||
case 'En espèce':
|
||||
$fonds_disponible = $total_espece_disponible;
|
||||
$mode_paiement_label = 'en espèce';
|
||||
break;
|
||||
|
||||
case 'MVOLA':
|
||||
$fonds_disponible = $total_mvola_disponible;
|
||||
$mode_paiement_label = 'MVOLA';
|
||||
break;
|
||||
|
||||
case 'Virement Bancaire':
|
||||
$fonds_disponible = $total_virement_disponible;
|
||||
$mode_paiement_label = 'virement bancaire';
|
||||
break;
|
||||
|
||||
default:
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Mode de paiement invalide'
|
||||
]);
|
||||
}
|
||||
|
||||
// Vérification des fonds
|
||||
if ($montant_retire > $fonds_disponible) {
|
||||
return $this->response->setJSON([
|
||||
'success' => false,
|
||||
'messages' => 'Modification échouée — fonds ' . $mode_paiement_label . ' insuffisants.<br>' .
|
||||
'<strong>Disponible:</strong> ' . number_format($fonds_disponible, 0, ',', ' ') . ' Ar<br>' .
|
||||
'<strong>Demandé:</strong> ' . number_format($montant_retire, 0, ',', ' ') . ' Ar'
|
||||
]);
|
||||
}
|
||||
|
||||
// PRÉPARATION DES DONNÉES
|
||||
|
||||
$data = [
|
||||
'montant_retire' => $montant_retire,
|
||||
'date_retrait' => date('Y-m-d H:i:s'),
|
||||
@ -1140,7 +974,6 @@ public function markAsPaid($id_sortie)
|
||||
'reference' => $this->request->getPost('reference_edit') ?? ''
|
||||
];
|
||||
|
||||
// Mapping source_fond et initiateur
|
||||
if (isset($this->mapping[$motif])) {
|
||||
$data['source_fond'] = $this->mapping[$motif]['source_fond'];
|
||||
$data['initiateur_demande'] = $this->mapping[$motif]['initiateur_demande'];
|
||||
@ -1149,7 +982,6 @@ public function markAsPaid($id_sortie)
|
||||
$data['initiateur_demande'] = 'Caissière';
|
||||
}
|
||||
|
||||
// Gestion du fichier (FACULTATIF)
|
||||
$preuveFile = $this->request->getFile('sortie_preuve_edit');
|
||||
if ($preuveFile && $preuveFile->isValid() && !$preuveFile->hasMoved()) {
|
||||
$newName = $preuveFile->getRandomName();
|
||||
@ -1163,9 +995,7 @@ public function markAsPaid($id_sortie)
|
||||
$data['preuve_achat'] = $newName;
|
||||
$data['mime_type'] = $preuveFile->getClientMimeType();
|
||||
}
|
||||
// Si aucun nouveau fichier n'est uploadé, on garde l'ancien (pas de modification)
|
||||
|
||||
// MISE À JOUR EN BASE
|
||||
if ($sortieCaisse->updateSortieCaisse($id_sortie, $data)) {
|
||||
return $this->response->setJSON([
|
||||
'success' => true,
|
||||
@ -1223,15 +1053,15 @@ public function markAsPaid($id_sortie)
|
||||
|
||||
switch ($statut) {
|
||||
case "Valider":
|
||||
$message = "✅ Votre décaissement a été validé par la Direction<br>Store: " . $this->returnStoreName($store_id);
|
||||
$message = "✅ Le décaissement a été validé par la Direction<br>Store: " . $this->returnStoreName($store_id);
|
||||
$Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
case "Refuser":
|
||||
$message = "❌ Votre décaissement a été refusé par la Direction<br>Store: " . $this->returnStoreName($store_id) . "<br>Raison: " . $this->request->getPost('admin_raison');
|
||||
$message = "❌ Le décaissement a été refusé par la Direction<br>Store: " . $this->returnStoreName($store_id) . "<br>Raison: " . $this->request->getPost('admin_raison');
|
||||
$Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
case "En attente":
|
||||
$message = "⏳ Votre décaissement a été mis en attente par la Direction<br>Store: " . $this->returnStoreName($store_id);
|
||||
$message = "⏳ Le décaissement a été mis en attente par la Direction<br>Store: " . $this->returnStoreName($store_id);
|
||||
$Notification->createNotification($message, "Caissière", (int)$store_id, 'sortieCaisse');
|
||||
break;
|
||||
}
|
||||
|
||||
302
app/Models/AutresEncaissements.php
Normal file
302
app/Models/AutresEncaissements.php
Normal file
@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class AutresEncaissements extends Model
|
||||
{
|
||||
protected $table = 'autres_encaissements';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
'type_encaissement',
|
||||
'autre_type',
|
||||
'montant',
|
||||
'mode_paiement',
|
||||
'commentaire',
|
||||
'user_id',
|
||||
'store_id',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
/**
|
||||
* Récupérer tous les encaissements avec les infos utilisateur et magasin
|
||||
*
|
||||
* @param int|null $storeId ID du magasin (optionnel)
|
||||
* @return array
|
||||
*/
|
||||
public function getEncaissementsWithDetails($storeId = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->select('
|
||||
autres_encaissements.*,
|
||||
CONCAT(users.firstname, " ", users.lastname) as user_name,
|
||||
users.email as user_email,
|
||||
stores.name as store_name
|
||||
');
|
||||
$builder->join('users', 'users.id = autres_encaissements.user_id', 'left');
|
||||
$builder->join('stores', 'stores.id = autres_encaissements.store_id', 'left');
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('autres_encaissements.store_id', $storeId);
|
||||
}
|
||||
|
||||
$builder->orderBy('autres_encaissements.created_at', 'DESC');
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer un encaissement par ID avec détails
|
||||
*
|
||||
* @param int $id ID de l'encaissement
|
||||
* @return array|null
|
||||
*/
|
||||
public function getTotalEncaissementsByMode($storeId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->select('
|
||||
SUM(CASE WHEN mode_paiement = "Espèces" THEN montant ELSE 0 END) as total_espece,
|
||||
SUM(CASE WHEN mode_paiement = "MVola" THEN montant ELSE 0 END) as total_mvola,
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant ELSE 0 END) as total_virement
|
||||
');
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('store_id', $storeId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$builder->where('DATE(created_at) >=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$builder->where('DATE(created_at) <=', $endDate);
|
||||
}
|
||||
|
||||
$result = $builder->get()->getRow();
|
||||
|
||||
return [
|
||||
'total_espece' => $result ? (float)$result->total_espece : 0,
|
||||
'total_mvola' => $result ? (float)$result->total_mvola : 0,
|
||||
'total_virement' => $result ? (float)$result->total_virement : 0,
|
||||
];
|
||||
}
|
||||
|
||||
public function getEncaissementById($id)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->select('
|
||||
autres_encaissements.*,
|
||||
CONCAT(users.firstname, " ", users.lastname) as user_name,
|
||||
users.email as user_email,
|
||||
stores.name as store_name
|
||||
');
|
||||
$builder->join('users', 'users.id = autres_encaissements.user_id', 'left');
|
||||
$builder->join('stores', 'stores.id = autres_encaissements.store_id', 'left');
|
||||
$builder->where('autres_encaissements.id', $id);
|
||||
|
||||
return $builder->get()->getRowArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiques par type d'encaissement
|
||||
*
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @param string|null $startDate Date de début (Y-m-d)
|
||||
* @param string|null $endDate Date de fin (Y-m-d)
|
||||
* @return array
|
||||
*/
|
||||
public function getStatsByType($storeId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
|
||||
// ✅ SÉLECTION DES CHAMPS (sans commentaire dans le SELECT)
|
||||
$builder->select('
|
||||
type_encaissement,
|
||||
mode_paiement,
|
||||
COUNT(*) as total_count,
|
||||
SUM(montant) as total_montant
|
||||
');
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('store_id', $storeId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$builder->where('DATE(created_at) >=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$builder->where('DATE(created_at) <=', $endDate);
|
||||
}
|
||||
|
||||
// ✅ GROUPER PAR type_encaissement ET mode_paiement
|
||||
$builder->groupBy('type_encaissement, mode_paiement');
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
/**
|
||||
* Total des encaissements
|
||||
*
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @param string|null $startDate Date de début (Y-m-d)
|
||||
* @param string|null $endDate Date de fin (Y-m-d)
|
||||
* @return float
|
||||
*/
|
||||
public function getTotalEncaissements($storeId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->selectSum('montant', 'total');
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('store_id', $storeId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$builder->where('DATE(created_at) >=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$builder->where('DATE(created_at) <=', $endDate);
|
||||
}
|
||||
|
||||
$result = $builder->get()->getRow();
|
||||
return $result ? (float)$result->total : 0;
|
||||
}
|
||||
/**
|
||||
* Nombre d'encaissements aujourd'hui
|
||||
*
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @return int
|
||||
*/
|
||||
public function getTodayCount($storeId = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->where('DATE(created_at)', date('Y-m-d'));
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('store_id', $storeId);
|
||||
}
|
||||
|
||||
return $builder->countAllResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nombre total d'encaissements
|
||||
*
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @param string|null $startDate Date de début (Y-m-d)
|
||||
* @param string|null $endDate Date de fin (Y-m-d)
|
||||
* @return int
|
||||
*/
|
||||
public function getTotalCount($storeId = null, $startDate = null, $endDate = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('store_id', $storeId);
|
||||
}
|
||||
|
||||
if ($startDate) {
|
||||
$builder->where('DATE(created_at) >=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$builder->where('DATE(created_at) <=', $endDate);
|
||||
}
|
||||
|
||||
return $builder->countAllResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encaissements récents (7 derniers jours)
|
||||
*
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @param int $limit Nombre de résultats
|
||||
* @return array
|
||||
*/
|
||||
public function getRecentEncaissements($storeId = null, $limit = 10)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->select('
|
||||
autres_encaissements.*,
|
||||
CONCAT(users.firstname, " ", users.lastname) as user_name,
|
||||
stores.name as store_name
|
||||
');
|
||||
$builder->join('users', 'users.id = autres_encaissements.user_id', 'left');
|
||||
$builder->join('stores', 'stores.id = autres_encaissements.store_id', 'left');
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('autres_encaissements.store_id', $storeId);
|
||||
}
|
||||
|
||||
$builder->where('DATE(autres_encaissements.created_at) >=', date('Y-m-d', strtotime('-7 days')));
|
||||
$builder->orderBy('autres_encaissements.created_at', 'DESC');
|
||||
$builder->limit($limit);
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encaissements par utilisateur
|
||||
*
|
||||
* @param int $userId ID de l'utilisateur
|
||||
* @param string|null $startDate Date de début
|
||||
* @param string|null $endDate Date de fin
|
||||
* @return array
|
||||
*/
|
||||
public function getEncaissementsByUser($userId, $startDate = null, $endDate = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->where('user_id', $userId);
|
||||
|
||||
if ($startDate) {
|
||||
$builder->where('DATE(created_at) >=', $startDate);
|
||||
}
|
||||
|
||||
if ($endDate) {
|
||||
$builder->where('DATE(created_at) <=', $endDate);
|
||||
}
|
||||
|
||||
$builder->orderBy('created_at', 'DESC');
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche d'encaissements
|
||||
*
|
||||
* @param string $keyword Mot-clé de recherche
|
||||
* @param int|null $storeId ID du magasin
|
||||
* @return array
|
||||
*/
|
||||
public function searchEncaissements($keyword, $storeId = null)
|
||||
{
|
||||
$builder = $this->db->table($this->table);
|
||||
$builder->select('
|
||||
autres_encaissements.*,
|
||||
CONCAT(users.firstname, " ", users.lastname) as user_name,
|
||||
stores.name as store_name
|
||||
');
|
||||
$builder->join('users', 'users.id = autres_encaissements.user_id', 'left');
|
||||
$builder->join('stores', 'stores.id = autres_encaissements.store_id', 'left');
|
||||
|
||||
$builder->groupStart()
|
||||
->like('autres_encaissements.type_encaissement', $keyword)
|
||||
->orLike('autres_encaissements.commentaire', $keyword)
|
||||
->orLike('CONCAT(users.firstname, " ", users.lastname)', $keyword)
|
||||
->groupEnd();
|
||||
|
||||
if ($storeId) {
|
||||
$builder->where('autres_encaissements.store_id', $storeId);
|
||||
}
|
||||
|
||||
$builder->orderBy('autres_encaissements.created_at', 'DESC');
|
||||
|
||||
return $builder->get()->getResultArray();
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,9 @@ class Avance extends Model {
|
||||
'avance_amount', 'avance_date','user_id',
|
||||
'customer_name', 'customer_address', 'customer_phone', 'customer_cin',
|
||||
'gross_amount','amount_due','product_id','is_order','active','store_id',
|
||||
'type_avance','type_payment', 'deadline','commentaire','product_name'
|
||||
];
|
||||
'type_avance','type_payment', 'deadline','commentaire','product_name',
|
||||
'validated', 'validated_by', 'validated_at'
|
||||
];
|
||||
|
||||
public function createAvance(array $data) {
|
||||
try {
|
||||
@ -210,44 +211,34 @@ class Avance extends Model {
|
||||
}
|
||||
|
||||
// ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière
|
||||
public function getPaymentModesAvance()
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
|
||||
|
||||
try {
|
||||
$builder = $this->db->table('avances')
|
||||
->select('
|
||||
SUM(avance_amount) AS total,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "mvola" THEN avance_amount ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "en espèce" THEN avance_amount ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
|
||||
')
|
||||
->where('active', 1)
|
||||
->where('is_order', 0); // ✅ Exclure les avances devenues orders
|
||||
|
||||
// ✅ CORRECTION : Ajouter le filtre store_id pour la caissière
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
$result = $builder->get()->getRowObject();
|
||||
|
||||
// ✅ Gérer le cas où il n'y a pas de résultats
|
||||
if (!$result) {
|
||||
return (object) [
|
||||
'total' => 0,
|
||||
'total_mvola' => 0,
|
||||
'total_espece' => 0,
|
||||
'total_virement_bancaire' => 0
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur getPaymentModesAvance: ' . $e->getMessage());
|
||||
// ✅ MODIFICATION : Ne compter QUE les avances VALIDÉES
|
||||
public function getPaymentModesAvance()
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
|
||||
|
||||
try {
|
||||
$builder = $this->db->table('avances')
|
||||
->select('
|
||||
SUM(avance_amount) AS total,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "mvola" THEN avance_amount ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "en espèce" THEN avance_amount ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
|
||||
')
|
||||
->where('validated', 1) // ✅ AJOUT : Uniquement les avances validées
|
||||
->where('active', 1)
|
||||
->where('is_order', 0);
|
||||
|
||||
// ✅ Filtre par store pour non-admin
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
$result = $builder->get()->getRowObject();
|
||||
|
||||
// ✅ Gérer le cas où il n'y a pas de résultats
|
||||
if (!$result) {
|
||||
return (object) [
|
||||
'total' => 0,
|
||||
'total_mvola' => 0,
|
||||
@ -255,7 +246,19 @@ class Avance extends Model {
|
||||
'total_virement_bancaire' => 0
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur getPaymentModesAvance: ' . $e->getMessage());
|
||||
return (object) [
|
||||
'total' => 0,
|
||||
'total_mvola' => 0,
|
||||
'total_espece' => 0,
|
||||
'total_virement_bancaire' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function getAllAvanceData1(int $id=null) {
|
||||
$session = session();
|
||||
@ -403,13 +406,53 @@ class Avance extends Model {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
|
||||
$isCommercial = in_array($users['group_name'], ['COMMERCIALE']);
|
||||
$isCaissier = in_array($users['group_name'], ['Caissière']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due >', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
// ✅ LOGIQUE PAR RÔLE
|
||||
if ($isCommercial) {
|
||||
// Commercial voit TOUTES ses avances (validées ET non validées)
|
||||
$builder->where('user_id', $users['id']);
|
||||
} elseif ($isCaissier) {
|
||||
// Caissière voit UNIQUEMENT les avances validées de son store
|
||||
$builder->where('validated', 1)
|
||||
->where('store_id', $users['store_id']);
|
||||
} elseif ($isAdmin) {
|
||||
// Admin voit tout (pas de filtre supplémentaire)
|
||||
} else {
|
||||
// Autres rôles : ne rien afficher
|
||||
$builder->where('1', '0'); // Condition toujours fausse
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$builder->where('user_id', $id);
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
public function getCompletedAvances(int $id = null)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
|
||||
$isCommercial = in_array($users['group_name'], ['COMMERCIALE']);
|
||||
$isCaissier = in_array($users['group_name'], ['Caissière']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due', 0);
|
||||
|
||||
if ($isCommercial) {
|
||||
$builder->where('user_id', $users['id']);
|
||||
} elseif ($isCaissier) {
|
||||
$builder->where('validated', 1)
|
||||
->where('store_id', $users['store_id']);
|
||||
} elseif (!$isAdmin) {
|
||||
$builder->where('1', '0');
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
@ -419,25 +462,65 @@ class Avance extends Model {
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
public function getCompletedAvances(int $id = null)
|
||||
/**
|
||||
* ✅ NOUVELLE : getPendingValidationAvances
|
||||
*/
|
||||
public function getPendingValidationAvances(int $store_id = null)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
try {
|
||||
$builder = $this->where('validated', 0)
|
||||
->where('active', 1)
|
||||
->where('is_order', 0);
|
||||
|
||||
if ($store_id) {
|
||||
$builder->where('store_id', $store_id);
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur getPendingValidationAvances: ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$builder->where('user_id', $id);
|
||||
/**
|
||||
* ✅ VALIDATION D'UNE AVANCE
|
||||
*/
|
||||
public function validateAvance(int $avance_id, int $caissiere_id): bool
|
||||
{
|
||||
try {
|
||||
$avance = $this->find($avance_id);
|
||||
|
||||
if (!$avance) {
|
||||
log_message('error', "Avance {$avance_id} introuvable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($avance['validated'] == 1) {
|
||||
log_message('warning', "Avance {$avance_id} déjà validée");
|
||||
return false;
|
||||
}
|
||||
|
||||
$updateResult = $this->update($avance_id, [
|
||||
'validated' => 1,
|
||||
'validated_by' => $caissiere_id,
|
||||
'validated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
if ($updateResult) {
|
||||
log_message('info', "✅ Avance {$avance_id} validée par caissière {$caissiere_id}");
|
||||
|
||||
// Vérifier si conversion nécessaire
|
||||
$this->autoCheckAndConvert($avance_id);
|
||||
}
|
||||
|
||||
return $updateResult;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "Erreur validation avance {$avance_id}: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
public function markAsPrinted($avance_id)
|
||||
|
||||
@ -8,7 +8,7 @@ use DateTime;
|
||||
class OrderItems extends Model
|
||||
{
|
||||
protected $table = 'orders_item';
|
||||
protected $allowedFields = ['order_id', 'product_id', 'puissance', 'rate', 'amount'];
|
||||
protected $allowedFields = ['order_id', 'product_id', 'puissance', 'rate', 'amount','qty'];
|
||||
|
||||
public function insertOrderItem($data)
|
||||
{
|
||||
|
||||
@ -212,11 +212,10 @@ class Orders extends Model
|
||||
'order_id' => $order_id,
|
||||
'product_id' => $post[$x],
|
||||
'rate' => $data['rate_value'][$x],
|
||||
'qty' => 1,
|
||||
'qty' => isset($data['qty'][$x]) ? (int)$data['qty'][$x] : 1,
|
||||
'amount' => $data['amount_value'][$x],
|
||||
'puissance' => $puissances[$x] ?? 1,
|
||||
];
|
||||
|
||||
$orderItemModel->insert($items);
|
||||
|
||||
// ✅ CORRECTION : Marquer product_sold = 1 dès la création
|
||||
@ -281,6 +280,7 @@ class Orders extends Model
|
||||
'order_id' => $id,
|
||||
'product_id' => $data['product'][$x],
|
||||
'rate' => $data['rate_value'][$x],
|
||||
'qty' => isset($data['qty'][$x]) ? (int)$data['qty'][$x] : 1,
|
||||
'puissance' => $data['puissance'][$x],
|
||||
'amount' => $data['amount_value'][$x],
|
||||
];
|
||||
@ -462,7 +462,7 @@ class Orders extends Model
|
||||
ELSE 0
|
||||
END) AS total_virement_bancaire2
|
||||
')
|
||||
->whereIn('orders.paid_status', [1, 2, 3]); // ← CHANGEZ CETTE LIGNE
|
||||
->whereIn('orders.paid_status', [1, 3]);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$baseQuery->where('orders.store_id', $users['store_id']);
|
||||
|
||||
@ -72,11 +72,11 @@ class SortieCaisse extends Model
|
||||
->findAll();
|
||||
}
|
||||
|
||||
// ✅ CAISSIÈRE : Voir uniquement SES décaissements
|
||||
// ✅ CAISSIÈRE : Voir TOUS les décaissements de SON STORE (pas seulement les siens)
|
||||
if($users["group_name"] === "Caissière"){
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->where('store_id', $users['store_id']) // ✅ CHANGEMENT ICI
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
@ -112,11 +112,11 @@ class SortieCaisse extends Model
|
||||
->findAll();
|
||||
}
|
||||
|
||||
// ✅ CAISSIÈRE : Voir uniquement SES décaissements
|
||||
// ✅ CAISSIÈRE : Voir TOUS les décaissements de SON STORE
|
||||
if($users["group_name"] === "Caissière"){
|
||||
return $this
|
||||
->select('*')
|
||||
->where('user_id', $users['id'])
|
||||
->where('store_id', $users['store_id']) // ✅ CHANGEMENT ICI
|
||||
->orderBy('date_retrait', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
@ -162,7 +162,6 @@ class SortieCaisse extends Model
|
||||
->first();
|
||||
return $reparation;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MODIFICATION : DAF, Direction, SuperAdmin voient TOUS les totaux
|
||||
*/
|
||||
@ -176,13 +175,13 @@ class SortieCaisse extends Model
|
||||
if ($isAdmin) {
|
||||
try {
|
||||
return $this->select('
|
||||
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN mode_paiement = "MVOLA" THEN montant_retire ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
|
||||
SUM(montant_retire) AS mr
|
||||
SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement,
|
||||
SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr
|
||||
')
|
||||
// ✅ SUPPRESSION DU FILTRE PAR STORE
|
||||
->whereIn('statut', ['Valider', 'Payé'])
|
||||
// ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider")
|
||||
->where('statut', 'Payé')
|
||||
->get()
|
||||
->getRowObject();
|
||||
} catch (\Exception $e) {
|
||||
@ -195,16 +194,17 @@ class SortieCaisse extends Model
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// ✅ CAISSIÈRE : Uniquement son store
|
||||
// ✅ CAISSIÈRE : Uniquement son store ET statut "Payé"
|
||||
try {
|
||||
return $this->select('
|
||||
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN mode_paiement = "MVOLA" THEN montant_retire ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
|
||||
SUM(montant_retire) AS mr
|
||||
SUM(CASE WHEN mode_paiement = "En espèce" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_espece,
|
||||
SUM(CASE WHEN mode_paiement = "MVOLA" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_mvola,
|
||||
SUM(CASE WHEN mode_paiement = "Virement Bancaire" AND statut = "Payé" THEN montant_retire ELSE 0 END) AS total_virement,
|
||||
SUM(CASE WHEN statut = "Payé" THEN montant_retire ELSE 0 END) AS mr
|
||||
')
|
||||
->where('store_id', $users['store_id'])
|
||||
->whereIn('statut', ['Valider', 'Payé'])
|
||||
// ✅ CHANGEMENT : Uniquement statut = "Payé" (plus "Valider")
|
||||
->where('statut', 'Payé')
|
||||
->get()
|
||||
->getRowObject();
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@ -64,6 +64,31 @@ class Users extends Model
|
||||
->where('groups.group_name', 'COMMERCIALE')
|
||||
->findAll(); // Get all matching users
|
||||
}
|
||||
public function getSecurityAgents()
|
||||
{
|
||||
return $this->select('users.id, users.firstname, users.lastname, CONCAT(users.firstname, " ", users.lastname) as full_name')
|
||||
->join('user_group', 'user_group.user_id = users.id', 'left')
|
||||
->join('groups', 'groups.id = user_group.group_id', 'left')
|
||||
->where('groups.group_name', 'SECURITE')
|
||||
->where('users.active', 1) // Uniquement les utilisateurs actifs
|
||||
->orderBy('users.firstname', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MÉTHODE GÉNÉRIQUE : Récupérer les utilisateurs par groupe
|
||||
*/
|
||||
public function getUsersByGroupName($groupName)
|
||||
{
|
||||
return $this->select('users.id, users.firstname, users.lastname, users.email, users.store_id, CONCAT(users.firstname, " ", users.lastname) as full_name, groups.group_name')
|
||||
->join('user_group', 'user_group.user_id = users.id', 'left')
|
||||
->join('groups', 'groups.id = user_group.group_id', 'left')
|
||||
->where('groups.group_name', strtoupper($groupName))
|
||||
->where('users.active', 1)
|
||||
->orderBy('users.firstname', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* get grouped user by id
|
||||
* @param mixed $userId
|
||||
|
||||
978
app/Views/autres_encaissements/index.php
Normal file
978
app/Views/autres_encaissements/index.php
Normal file
@ -0,0 +1,978 @@
|
||||
<!-- application/Views/autres_encaissements/index.php -->
|
||||
<style>
|
||||
.encaissement-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
color: white;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.encaissement-header h1 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color : white;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 5px solid #2196f3;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #1976d2;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: #1565c0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.form-section h3 {
|
||||
color: #667eea;
|
||||
font-weight: 700;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 3px solid #667eea;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
|
||||
.btn-primary-custom {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary-custom:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#historyTable thead {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#historyTable thead th {
|
||||
border: none !important;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#historyTable tbody tr {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#historyTable tbody tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
#autreTypeGroup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
min-width: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header -->
|
||||
<section class="content-header">
|
||||
<div class="encaissement-header">
|
||||
<h1>
|
||||
<i class="fa fa-money"></i> Autres Encaissements
|
||||
</h1>
|
||||
<p style="margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;">
|
||||
Gestion des encaissements divers (Plastification, Duplicata, etc.)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div id="messages"></div>
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<i class="fa fa-money" style="font-size: 40px; color: #2196f3;"></i>
|
||||
<div class="stat-number" id="totalMontant">0 Ar</div>
|
||||
<div class="stat-label">Total Encaissements</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<i class="fa fa-calendar" style="font-size: 40px; color: #2196f3;"></i>
|
||||
<div class="stat-number" id="todayCount">0</div>
|
||||
<div class="stat-label">Encaissements Aujourd'hui</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire d'ajout -->
|
||||
<?php if (in_array('createEncaissement', $user_permission)): ?>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-section">
|
||||
<h3><i class="fa fa-plus-circle"></i> Ajouter un Encaissement</h3>
|
||||
|
||||
<form id="formEncaissement">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="typeEncaissement">Type d'Encaissement <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="typeEncaissement" name="type_encaissement" required>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Plastification">Plastification</option>
|
||||
<option value="Duplicata">Duplicata</option>
|
||||
<option value="Retour Décaissement">Retour Décaissement</option>
|
||||
<option value="Vente de Pièce">Vente de Pièce</option>
|
||||
<option value="Autre">Autre (Spécifier)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3" id="autreTypeGroup">
|
||||
<div class="form-group">
|
||||
<label for="autreType">Spécifier le type <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="autreType" name="autre_type" placeholder="Ex: Frais de dossier">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="montant">Montant (Ar) <span class="text-danger">*</span></label>
|
||||
<input type="number" class="form-control" id="montant" name="montant" placeholder="Ex: 50000" required min="0" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU CHAMP MODE DE PAIEMENT -->
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="modePaiement">Mode de Paiement <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="modePaiement" name="mode_paiement" required>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Espèces">Espèces</option>
|
||||
<option value="MVola">MVola</option>
|
||||
<option value="Virement Bancaire">Virement Bancaire</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="commentaire">Commentaire</label>
|
||||
<textarea class="form-control" id="commentaire" name="commentaire" rows="3" placeholder="Ajouter un commentaire (optionnel)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<button type="reset" class="btn btn-default">
|
||||
<i class="fa fa-refresh"></i> Réinitialiser
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa fa-save"></i> Enregistrer l'Encaissement
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Filtres -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="filter-section">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="filterStartDate">Date de début</label>
|
||||
<input type="date" id="filterStartDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="filterEndDate">Date de fin</label>
|
||||
<input type="date" id="filterEndDate" class="form-control">
|
||||
</div>
|
||||
<?php if (in_array($user_role, ['SuperAdmin', 'DAF', 'Direction'])): ?>
|
||||
<div class="col-md-3">
|
||||
<label for="filterStore">Magasin</label>
|
||||
<select id="filterStore" class="form-control">
|
||||
<option value="">Tous les magasins</option>
|
||||
<?php foreach ($stores as $store): ?>
|
||||
<option value="<?= $store['id'] ?>"><?= $store['name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="col-md-3">
|
||||
<label> </label><br>
|
||||
<button id="btnFilter" class="btn btn-primary">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="btnReset" class="btn btn-warning">
|
||||
<i class="fa fa-refresh"></i> Réinitialiser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tableau historique -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-history"></i> Historique des Encaissements
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table id="historyTable" class="table table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Type</th>
|
||||
<th>Montant</th>
|
||||
<th>Mode</th> <!-- ✅ NOUVELLE COLONNE -->
|
||||
<th>Commentaire</th>
|
||||
<th>Créé par</th>
|
||||
<th>Magasin</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Les données seront chargées par DataTables -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Modal Détails -->
|
||||
<div class="modal fade" id="detailsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<button type="button" class="close" data-dismiss="modal" style="color: white;">×</button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-info-circle"></i> Détails de l'Encaissement
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<i class="fa fa-money"></i> Informations Encaissement
|
||||
</h4>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>ID:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailId"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Type:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailType" style="font-weight: 600; color: #667eea;"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Montant:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailMontant" style="font-size: 22px; font-weight: bold; color: #2196f3;"></span></div>
|
||||
</div>
|
||||
<!-- ✅ NOUVEAU CHAMP -->
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Mode de paiement:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailMode"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Date:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailDate"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<i class="fa fa-user"></i> Informations Utilisateur
|
||||
</h4>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Créé par:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailUser"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Email:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailEmail"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-5"><strong>Magasin:</strong></div>
|
||||
<div class="col-xs-7"><span id="detailStore"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-md-12">
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<i class="fa fa-comment"></i> Commentaire
|
||||
</h4>
|
||||
<p id="detailCommentaire" style="padding: 15px; background: #f8f9fa; border-radius: 8px; min-height: 60px;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i> Fermer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Édition -->
|
||||
<?php if (in_array('updateEncaissement', $user_permission)): ?>
|
||||
<div class="modal fade" id="editModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<button type="button" class="close" data-dismiss="modal" style="color: white;">×</button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-edit"></i> Modifier l'Encaissement
|
||||
</h4>
|
||||
</div>
|
||||
<form id="formEditEncaissement">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editTypeEncaissement">Type d'Encaissement <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="editTypeEncaissement" name="type_encaissement" required>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Plastification">Plastification</option>
|
||||
<option value="Duplicata">Duplicata</option>
|
||||
<option value="Retour Décaissement">Retour Décaissement</option>
|
||||
<option value="Vente de Pièce">Vente de Pièce</option>
|
||||
<option value="Autre">Autre (Spécifier)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="editAutreTypeGroup" style="display: none;">
|
||||
<label for="editAutreType">Spécifier le type</label>
|
||||
<input type="text" class="form-control" id="editAutreType" name="autre_type" placeholder="Ex: Frais de dossier">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editMontant">Montant (Ar) <span class="text-danger">*</span></label>
|
||||
<input type="number" class="form-control" id="editMontant" name="montant" required min="0" step="0.01">
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU CHAMP MODE DE PAIEMENT -->
|
||||
<div class="form-group">
|
||||
<label for="editModePaiement">Mode de Paiement <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="editModePaiement" name="mode_paiement" required>
|
||||
<option value="">-- Sélectionner --</option>
|
||||
<option value="Espèces">Espèces</option>
|
||||
<option value="MVola">MVola</option>
|
||||
<option value="Virement Bancaire">Virement Bancaire</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editCommentaire">Commentaire</label>
|
||||
<textarea class="form-control" id="editCommentaire" name="commentaire" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i> Annuler
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary-custom">
|
||||
<i class="fa fa-save"></i> Enregistrer les modifications
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<script>
|
||||
// ✅ DÉCLARER LA FONCTION EN DEHORS DU $(function) POUR LA RENDRE GLOBALE
|
||||
function loadStatisticsForFilteredPeriod(startDate, endDate, store_id) {
|
||||
console.log('📊 [STATS] Début chargement statistiques');
|
||||
console.log('📊 [STATS] Paramètres:', {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
store_id: store_id
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/statistics') ?>',
|
||||
type: 'GET',
|
||||
data: {
|
||||
startDate: startDate || '',
|
||||
endDate: endDate || '',
|
||||
store_id: store_id || ''
|
||||
},
|
||||
beforeSend: function() {
|
||||
console.log('📊 [STATS] Requête envoyée...');
|
||||
// Afficher un loader
|
||||
$('#totalMontant').html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
$('#todayCount').html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('✅ [STATS] Réponse reçue:', response);
|
||||
|
||||
if (response.success) {
|
||||
console.log('✅ [STATS] Mise à jour des valeurs:', {
|
||||
total_montant: response.total_montant,
|
||||
today_count: response.today_count,
|
||||
total_count: response.total_count
|
||||
});
|
||||
|
||||
// ✅ MISE À JOUR DES CARTES
|
||||
$('#totalMontant').text(response.total_montant);
|
||||
$('#todayCount').text(response.today_count);
|
||||
|
||||
// Si tu ajoutes une carte pour total_count
|
||||
if ($('#totalCount').length) {
|
||||
$('#totalCount').text(response.total_count);
|
||||
}
|
||||
|
||||
// ✅ Si tu as des éléments pour afficher les totaux par mode
|
||||
if ($('#totalEspece').length) {
|
||||
$('#totalEspece').text(response.total_espece);
|
||||
}
|
||||
if ($('#totalMvola').length) {
|
||||
$('#totalMvola').text(response.total_mvola);
|
||||
}
|
||||
if ($('#totalVirement').length) {
|
||||
$('#totalVirement').text(response.total_virement);
|
||||
}
|
||||
|
||||
console.log('✅ [STATS] DOM mis à jour avec succès');
|
||||
|
||||
// Afficher les infos de debug si disponibles
|
||||
if (response.debug) {
|
||||
console.log('🔍 [DEBUG]', response.debug);
|
||||
}
|
||||
} else {
|
||||
console.error('❌ [STATS] Erreur: response.success = false', response);
|
||||
$('#totalMontant').text('Erreur');
|
||||
$('#todayCount').text('Erreur');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('❌ [STATS] Erreur AJAX complète:', {
|
||||
status: status,
|
||||
error: error,
|
||||
responseText: xhr.responseText,
|
||||
statusCode: xhr.status
|
||||
});
|
||||
|
||||
// Essayer de parser la réponse
|
||||
try {
|
||||
var errorResponse = JSON.parse(xhr.responseText);
|
||||
console.error('❌ [STATS] Détails erreur:', errorResponse);
|
||||
} catch(e) {
|
||||
console.error('❌ [STATS] Impossible de parser la réponse');
|
||||
}
|
||||
|
||||
$('#totalMontant').text('0 Ar');
|
||||
$('#todayCount').text('0');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
console.log('🚀 [INIT] Initialisation de la page...');
|
||||
|
||||
$("#encaissementNav").addClass('active');
|
||||
|
||||
// Afficher/masquer le champ "Autre type" - Formulaire principal
|
||||
$('#typeEncaissement').on('change', function() {
|
||||
if ($(this).val() === 'Autre') {
|
||||
$('#autreTypeGroup').slideDown();
|
||||
$('#autreType').prop('required', true);
|
||||
} else {
|
||||
$('#autreTypeGroup').slideUp();
|
||||
$('#autreType').prop('required', false).val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher/masquer le champ "Autre type" - Modal édition
|
||||
$('#editTypeEncaissement').on('change', function() {
|
||||
if ($(this).val() === 'Autre') {
|
||||
$('#editAutreTypeGroup').slideDown();
|
||||
$('#editAutreType').prop('required', true);
|
||||
} else {
|
||||
$('#editAutreTypeGroup').slideUp();
|
||||
$('#editAutreType').prop('required', false).val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration DataTable en français
|
||||
$.extend(true, $.fn.dataTable.defaults, {
|
||||
language: {
|
||||
sProcessing: "Traitement en cours...",
|
||||
sSearch: "Rechercher :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Aucun élément à afficher",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucun encaissement trouvé",
|
||||
sEmptyTable: "Aucune donnée disponible",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ INITIALISER LES DATES À AUJOURD'HUI PAR DÉFAUT
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
|
||||
console.log('📅 [INIT] Dates initialisées:', {
|
||||
startDate: $('#filterStartDate').val(),
|
||||
endDate: $('#filterEndDate').val()
|
||||
});
|
||||
|
||||
// ✅ FONCTION : CHARGER LES STATISTIQUES DU MOIS EN COURS
|
||||
function loadStatisticsForCurrentMonth() {
|
||||
console.log('📊 [INIT] Chargement des statistiques du mois en cours...');
|
||||
|
||||
var now = new Date();
|
||||
var firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
var lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
var startDate = firstDay.toISOString().split('T')[0];
|
||||
var endDate = lastDay.toISOString().split('T')[0];
|
||||
var store_id = $('#filterStore').val() || '';
|
||||
|
||||
console.log('📅 [INIT] Période du mois:', {
|
||||
firstDay: startDate,
|
||||
lastDay: endDate,
|
||||
store_id: store_id
|
||||
});
|
||||
|
||||
// ✅ Appeler la fonction globale
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, store_id);
|
||||
}
|
||||
|
||||
// ✅ INITIALISATION DATATABLE
|
||||
var historyTable = $('#historyTable').DataTable({
|
||||
ajax: {
|
||||
url: '<?= base_url('encaissements/fetch') ?>',
|
||||
type: 'GET',
|
||||
data: function(d) {
|
||||
d.startDate = $('#filterStartDate').val();
|
||||
d.endDate = $('#filterEndDate').val();
|
||||
d.store_id = $('#filterStore').val();
|
||||
|
||||
console.log('📤 [DATATABLE] Filtres envoyés:', {
|
||||
startDate: d.startDate,
|
||||
endDate: d.endDate,
|
||||
store_id: d.store_id
|
||||
});
|
||||
},
|
||||
dataSrc: function(json) {
|
||||
console.log('📥 [DATATABLE] Données reçues:', json.data.length, 'lignes');
|
||||
return json.data;
|
||||
},
|
||||
error: function(xhr, error, code) {
|
||||
console.error('❌ [DATATABLE] Erreur:', error);
|
||||
showAlert('danger', 'Erreur lors du chargement des données');
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{ data: 0 }, // ID
|
||||
{ data: 1 }, // Type
|
||||
{ data: 2 }, // Montant
|
||||
{ data: 3 }, // Mode de paiement
|
||||
{ data: 4 }, // Commentaire
|
||||
{ data: 5 }, // Utilisateur
|
||||
{ data: 6 }, // Magasin
|
||||
{ data: 7 }, // Date
|
||||
{ data: 8, orderable: false } // Actions
|
||||
],
|
||||
order: [[7, 'desc']],
|
||||
pageLength: 10,
|
||||
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, "Tout"]]
|
||||
});
|
||||
|
||||
console.log('✅ [INIT] DataTable initialisée');
|
||||
|
||||
// ✅ CHARGER LES STATISTIQUES DU MOIS AU DÉMARRAGE
|
||||
console.log('🚀 [INIT] Appel de loadStatisticsForCurrentMonth()');
|
||||
loadStatisticsForCurrentMonth();
|
||||
|
||||
// ✅ BOUTON FILTRER - RECHARGE TABLEAU + STATISTIQUES
|
||||
$('#btnFilter').on('click', function() {
|
||||
console.log('🔍 [FILTER] Bouton Filtrer cliqué');
|
||||
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
var store_id = $('#filterStore').val();
|
||||
|
||||
console.log('📋 [FILTER] Filtres appliqués:', {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
store_id: store_id
|
||||
});
|
||||
|
||||
// ✅ RECHARGER LE TABLEAU
|
||||
historyTable.ajax.reload();
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES SELON LA PÉRIODE FILTRÉE
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, store_id);
|
||||
});
|
||||
|
||||
// ✅ BOUTON RÉINITIALISER
|
||||
$('#btnReset').on('click', function() {
|
||||
console.log('🔄 [RESET] Réinitialisation des filtres');
|
||||
|
||||
// ✅ RÉINITIALISER À LA DATE DU JOUR
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
$('#filterStore').val('');
|
||||
|
||||
console.log('📅 [RESET] Nouvelles dates:', {
|
||||
startDate: today,
|
||||
endDate: today
|
||||
});
|
||||
|
||||
// ✅ RECHARGER LE TABLEAU
|
||||
historyTable.ajax.reload();
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES DU MOIS EN COURS
|
||||
loadStatisticsForCurrentMonth();
|
||||
});
|
||||
|
||||
// ✅ CHANGEMENT DE MAGASIN - RECHARGER AUTOMATIQUEMENT
|
||||
$('#filterStore').on('change', function() {
|
||||
var selectedStore = $(this).val();
|
||||
console.log('🏪 [STORE] Changement de magasin:', selectedStore || 'Tous');
|
||||
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
|
||||
// ✅ RECHARGER LE TABLEAU
|
||||
historyTable.ajax.reload();
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, selectedStore);
|
||||
});
|
||||
|
||||
// ✅ FORMULAIRE D'AJOUT
|
||||
$('#formEncaissement').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
console.log('➕ [CREATE] Soumission du formulaire d\'ajout');
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/create') ?>',
|
||||
type: 'POST',
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
beforeSend: function() {
|
||||
$('button[type="submit"]').prop('disabled', true)
|
||||
.html('<i class="fa fa-spinner fa-spin"></i> Enregistrement...');
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('✅ [CREATE] Réponse:', response);
|
||||
|
||||
if (response.success) {
|
||||
showAlert('success', response.messages);
|
||||
$('#formEncaissement')[0].reset();
|
||||
$('#autreTypeGroup').hide();
|
||||
historyTable.ajax.reload(null, false);
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES APRÈS AJOUT
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
var store_id = $('#filterStore').val();
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, store_id);
|
||||
} else {
|
||||
var errorMsg = typeof response.messages === 'object'
|
||||
? Object.values(response.messages).join('<br>')
|
||||
: response.messages;
|
||||
showAlert('danger', errorMsg);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error('❌ [CREATE] Erreur:', xhr);
|
||||
var errorMsg = 'Erreur lors de l\'enregistrement';
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.messages) {
|
||||
errorMsg = typeof response.messages === 'object'
|
||||
? Object.values(response.messages).join('<br>')
|
||||
: response.messages;
|
||||
}
|
||||
} catch(e) {
|
||||
errorMsg += ': ' + xhr.statusText;
|
||||
}
|
||||
showAlert('danger', errorMsg);
|
||||
},
|
||||
complete: function() {
|
||||
$('button[type="submit"]').prop('disabled', false)
|
||||
.html('<i class="fa fa-save"></i> Enregistrer l\'Encaissement');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ FORMULAIRE D'ÉDITION
|
||||
$('#formEditEncaissement').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var id = $('#editId').val();
|
||||
console.log('✏️ [UPDATE] Soumission du formulaire d\'édition, ID:', id);
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/update/') ?>' + id,
|
||||
type: 'POST',
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
console.log('✅ [UPDATE] Réponse:', response);
|
||||
|
||||
if (response.success) {
|
||||
$('#editModal').modal('hide');
|
||||
showAlert('success', response.messages);
|
||||
historyTable.ajax.reload(null, false);
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES APRÈS MODIFICATION
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
var store_id = $('#filterStore').val();
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, store_id);
|
||||
} else {
|
||||
var errorMsg = typeof response.messages === 'object'
|
||||
? Object.values(response.messages).join('<br>')
|
||||
: response.messages;
|
||||
showAlert('danger', errorMsg);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error('❌ [UPDATE] Erreur:', xhr);
|
||||
var errorMsg = 'Erreur lors de la modification';
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.messages) {
|
||||
errorMsg = response.messages;
|
||||
}
|
||||
} catch(e) {
|
||||
errorMsg += ': ' + xhr.statusText;
|
||||
}
|
||||
showAlert('danger', errorMsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ [INIT] Page complètement initialisée');
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// AUTRES FONCTIONS (viewDetails, editEncaissement, deleteEncaissement, showAlert)
|
||||
// ============================================
|
||||
function viewDetails(id) {
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/details/') ?>' + id,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
var data = response.data;
|
||||
$('#detailId').text(data.id);
|
||||
$('#detailType').text(data.type_encaissement);
|
||||
$('#detailMontant').text(new Intl.NumberFormat('fr-FR').format(data.montant) + ' Ar');
|
||||
$('#detailMode').text(data.mode_paiement);
|
||||
$('#detailDate').text(new Date(data.created_at).toLocaleString('fr-FR'));
|
||||
$('#detailUser').text(data.user_name);
|
||||
$('#detailEmail').text(data.user_email);
|
||||
$('#detailStore').text(data.store_name);
|
||||
$('#detailCommentaire').text(data.commentaire || 'Aucun commentaire');
|
||||
|
||||
$('#detailsModal').modal('show');
|
||||
} else {
|
||||
showAlert('danger', response.message || 'Erreur lors du chargement des détails');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
showAlert('danger', 'Erreur lors du chargement des détails');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editEncaissement(id) {
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/getEncaissement/') ?>' + id,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
var data = response.data;
|
||||
$('#editId').val(data.id);
|
||||
$('#editTypeEncaissement').val(data.type_encaissement);
|
||||
$('#editAutreType').val(data.autre_type);
|
||||
$('#editMontant').val(data.montant);
|
||||
$('#editModePaiement').val(data.mode_paiement);
|
||||
$('#editCommentaire').val(data.commentaire);
|
||||
|
||||
var predefinedTypes = ['Plastification', 'Duplicata', 'Retour Décaissement', 'Vente de Pièce'];
|
||||
if (data.type_encaissement === 'Autre' || !predefinedTypes.includes(data.type_encaissement)) {
|
||||
$('#editTypeEncaissement').val('Autre');
|
||||
$('#editAutreTypeGroup').show();
|
||||
$('#editAutreType').val(data.type_encaissement);
|
||||
}
|
||||
|
||||
$('#editModal').modal('show');
|
||||
} else {
|
||||
showAlert('danger', response.message || 'Erreur lors du chargement');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
showAlert('danger', 'Erreur lors du chargement de l\'encaissement');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteEncaissement(id) {
|
||||
Swal.fire({
|
||||
title: 'Confirmation',
|
||||
text: "Voulez-vous vraiment supprimer cet encaissement ?",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#3085d6',
|
||||
confirmButtonText: 'Oui, supprimer',
|
||||
cancelButtonText: 'Annuler'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
console.log('🗑️ [DELETE] Suppression de l\'encaissement ID:', id);
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('encaissements/delete/') ?>' + id,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
console.log('✅ [DELETE] Réponse:', response);
|
||||
|
||||
if (response.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Supprimé',
|
||||
text: response.messages,
|
||||
timer: 2000,
|
||||
showConfirmButton: false
|
||||
});
|
||||
|
||||
$('#historyTable').DataTable().ajax.reload(null, false);
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES APRÈS SUPPRESSION
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
var store_id = $('#filterStore').val();
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, store_id);
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: response.messages || 'Erreur lors de la suppression'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error('❌ [DELETE] Erreur:', xhr);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur serveur',
|
||||
text: 'Impossible de supprimer cet encaissement'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showAlert(type, message) {
|
||||
var alertHtml = '<div class="alert alert-' + type + ' alert-dismissible alert-notification">' +
|
||||
'<button type="button" class="close" data-dismiss="alert">×</button>' +
|
||||
message +
|
||||
'</div>';
|
||||
|
||||
$('.alert-notification').remove();
|
||||
|
||||
$('body').append(alertHtml);
|
||||
|
||||
setTimeout(function() {
|
||||
$('.alert-notification').fadeOut('slow', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,231 @@
|
||||
/* ============================================
|
||||
VARIABLES CSS - Design System Couleurs Unies
|
||||
============================================ */
|
||||
.treasury-summary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.treasury-summary .box-header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.treasury-summary .box-title {
|
||||
color: #667eea;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.treasury-summary .box-body {
|
||||
background: #ffffff;
|
||||
padding: 35px 25px;
|
||||
}
|
||||
|
||||
/* Blocs de montants */
|
||||
.amount-block {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 25px 15px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.amount-block::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, transparent, currentColor, transparent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.amount-block:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.amount-block:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Bloc Total Brut */
|
||||
.amount-block.brut {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
||||
border-left: 5px solid #4caf50;
|
||||
}
|
||||
|
||||
.amount-block.brut::before {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
/* Bloc Décaissements */
|
||||
.amount-block.sorties {
|
||||
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
|
||||
border-left: 5px solid #f44336;
|
||||
}
|
||||
|
||||
.amount-block.sorties::before {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
/* Bloc Solde Net */
|
||||
.amount-block.net {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-left: 5px solid #2196f3;
|
||||
border: 3px solid #2196f3;
|
||||
}
|
||||
|
||||
.amount-block.net::before {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
/* En-têtes des montants */
|
||||
.amount-header {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
margin: 15px 0 10px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.amount-sublabel {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Opérateurs mathématiques */
|
||||
.math-operator {
|
||||
font-size: 48px;
|
||||
font-weight: 300;
|
||||
color: #9e9e9e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* Séparateur stylisé */
|
||||
.custom-divider {
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #667eea, transparent);
|
||||
margin: 30px 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Détail par mode de paiement */
|
||||
.payment-detail-block {
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.payment-detail-block:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: currentColor;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.payment-detail-block.espece {
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
|
||||
.payment-detail-block.mvola {
|
||||
border-left: 4px solid #ff9800;
|
||||
}
|
||||
|
||||
.payment-detail-block.banque {
|
||||
border-left: 4px solid #2196f3;
|
||||
}
|
||||
|
||||
.payment-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.payment-icon.espece {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.payment-icon.mvola {
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.payment-icon.banque {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.payment-amount {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #212529;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.payment-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: #6c757d;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Animation au chargement */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.treasury-summary {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.math-operator {
|
||||
font-size: 32px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.amount-header {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.treasury-summary .box-body {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -79,28 +304,110 @@
|
||||
</div>
|
||||
|
||||
<!-- Détail Orders vs Avances -->
|
||||
<div class="container-fluid row" style="margin-top: 10px;">
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
<div class="info-box bg-aqua">
|
||||
<span class="info-box-icon"><i class="fa fa-shopping-cart"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Total Orders (Ventes complètes)</span>
|
||||
<span class="info-box-number"><?php echo number_format($total_orders_only, 0, '.', ' '); ?> Ar</span>
|
||||
<div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;">
|
||||
<div class="col-lg-12">
|
||||
<div class="box box-success treasury-summary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-calculator"></i> Résumé de Trésorerie
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
<div class="info-box bg-yellow">
|
||||
<span class="info-box-icon"><i class="fa fa-clock-o"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Total Avances (Paiements partiels)</span>
|
||||
<span class="info-box-number"><?php echo number_format($total_avances, 0, '.', ' '); ?> Ar</span>
|
||||
|
||||
<div class="box-body">
|
||||
<!-- Calcul Principal : Brut - Sorties = Net -->
|
||||
<div class="row text-center">
|
||||
<!-- Total Brut Encaissé -->
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="amount-block brut">
|
||||
<span class="amount-label" style="color: #388e3c;">💰 Total Brut</span>
|
||||
<div class="amount-header" style="color: #2e7d32;">
|
||||
<?php echo number_format($total_brut, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<span class="amount-sublabel">Orders + Avances</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opérateur Moins -->
|
||||
<div class="col-sm-1 col-xs-12">
|
||||
<div class="math-operator">−</div>
|
||||
</div>
|
||||
|
||||
<!-- Décaissements Payés -->
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="amount-block sorties">
|
||||
<span class="amount-label" style="color: #c62828;">💸 Décaissements</span>
|
||||
<div class="amount-header" style="color: #d32f2f;">
|
||||
<?php echo number_format($total_sorties, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<span class="amount-sublabel">Espèce + MVOLA + Virement</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opérateur Égal -->
|
||||
<div class="col-sm-1 col-xs-12">
|
||||
<div class="math-operator">=</div>
|
||||
</div>
|
||||
|
||||
<!-- Solde Net Disponible -->
|
||||
<div class="col-sm-4 col-xs-12">
|
||||
<div class="amount-block net">
|
||||
<span class="amount-label" style="color: #1565c0;">✅ Solde Net</span>
|
||||
<div class="amount-header" style="color: #1976d2; font-size: 36px;">
|
||||
<?php echo number_format($total_caisse, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<span class="amount-sublabel">Disponible en caisse</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Séparateur Stylisé -->
|
||||
<hr class="custom-divider">
|
||||
|
||||
<!-- Détail par Mode de Paiement -->
|
||||
<div class="row text-center">
|
||||
<!-- Espèce -->
|
||||
<div class="col-sm-4 col-xs-12" style="margin-bottom: 15px;">
|
||||
<div class="payment-detail-block espece">
|
||||
<div class="payment-icon espece">
|
||||
<i class="fa fa-money"></i>
|
||||
</div>
|
||||
<div class="payment-amount">
|
||||
<?php echo number_format($total_espece_caisse, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<div class="payment-label">💵 Espèce Disponible</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MVOLA -->
|
||||
<div class="col-sm-4 col-xs-12" style="margin-bottom: 15px;">
|
||||
<div class="payment-detail-block mvola">
|
||||
<div class="payment-icon mvola">
|
||||
<i class="fa fa-mobile"></i>
|
||||
</div>
|
||||
<div class="payment-amount">
|
||||
<?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<div class="payment-label">📱 MVOLA Disponible</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banque -->
|
||||
<div class="col-sm-4 col-xs-12" style="margin-bottom: 15px;">
|
||||
<div class="payment-detail-block banque">
|
||||
<div class="payment-icon banque">
|
||||
<i class="fa fa-bank"></i>
|
||||
</div>
|
||||
<div class="payment-amount">
|
||||
<?php echo number_format($total_vb_caisse, 0, '.', ' '); ?> Ar
|
||||
</div>
|
||||
<div class="payment-label">🏦 Banque Disponible</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Détail des avances par mode de paiement -->
|
||||
<div class="container-fluid row" style="margin-top: 10px; margin-bottom: 20px;">
|
||||
<div class="col-lg-12">
|
||||
@ -217,7 +524,7 @@
|
||||
var caissierTable;
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log('🔍 Initialisation du tableau caissier...');
|
||||
// console.log('🔍 Initialisation du tableau caissier...');
|
||||
|
||||
// Configuration DataTable pour caissier
|
||||
caissierTable = $('#caissierperf').DataTable({
|
||||
@ -288,7 +595,7 @@
|
||||
const startDate = $('#startDateCaissier').val();
|
||||
const endDate = $('#endDateCaissier').val();
|
||||
|
||||
console.log('🔍 Filtrage:', startDate, endDate);
|
||||
// console.log('🔍 Filtrage:', startDate, endDate);
|
||||
|
||||
// Recharger les données avec les nouveaux paramètres
|
||||
caissierTable.ajax.reload();
|
||||
@ -670,79 +977,86 @@
|
||||
<div class="col-12">
|
||||
|
||||
<!-- CARD COMMERCIAL -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<!-- CARD COMMERCIAL -->
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white text-primary font-weight-bold">
|
||||
<h3 class="card-title m-0">
|
||||
<i class="fa fa-users"></i> Performances des commerciaux
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white text-primary font-weight-bold">
|
||||
<h3 class="card-title m-0">
|
||||
<i class="fa fa-users"></i> Performances des commercials
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Filtres Commercial - AVEC IDs UNIQUES -->
|
||||
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filterBtnComm" class="btn btn-primary" style="margin-right: 5px;">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="resetFilterComm" class="btn btn-warning" style="margin-right: 5px;">
|
||||
<i class="fa fa-refresh"></i> Aujourd'hui
|
||||
</button>
|
||||
<button id="exportBtnComm" class="btn btn-success">
|
||||
<i class="fa fa-file-excel-o"></i> Exporter
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="pventeComm" class="form-label">Points de ventes</label>
|
||||
<select id="pventeComm" class="form-control">
|
||||
<option value="TOUS">TOUS</option>
|
||||
<?php foreach ($stores as $value): ?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filterBtnComm" class="btn btn-primary w-100">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="exportBtnComm" class="btn btn-success w-100" style="margin-left: 5px;">
|
||||
<i class="fa fa-file-excel-o"></i> Exporter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table id="commperformance" class="table table-hover table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom et prénom</th>
|
||||
<th>Email</th>
|
||||
<th>Motos vendue</th>
|
||||
<th>Date de vente</th>
|
||||
<th>Prix d'achat</th>
|
||||
<th>Prix de vente</th>
|
||||
<th>Point de ventes</th>
|
||||
<th>Bénefices</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="5" style="text-align:right; font-weight: bold;">Total :</th>
|
||||
<th style="font-weight: bold;"></th>
|
||||
<th></th>
|
||||
<th style="font-weight: bold;"></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filtres Commercial -->
|
||||
<div class="row g-3 align-items-center mb-4" style="margin:5px;">
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="startDateComm" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDateComm" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="endDateComm" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDateComm" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="pventeComm" class="form-label">Points de ventes</label>
|
||||
<select id="pventeComm" class="form-control">
|
||||
<option value="TOUS">TOUS</option>
|
||||
<?php foreach ($stores as $value): ?>
|
||||
<option value="<?= $value['name']; ?>"><?= $value['name']; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button id="filterBtnComm" class="btn btn-primary w-100">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="resetFilterComm" class="btn btn-warning w-100" style="margin-left:5px;">
|
||||
<i class="fa fa-refresh"></i> Aujourd'hui
|
||||
</button>
|
||||
<button id="exportBtnComm" class="btn btn-success w-100" style="margin-left:5px;">
|
||||
<i class="fa fa-file-excel-o"></i> Exporter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABLE -->
|
||||
<table id="commperformance" class="table table-hover table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom et prénom</th>
|
||||
<th>Email</th>
|
||||
<th>Motos vendue</th>
|
||||
<th>Date de vente</th>
|
||||
<th>Prix d'achat</th>
|
||||
<th>Prix de vente</th>
|
||||
<th>Point de ventes</th>
|
||||
<th>Bénéfices</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="5" class="text-end fw-bold">Total :</th>
|
||||
<th class="fw-bold"></th>
|
||||
<th></th>
|
||||
<th class="fw-bold"></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- CARD MÉCANICIEN -->
|
||||
<div class="card shadow-sm border-0">
|
||||
@ -905,7 +1219,7 @@ $(document).ready(function () {
|
||||
|
||||
// ✅ BOUTON FILTRAGE COMMERCIAL
|
||||
$('#filterBtnComm').on('click', function () {
|
||||
console.log('🔍 Filtrage Commercial:', {
|
||||
('🔍 Filtrage Commercial:', {
|
||||
startDate: $('#startDateComm').val(),
|
||||
endDate: $('#endDateComm').val(),
|
||||
pvente: $('#pventeComm').val()
|
||||
@ -956,7 +1270,7 @@ $(document).ready(function () {
|
||||
|
||||
// ✅ BOUTON FILTRAGE MÉCANICIEN
|
||||
$('#filterBtnMec').on('click', function () {
|
||||
console.log('🔍 Filtrage Mécanicien:', {
|
||||
('🔍 Filtrage Mécanicien:', {
|
||||
startDate: $('#startDateMec').val(),
|
||||
endDate: $('#endDateMec').val(),
|
||||
pvente: $('#pventeMec').val()
|
||||
@ -1280,7 +1594,7 @@ $(document).ready(function () {
|
||||
let brand = order.name;
|
||||
brandCount[brand] = (brandCount[brand] || 0) + 1;
|
||||
});
|
||||
console.log(brandCount);
|
||||
// console.log(brandCount);
|
||||
|
||||
// Step 2: Convert to array and sort by count (descending)
|
||||
let sortedBrands = Object.entries(brandCount)
|
||||
@ -1290,7 +1604,7 @@ $(document).ready(function () {
|
||||
// Step 3: Prepare data for the chart
|
||||
let labels = sortedBrands.map(item => item[0]); // Brand names
|
||||
let data = sortedBrands.map(item => item[1]); // Order counts
|
||||
console.log(labels);
|
||||
// console.log(labels);
|
||||
|
||||
// Step 4: Create the Pie Chart
|
||||
let ctx2 = document.getElementById('MotosChart').getContext('2d');
|
||||
|
||||
@ -459,7 +459,33 @@
|
||||
}
|
||||
} ?>></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Autres encaissements</td>
|
||||
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="createEncaissement"
|
||||
<?php if ($serialize_permission) {
|
||||
if (in_array('createEncaissement', $serialize_permission)) {
|
||||
echo "checked";
|
||||
}
|
||||
} ?>></td>
|
||||
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="updateEncaissement"
|
||||
<?php if ($serialize_permission) {
|
||||
if (in_array('updateEncaissement', $serialize_permission)) {
|
||||
echo "checked";
|
||||
}
|
||||
} ?>></td>
|
||||
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewEncaissement"
|
||||
<?php if ($serialize_permission) {
|
||||
if (in_array('viewEncaissement', $serialize_permission)) {
|
||||
echo "checked";
|
||||
}
|
||||
} ?>></td>
|
||||
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="deleteEncaissement"
|
||||
<?php if ($serialize_permission) {
|
||||
if (in_array('deleteEncaissement', $serialize_permission)) {
|
||||
echo "checked";
|
||||
}
|
||||
} ?>>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Remise</td>
|
||||
<td><input type="checkbox" name="permission[]" id="permission" class="minimal" value="viewRemise"
|
||||
|
||||
@ -34,6 +34,90 @@
|
||||
<button class="btn btn-primary" data-toggle="modal" data-target="#addModal">Ajouter une réparation</button>
|
||||
<br /> <br />
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ✅ SECTION FILTRES AVEC MÉCANICIENS -->
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><i class="fa fa-filter"></i> Filtres</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="filterStartDate">Date de début</label>
|
||||
<input type="date" id="filterStartDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="filterEndDate">Date de fin</label>
|
||||
<input type="date" id="filterEndDate" class="form-control">
|
||||
</div>
|
||||
<!-- ✅ FILTRE PAR MÉCANICIEN -->
|
||||
<div class="col-md-3">
|
||||
<label for="filterMecanic">Mécanicien</label>
|
||||
<select id="filterMecanic" class="form-control">
|
||||
<option value="">Tous les mécaniciens</option>
|
||||
<?php if (isset($users) && is_array($users)): ?>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= $user['id'] ?>">
|
||||
<?= $user['firstname'] . ' ' . $user['lastname'] ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label> </label><br>
|
||||
<button id="btnFilter" class="btn btn-primary">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="btnReset" class="btn btn-warning">
|
||||
<i class="fa fa-refresh"></i> Réinitialiser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ STATISTIQUES PAR MÉCANICIEN -->
|
||||
<div class="row" id="statsContainer">
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="info-box bg-aqua">
|
||||
<span class="info-box-icon"><i class="fa fa-wrench"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Total Réparations</span>
|
||||
<span class="info-box-number" id="statTotal">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="info-box bg-yellow">
|
||||
<span class="info-box-icon"><i class="fa fa-clock-o"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">En Cours</span>
|
||||
<span class="info-box-number" id="statEnCours">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="info-box bg-green">
|
||||
<span class="info-box-icon"><i class="fa fa-check"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Réparées</span>
|
||||
<span class="info-box-number" id="statRepare">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
<div class="info-box bg-red">
|
||||
<span class="info-box-icon"><i class="fa fa-times"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Non Réparées</span>
|
||||
<span class="info-box-number" id="statNonRepare">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ TABLEAU -->
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Gérer les Réparations</h3>
|
||||
@ -45,7 +129,8 @@
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Motos</th>
|
||||
<th>Username</th>
|
||||
<th>Mécanicien</th>
|
||||
<th>Magasin</th>
|
||||
<th>Statut</th>
|
||||
<th>Observation</th>
|
||||
<th>Date de début</th>
|
||||
@ -55,273 +140,325 @@
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (in_array('createMecanicien', $user_permission)): ?>
|
||||
<!-- create brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="addModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Ajouter une moto pour réparation</h4>
|
||||
<!-- Vos modaux existants (addModal, removeModal, editModal) restent inchangés -->
|
||||
<?php if (in_array('createMecanicien', $user_permission)): ?>
|
||||
<!-- create brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="addModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Ajouter une moto pour réparation</h4>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/create') ?>" method="post" id="createForm">
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="motos">Nom du motos</label>
|
||||
<select name="motos" class="form-control select2" id="moto_select" style="width: 100%;" required>
|
||||
<option value=""></option>
|
||||
<?php foreach ($moto as $k => $v): ?>
|
||||
<?php if ($v['product_sold'] == false): ?>
|
||||
<option value="<?php echo $v['id'] ?>">
|
||||
<?= $v['sku'] . ' | ' . $v['name'] . ' | ' . $v['numero_de_moteur'] . ' | ' . $v['puissance'] ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mecano">Nom du mecanicien</label>
|
||||
<select class="form-control select2" id="moto_select2" name="mecano" style="width: 100%;">
|
||||
<?php foreach ($users as $k => $v): ?>
|
||||
<option value="<?php echo $v['id'] ?>"><?php echo $v['firstname'] . " " . $v['lastname'] ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="statut">Status</label>
|
||||
<select class="form-control" id="statut" name="statut" required>
|
||||
<option value="1">En cours de réparation</option>
|
||||
<option value="2">Réparer</option>
|
||||
<option value="3">Non réparer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="observation">Observation</label>
|
||||
<textarea class="form-control" name="observation" id="observation" cols="30" rows="3"
|
||||
placeholder="votre observation"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_debut">Date de debut</label>
|
||||
<input type="date" name="date_debut" id="date_debut" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_fin">Date de fin</label>
|
||||
<input type="date" name="date_fin" id="date_fin" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/create') ?>" method="post" id="createForm">
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="motos">Nom du motos</label>
|
||||
<!-- <select class="form-control" id="motos" name="motos" style="width: 100%;">
|
||||
<//?php foreach ($moto as $k => $v): ?>
|
||||
<option value="<//?php echo $v['id'] ?>"><//?php echo $v['name'] ?></option>
|
||||
<//?php endforeach ?>
|
||||
</select> -->
|
||||
<select name="motos" class="form-control select2" id="moto_select" style="width: 100%;"
|
||||
style="width:100%;" required>
|
||||
<option value=""></option>
|
||||
<?php foreach ($moto as $k => $v): ?>
|
||||
<?php if ($v['product_sold'] == false): ?>
|
||||
<option value="<?php echo $v['id'] ?>">
|
||||
<?= $v['sku'] . ' | ' . $v['name'] . ' | ' . $v['numero_de_moteur'] . ' | ' . $v['puissance'] ?>
|
||||
</option>
|
||||
<!-- <//?php else: ?>
|
||||
<option value="<?php echo $v['id'] ?>" disabled><?php echo $v['name'] ?> <span style="background-color: #dc3545; color: #ffffff; padding: 2px 5px; border-radius: 5px;"> (Rupture de stock)</span></option>
|
||||
<?php endif; ?> -->
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mecano">Nom du mecanicien</label>
|
||||
<select class="form-control select2" id="moto_select2" name="mecano" style="width: 100%;">
|
||||
<?php foreach ($users as $k => $v): ?>
|
||||
<option value="<?php echo $v['id'] ?>"><?php echo $v['firstname'] . " " . $v['lastname'] ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="statut">Status</label>
|
||||
<select class="form-control" id="statut" name="statut" required>
|
||||
<option value="1">En cours de réparation</option>
|
||||
<option value="2">Réparer</option>
|
||||
<option value="3">Non réparer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="observation">Observation</label>
|
||||
<textarea class="form-control" name="observation" id="observation" cols="30" rows="3"
|
||||
placeholder="votre observation"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_debut">Date de debut</label>
|
||||
<input type="date" name="date_debut" id="date_debut" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_fin">Date de fin</label>
|
||||
<input type="date" name="date_fin" id="date_fin" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('deleteMecanicien', $user_permission)): ?>
|
||||
<!-- remove brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="removeModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Supprimer la réparation</h4>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/delete') ?>" method="post" id="removeForm">
|
||||
<div class="modal-body">
|
||||
<p>Voulez-vous vraiment supprimer ?</p>
|
||||
</form>
|
||||
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('deleteMecanicien', $user_permission)): ?>
|
||||
<!-- remove brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="removeModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Supprimer la réparation</h4>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/delete') ?>" method="post" id="removeForm">
|
||||
<div class="modal-body">
|
||||
<p>Voulez-vous vraiment supprimer ?</p>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Oui</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('updateMecanicien', $user_permission)): ?>
|
||||
<!-- edit brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="editModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Mise à jour du réparation</h4>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/update') ?>" method="post" id="updateForm">
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="motos">Nom du motos</label>
|
||||
<select class="form-control" id="motos_edit" name="motos" style="width: 100%;" required>
|
||||
<?php foreach ($moto as $k => $v): ?>
|
||||
<?php if ($v['product_sold'] == false): ?>
|
||||
<option value="<?php echo $v['id'] ?>">
|
||||
<?= $v['sku'] . ' | ' . $v['name'] . ' | ' . $v['numero_de_moteur'] . ' | ' . $v['puissance'] ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Oui</button>
|
||||
<div class="form-group">
|
||||
<label for="mecano">Nom du mecanicien</label>
|
||||
<select class="mecanic" id="mecano" name="mecano" style="width: 100%;">
|
||||
<?php foreach ($users as $k => $v): ?>
|
||||
<option value="<?php echo $v['id'] ?>"><?php echo $v['firstname'] . " " . $v['lastname'] ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="statut">Status</label>
|
||||
<select class="form-control" id="statut_edit" name="statut" required>
|
||||
<option value="1">En cours de réparation</option>
|
||||
<option value="2">Réparer</option>
|
||||
<option value="3">Non réparer</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="observation">Observation</label>
|
||||
<textarea class="form-control" name="observation" id="observation_edit" cols="30" rows="3"
|
||||
placeholder="votre observation"></textarea>
|
||||
</div>
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
<div class="form-group">
|
||||
<label for="date_debut">Date de debut</label>
|
||||
<input type="date" name="date_debut" id="date_debut_edit" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<?php if (in_array('updateMecanicien', $user_permission)): ?>
|
||||
<!-- edit brand modal -->
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="editModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Mise à jour du réparation</h4>
|
||||
<div class="form-group">
|
||||
<label for="date_fin">Date de fin</label>
|
||||
<input type="date" name="date_fin" id="date_fin_edit" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form role="form" action="<?php echo base_url('mecanicien/update') ?>" method="post" id="updateForm">
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="motos">Nom du motos</label>
|
||||
<select class="form-control" id="motos_edit" name="motos" style="width: 100%;" required>
|
||||
<?php foreach ($moto as $k => $v): ?>
|
||||
<?php if ($v['product_sold'] == false): ?>
|
||||
<option value="<?php echo $v['id'] ?>">
|
||||
<?= $v['sku'] . ' | ' . $v['name'] . ' | ' . $v['numero_de_moteur'] . ' | ' . $v['puissance'] ?>
|
||||
</option>
|
||||
<!-- <//?php else: ?>
|
||||
<option value="<?php echo $v['id'] ?>" disabled><?php echo $v['name'] ?> <span style="background-color: #dc3545; color: #ffffff; padding: 2px 5px; border-radius: 5px;"> (Rupture de stock)</span></option>
|
||||
<?php endif; ?> -->
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mecano">Nom du mecanicien</label>
|
||||
<select class="mecanic" id="mecano" name="mecano" style="width: 100%;">
|
||||
<?php foreach ($users as $k => $v): ?>
|
||||
<option value="<?php echo $v['id'] ?>"><?php echo $v['firstname'] . " " . $v['lastname'] ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="statut">Status</label>
|
||||
<select class="form-control" id="statut_edit" name="statut" required>
|
||||
<option value="1">En cours de réparation</option>
|
||||
<option value="2">Réparer</option>
|
||||
<option value="3">Non réparer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="observation">Observation</label>
|
||||
<textarea class="form-control" name="observation" id="observation_edit" cols="30" rows="3"
|
||||
placeholder="votre observation"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_debut">Date de debut</label>
|
||||
<input type="date" name="date_debut" id="date_debut_edit" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date_fin">Date de fin</label>
|
||||
<input type="date" name="date_fin" id="date_fin_edit" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#addModal').on('shown.bs.modal', function () {
|
||||
// Only initialize if not already done
|
||||
if (!$('#moto_select').hasClass("select2-hidden-accessible")) {
|
||||
$('#moto_select').select2({
|
||||
dropdownParent: $('#addModal'),
|
||||
width: '100%' // Ensure it takes full width
|
||||
width: '100%'
|
||||
});
|
||||
}
|
||||
|
||||
if (!$('#moto_select2').hasClass("select2-hidden-accessible")) {
|
||||
$('#moto_select2').select2({
|
||||
dropdownParent: $('#addModal'),
|
||||
width: '100%' // Ensure it takes full width
|
||||
width: '100%'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#motos_edit").select2();
|
||||
$('.mecanic').select2();
|
||||
$("#mecanicNav").addClass('active');
|
||||
|
||||
// initialize the datatable
|
||||
// datatable-fr.js
|
||||
$.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",
|
||||
// ✅ INITIALISER LES DATES À AUJOURD'HUI
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
|
||||
// Configuration DataTable 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",
|
||||
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"
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ FONCTION POUR CALCULER LES STATISTIQUES
|
||||
function updateStatistics(data) {
|
||||
let total = data.length;
|
||||
let enCours = 0;
|
||||
let repare = 0;
|
||||
let nonRepare = 0;
|
||||
|
||||
data.forEach(function(row) {
|
||||
// Le statut est dans la colonne index 4
|
||||
let statusHTML = row[4];
|
||||
if (statusHTML.includes('label-warning')) {
|
||||
enCours++;
|
||||
} else if (statusHTML.includes('label-success')) {
|
||||
repare++;
|
||||
} else if (statusHTML.includes('label-danger')) {
|
||||
nonRepare++;
|
||||
}
|
||||
});
|
||||
|
||||
$('#statTotal').text(total);
|
||||
$('#statEnCours').text(enCours);
|
||||
$('#statRepare').text(repare);
|
||||
$('#statNonRepare').text(nonRepare);
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ INITIALISATION DATATABLE AVEC FILTRES
|
||||
manageTable = $('#manageTable').DataTable({
|
||||
'ajax': `<?= base_url('mecanicien/fetchMecanicien') ?>`,
|
||||
'order': []
|
||||
});
|
||||
ajax: {
|
||||
url: '<?= base_url('mecanicien/fetchMecanicien') ?>',
|
||||
type: 'GET',
|
||||
data: function(d) {
|
||||
d.startDate = $('#filterStartDate').val();
|
||||
d.endDate = $('#filterEndDate').val();
|
||||
d.mecanic_id = $('#filterMecanic').val();
|
||||
|
||||
console.log('📤 Filtres envoyés:', {
|
||||
startDate: d.startDate,
|
||||
endDate: d.endDate,
|
||||
mecanic_id: d.mecanic_id
|
||||
});
|
||||
},
|
||||
dataSrc: function(json) {
|
||||
console.log('📥 Données reçues:', json.data.length, 'réparations');
|
||||
updateStatistics(json.data);
|
||||
return json.data;
|
||||
},
|
||||
error: function(xhr, error, code) {
|
||||
console.error('❌ Erreur DataTables:', error);
|
||||
}
|
||||
},
|
||||
order: []
|
||||
});
|
||||
|
||||
// ✅ BOUTON FILTRER
|
||||
$('#btnFilter').on('click', function() {
|
||||
console.log('🔍 Bouton Filtrer cliqué');
|
||||
console.log('Dates:', $('#filterStartDate').val(), 'à', $('#filterEndDate').val());
|
||||
console.log('Mécanicien ID:', $('#filterMecanic').val());
|
||||
|
||||
manageTable.ajax.reload();
|
||||
});
|
||||
|
||||
// submit the create from
|
||||
// ✅ BOUTON RÉINITIALISER
|
||||
$('#btnReset').on('click', function() {
|
||||
console.log('🔄 Réinitialisation des filtres');
|
||||
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
$('#filterMecanic').val('');
|
||||
|
||||
manageTable.ajax.reload();
|
||||
});
|
||||
|
||||
// submit the create form
|
||||
$("#createForm").unbind('submit').on('submit', function () {
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(), // /converting the form data into array and sending it to server
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
|
||||
manageTable.ajax.reload(null, false);
|
||||
|
||||
if (response.success === true) {
|
||||
@ -330,27 +467,18 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
|
||||
// hide the modal
|
||||
$("#addModal").modal('hide');
|
||||
|
||||
// reset the form
|
||||
$("#createForm")[0].reset();
|
||||
$("#createForm .form-group").removeClass('has-error').removeClass('has-success');
|
||||
|
||||
} else {
|
||||
|
||||
if (response.messages instanceof Object) {
|
||||
$.each(response.messages, function (index, value) {
|
||||
var id = $("#" + index);
|
||||
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
|
||||
id.after(value);
|
||||
|
||||
});
|
||||
} else {
|
||||
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
|
||||
@ -365,7 +493,7 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
return false;
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
// edit function
|
||||
function editFunc(id) {
|
||||
@ -377,28 +505,25 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
console.log(response);
|
||||
let date1 = new Date(response.reparation_debut);
|
||||
let date2 = new Date(response.reparation_fin);
|
||||
let formattedDate1 = date1.toLocaleDateString("fr-CA").split("T")[0]; // Convert to YYYY-MM-DD
|
||||
let formattedDate2 = date2.toLocaleDateString("fr-CA").split("T")[0]; // Convert to YYYY-MM-DD
|
||||
let formattedDate1 = date1.toLocaleDateString("fr-CA").split("T")[0];
|
||||
let formattedDate2 = date2.toLocaleDateString("fr-CA").split("T")[0];
|
||||
$("#motos_edit").val(response.produit_id).change();
|
||||
$("#mecano").val(response.user_id).trigger('change');
|
||||
$("#statut_edit").val(response.reparation_statut);
|
||||
$("#observation_edit").val(response.reparation_observation);
|
||||
$("#date_debut_edit").val(formattedDate1);
|
||||
$("#date_fin_edit").val(formattedDate2);
|
||||
|
||||
// submit the edit from
|
||||
$("#updateForm").unbind('submit').bind('submit', function () {
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action').replace(/\/?$/, '/') + id,
|
||||
type: form.attr('method'),
|
||||
data: form.serialize(), // /converting the form data into array and sending it to server
|
||||
data: form.serialize(),
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
|
||||
manageTable.ajax.reload(null, false);
|
||||
|
||||
if (response.success === true) {
|
||||
@ -407,25 +532,17 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
|
||||
// hide the modal
|
||||
$("#editModal").modal('hide');
|
||||
// reset the form
|
||||
$("#updateForm .form-group").removeClass('has-error').removeClass('has-success');
|
||||
|
||||
} else {
|
||||
|
||||
if (response.messages instanceof Object) {
|
||||
$.each(response.messages, function (index, value) {
|
||||
var id = $("#" + index);
|
||||
|
||||
id.closest('.form-group')
|
||||
.removeClass('has-error')
|
||||
.removeClass('has-success')
|
||||
.addClass(value.length > 0 ? 'has-error' : 'has-success');
|
||||
|
||||
id.after(value);
|
||||
|
||||
});
|
||||
} else {
|
||||
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
|
||||
@ -439,7 +556,6 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -447,21 +563,15 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
function removeFunc(id) {
|
||||
if (id) {
|
||||
$("#removeForm").on('submit', function () {
|
||||
|
||||
var form = $(this);
|
||||
|
||||
// remove the text-danger
|
||||
$(".text-danger").remove();
|
||||
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
type: form.attr('method'),
|
||||
data: {
|
||||
reparation_id: id
|
||||
},
|
||||
data: { reparation_id: id },
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
|
||||
manageTable.ajax.reload(null, false);
|
||||
|
||||
if (response.success === true) {
|
||||
@ -470,11 +580,8 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
|
||||
'</div>');
|
||||
|
||||
// hide the modal
|
||||
$("#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 +
|
||||
|
||||
@ -145,58 +145,66 @@
|
||||
<br /> <br />
|
||||
|
||||
<table class="table table-bordered" id="product_info_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40%">Produit</th>
|
||||
<th style="width:15%">Puissance (CC)</th>
|
||||
<th style="width:15%">Prix unitaire</th>
|
||||
<th style="width:20%">Montant</th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr id="row_1">
|
||||
<td>
|
||||
<select class="form-control select_group product" data-row-id="row_1"
|
||||
id="product_1" name="product[]" style="width:100%;"
|
||||
onchange="getProductData(1)" required>
|
||||
<option value="<?= $products['id'] ?>">
|
||||
<?= $products['sku'] . ' | ' . $products['name'] . ' | ' . $products['numero_de_moteur'] ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:35%">Produit</th>
|
||||
<!-- ✅ COLONNE QUANTITÉ (cachée par défaut) -->
|
||||
<th class="qty-column" style="width:10%; display:none;">Quantité</th>
|
||||
<th style="width:12%">Puissance (CC)</th>
|
||||
<th style="width:15%">Prix unitaire</th>
|
||||
<th style="width:18%">Montant</th>
|
||||
<th style="width:10%"><button type="button" class="btn btn-default" id="add_row"><i class="fa fa-plus"></i></button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr id="row_1">
|
||||
<td>
|
||||
<select class="form-control select_group product" data-row-id="row_1"
|
||||
id="product_1" name="product[]" style="width:100%;"
|
||||
onchange="getProductData(1)" required>
|
||||
<option value="<?= $products['id'] ?>">
|
||||
<?= $products['sku'] . ' | ' . $products['name'] . ' | ' . $products['numero_de_moteur'] ?>
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<input type="number" name="puissance[]" id="puissance_1"
|
||||
class="form-control" placeholder="CC"
|
||||
value="<?= esc($products['puissance'] ?? '1') ?>"
|
||||
min="1" autocomplete="off">
|
||||
</td>
|
||||
<!-- ✅ CHAMP QUANTITÉ (caché par défaut) -->
|
||||
<td class="qty-column" style="display:none;">
|
||||
<input type="number" name="qty[]" id="qty_1"
|
||||
class="form-control numeric-input"
|
||||
value="1" min="1" disabled autocomplete="off">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" name="rate[]" id="rate_1" class="form-control" disabled
|
||||
autocomplete="off" value="<?= esc($pu) ?>" min="0">
|
||||
<input type="hidden" name="rate_value[]" id="rate_value_1"
|
||||
value="<?= esc($pu) ?>" class="form-control" autocomplete="off">
|
||||
<input type="hidden" id="min_price_1" name="min_price[]" value="">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" name="amount[]" id="amount_1" class="form-control"
|
||||
disabled autocomplete="off" min="0">
|
||||
<input type="hidden" name="amount_value[]" id="amount_value_1"
|
||||
class="form-control" autocomplete="off">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" onclick="removeRow('1')">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td>
|
||||
<input type="number" name="puissance[]" id="puissance_1"
|
||||
class="form-control" placeholder="CC"
|
||||
value="<?= esc($products['puissance'] ?? '1') ?>"
|
||||
min="1" autocomplete="off">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" name="rate[]" id="rate_1" class="form-control" disabled
|
||||
autocomplete="off" value="<?= esc($pu) ?>" min="0">
|
||||
<input type="hidden" name="rate_value[]" id="rate_value_1"
|
||||
value="<?= esc($pu) ?>" class="form-control" autocomplete="off">
|
||||
<input type="hidden" id="min_price_1" name="min_price[]" value="">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" name="amount[]" id="amount_1" class="form-control"
|
||||
disabled autocomplete="off" min="0">
|
||||
<input type="hidden" name="amount_value[]" id="amount_value_1"
|
||||
class="form-control" autocomplete="off">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="btn btn-default" onclick="removeRow('1')">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ✅ Champ qty caché (toujours = 1) -->
|
||||
<input type="hidden" name="qty[]" value="1" id="qty_1" class="form-control">
|
||||
@ -254,10 +262,32 @@
|
||||
<script type="text/javascript">
|
||||
var base_url = "<?php echo base_url(); ?>";
|
||||
|
||||
// ✅ Fonction pour valider les nombres positifs
|
||||
// ✅ Fonction pour afficher/masquer la colonne quantité
|
||||
function toggleQuantityColumn() {
|
||||
var customerType = $('#customer_type').val();
|
||||
|
||||
if (customerType === 'revendeur') {
|
||||
$('.qty-column').show();
|
||||
$('input[name="qty[]"]').prop('disabled', false);
|
||||
} else {
|
||||
$('.qty-column').hide();
|
||||
$('input[name="qty[]"]').val(1).prop('disabled', true);
|
||||
recalculateAllRows();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Fonction pour recalculer tous les montants
|
||||
function recalculateAllRows() {
|
||||
var tableProductLength = $("#product_info_table tbody tr").length;
|
||||
for (var i = 1; i <= tableProductLength; i++) {
|
||||
if ($("#row_" + i).length > 0) {
|
||||
getTotal(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validatePositiveNumber(input) {
|
||||
let value = parseFloat(input.value);
|
||||
|
||||
if (isNaN(value) || value < 0) {
|
||||
input.value = '';
|
||||
return false;
|
||||
@ -265,20 +295,17 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
// ✅ Fonction pour afficher l'erreur sur le champ prix demandé
|
||||
function showDiscountError(message) {
|
||||
$('#discount_error_text').text(message);
|
||||
$('#discount_error').slideDown(200);
|
||||
$('#discount').addClass('border-danger');
|
||||
}
|
||||
|
||||
// ✅ Fonction pour masquer l'erreur sur le champ prix demandé
|
||||
function hideDiscountError() {
|
||||
$('#discount_error').slideUp(200);
|
||||
$('#discount').removeClass('border-danger');
|
||||
}
|
||||
|
||||
// ✅ Fonction pour valider que le prix demandé ne dépasse pas le prix affiché
|
||||
function validatePrixDemande(input) {
|
||||
let prixDemande = parseFloat(input.value);
|
||||
let prixAffiche = parseFloat($('#gross_amount').val()) || 0;
|
||||
@ -302,7 +329,6 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
// ✅ Fonction pour empêcher la saisie de caractères négatifs
|
||||
function preventNegativeInput(e) {
|
||||
if (e.key === '-' || e.key === 'e' || e.key === 'E' || e.key === '+') {
|
||||
e.preventDefault();
|
||||
@ -311,18 +337,34 @@
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// ✅ INITIALISATION : Calculer le total au chargement
|
||||
getTotal(1);
|
||||
|
||||
// ✅ INITIALISATION : S'assurer que rate_value_1 est bien rempli
|
||||
$("#rate_value_1").val($("#rate_1").val());
|
||||
|
||||
$(".select_group").select2();
|
||||
|
||||
$("#mainOrdersNav").addClass('active');
|
||||
$("#addOrderNav").addClass('active');
|
||||
|
||||
// ✅ Appliquer la validation sur tous les champs numériques
|
||||
// ✅ Initialiser l'affichage selon le type de client
|
||||
toggleQuantityColumn();
|
||||
|
||||
// ✅ Écouter les changements du type de client
|
||||
$('#customer_type').on('change', function() {
|
||||
toggleQuantityColumn();
|
||||
});
|
||||
|
||||
// ✅ Gérer les changements de quantité
|
||||
$(document).on('input', 'input[name="qty[]"]', function() {
|
||||
var row_id = $(this).attr('id').replace('qty_', '');
|
||||
var qty = parseInt($(this).val()) || 1;
|
||||
|
||||
if (qty < 1) {
|
||||
$(this).val(1);
|
||||
qty = 1;
|
||||
}
|
||||
|
||||
getTotal(row_id);
|
||||
});
|
||||
|
||||
$(document).on('keydown', '.numeric-input, input[type="number"]', function(e) {
|
||||
preventNegativeInput(e);
|
||||
});
|
||||
@ -331,7 +373,6 @@
|
||||
validatePositiveNumber(this);
|
||||
});
|
||||
|
||||
// ✅ Validation sur le champ discount
|
||||
$("#discount").on('input', function() {
|
||||
validatePrixDemande(this);
|
||||
});
|
||||
@ -341,7 +382,6 @@
|
||||
checkMinimalPrice();
|
||||
});
|
||||
|
||||
// Bloquer la soumission du formulaire
|
||||
$('form').on('submit', function(e) {
|
||||
let hasNegative = false;
|
||||
$('.numeric-input, input[type="number"]').each(function() {
|
||||
@ -363,9 +403,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Add new row in the table
|
||||
$("#add_row").unbind('click').bind('click', function () {
|
||||
var table = $("#product_info_table");
|
||||
var count_table_tbody_tr = $("#product_info_table tbody tr").length;
|
||||
var row_id = count_table_tbody_tr + 1;
|
||||
|
||||
@ -374,18 +412,21 @@
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
var customerType = $('#customer_type').val();
|
||||
var qtyColumnVisible = (customerType === 'revendeur') ? '' : 'style="display:none;"';
|
||||
var qtyDisabled = (customerType === 'revendeur') ? '' : 'disabled';
|
||||
|
||||
var html = '<tr id="row_' + row_id + '">' +
|
||||
'<td>' +
|
||||
'<select class="form-control select_group product" data-row-id="' + row_id + '" id="product_' + row_id + '" name="product[]" style="width:100%;" onchange="getProductData(' + row_id + ')">' +
|
||||
'<option value=""></option>';
|
||||
|
||||
$.each(response, function (index, value) {
|
||||
// ✅ NE PLUS AFFICHER LA PUISSANCE DANS LE SELECT
|
||||
html += '<option value="' + value.id + '">' + value.sku + ' | ' + value.name + ' | ' + value.numero_de_moteur + '</option>';
|
||||
});
|
||||
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
// ✅ COLONNE PUISSANCE
|
||||
html += '</select></td>' +
|
||||
'<td class="qty-column" ' + qtyColumnVisible + '><input type="number" name="qty[]" id="qty_' + row_id + '" class="form-control numeric-input" min="1" value="1" ' + qtyDisabled + ' autocomplete="off"></td>' +
|
||||
'<td><input type="number" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="CC" min="1" value="1" autocomplete="off"></td>' +
|
||||
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' +
|
||||
'<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control numeric-input" disabled min="0"><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
|
||||
@ -404,16 +445,15 @@
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
}); // /document
|
||||
});
|
||||
|
||||
function getTotal(row = null) {
|
||||
if (row) {
|
||||
var rate = Number($("#rate_value_" + row).val());
|
||||
var qty = Number($("#qty_" + row).val());
|
||||
var rate = Number($("#rate_value_" + row).val()) || 0;
|
||||
var qty = Number($("#qty_" + row).val()) || 1;
|
||||
|
||||
if (rate < 0) rate = 0;
|
||||
if (qty < 0) qty = 0;
|
||||
if (qty < 1) qty = 1;
|
||||
|
||||
var total = rate * qty;
|
||||
total = total.toFixed(2);
|
||||
@ -421,13 +461,11 @@
|
||||
$("#amount_value_" + row).val(total);
|
||||
|
||||
subAmount();
|
||||
|
||||
} else {
|
||||
alert('no row !! please refresh the page');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ GET PRODUCT DATA - Récupère prix + puissance
|
||||
function getProductData(row_id) {
|
||||
var product_id = $("#product_" + row_id).val();
|
||||
if (product_id == "") {
|
||||
@ -435,20 +473,16 @@
|
||||
$("#rate_value_" + row_id).val("");
|
||||
$("#min_price_" + row_id).val("");
|
||||
$("#puissance_" + row_id).val("1");
|
||||
$("#qty_" + row_id).val("1");
|
||||
$("#amount_" + row_id).val("");
|
||||
$("#amount_value_" + row_id).val("");
|
||||
|
||||
} else {
|
||||
$.ajax({
|
||||
url: base_url + 'orders/getProductValueById',
|
||||
type: 'post',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
data: { product_id: product_id },
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
console.log('✅ Response:', response); // Debug
|
||||
|
||||
var prixVente = parseFloat(response.prix_vente) || 0;
|
||||
var prixMinimal = parseFloat(response.prix_minimal) || 0;
|
||||
|
||||
@ -459,14 +493,11 @@
|
||||
$("#rate_value_" + row_id).val(prixVente);
|
||||
$("#min_price_" + row_id).val(prixMinimal);
|
||||
|
||||
// ✅ REMPLIR LA PUISSANCE
|
||||
var puissanceValue = response.puissance || '1';
|
||||
console.log('✅ Puissance extraite:', puissanceValue); // Debug
|
||||
$("#puissance_" + row_id).val(puissanceValue);
|
||||
|
||||
$("#qty_" + row_id).val(1);
|
||||
|
||||
var total = prixVente * 1;
|
||||
var qty = parseInt($("#qty_" + row_id).val()) || 1;
|
||||
var total = prixVente * qty;
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row_id).val(total);
|
||||
$("#amount_value_" + row_id).val(total);
|
||||
@ -477,7 +508,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the total amount of the order
|
||||
function subAmount() {
|
||||
var service_charge = <?php echo ($company_data['service_charge_value'] > 0) ? $company_data['service_charge_value'] : 0; ?>;
|
||||
var vat_charge = <?php echo ($company_data['vat_charge_value'] > 0) ? $company_data['vat_charge_value'] : 0; ?>;
|
||||
@ -496,23 +526,19 @@
|
||||
|
||||
totalSubAmount = totalSubAmount.toFixed(2);
|
||||
|
||||
// sub total
|
||||
$("#gross_amount").val(totalSubAmount);
|
||||
$("#gross_amount_value").val(totalSubAmount);
|
||||
|
||||
// vat
|
||||
var vat = (Number($("#gross_amount").val()) / 100) * vat_charge;
|
||||
vat = vat.toFixed(2);
|
||||
$("#vat_charge").val(vat);
|
||||
$("#vat_charge_value").val(vat);
|
||||
|
||||
// service
|
||||
var service = (Number($("#gross_amount").val()) / 100) * service_charge;
|
||||
service = service.toFixed(2);
|
||||
$("#service_charge").val(service);
|
||||
$("#service_charge_value").val(service);
|
||||
|
||||
// total amount
|
||||
var totalAmount = (Number(totalSubAmount));
|
||||
totalAmount = totalAmount.toFixed(2);
|
||||
|
||||
@ -534,7 +560,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Vérification du rabais vs prix minimal
|
||||
function checkMinimalPrice() {
|
||||
var discount = Number($("#discount").val()) || 0;
|
||||
|
||||
@ -577,7 +602,7 @@
|
||||
subAmount();
|
||||
}
|
||||
|
||||
// ✅ INITIALISATION AU CHARGEMENT
|
||||
// ✅ INITIALISATION
|
||||
const quantity = document.getElementById('qty_1')
|
||||
const rates = document.getElementById('rate_1')
|
||||
const amount = document.getElementById('amount_1')
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<style>
|
||||
.border-danger {
|
||||
border: 2px solid #d9534f !important;
|
||||
box-shadow: 0 0 5px rgba(217, 83, 79, 0.5) !important;
|
||||
}
|
||||
</style>
|
||||
<div class="content-wrapper">
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
@ -65,7 +72,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-xs-12 pull pull-left">
|
||||
<!-- ✅ NOUVEAU : Type de document -->
|
||||
<!-- ✅ Type de document -->
|
||||
<div class="form-group">
|
||||
<label for="document_type" class="col-sm-5 control-label" style="text-align:left;">
|
||||
Type de document <span class="text-danger">*</span>
|
||||
@ -85,7 +92,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Types d'impression (ancien système - peut être supprimé si vous voulez) -->
|
||||
<!-- Types d'impression -->
|
||||
<div class="form-group">
|
||||
<label for="types" class="col-sm-5 control-label" style="text-align:left;">Types d'impression</label>
|
||||
<div class="col-sm-7">
|
||||
@ -125,7 +132,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU CHAMP : Type de client -->
|
||||
<!-- ✅ Type de client -->
|
||||
<div class="form-group">
|
||||
<label for="customer_type" class="col-sm-5 control-label" style="text-align:left;">Type de client <span class="text-danger">*</span></label>
|
||||
<div class="col-sm-7">
|
||||
@ -137,7 +144,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU CHAMP : Source -->
|
||||
<!-- ✅ Source -->
|
||||
<div class="form-group">
|
||||
<label for="source" class="col-sm-5 control-label" style="text-align:left;">Source <span class="text-danger">*</span></label>
|
||||
<div class="col-sm-7">
|
||||
@ -158,10 +165,12 @@
|
||||
<table class="table table-bordered" id="product_info_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50%">Produit</th>
|
||||
<th style="width:15%">Puissance (CC)</th>
|
||||
<th style="width:10%">Prix unitaire</th>
|
||||
<th style="width:20%">Montant</th>
|
||||
<th style="width:35%">Produit</th>
|
||||
<!-- ✅ COLONNE QUANTITÉ (cachée par défaut) -->
|
||||
<th class="qty-column" style="width:10%; display:none;">Quantité</th>
|
||||
<th style="width:12%">Puissance (CC)</th>
|
||||
<th style="width:15%">Prix unitaire</th>
|
||||
<th style="width:18%">Montant</th>
|
||||
<th style="width:10%"><button type="button" id="add_row" class="btn btn-default"><i class="fa fa-plus"></i></button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -190,6 +199,14 @@
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<!-- ✅ CHAMP QUANTITÉ (caché par défaut) -->
|
||||
<td class="qty-column" style="display:none;">
|
||||
<input type="number" name="qty[]" id="qty_<?php echo $x; ?>"
|
||||
class="form-control numeric-input"
|
||||
value="<?php echo isset($val['qty']) ? esc($val['qty']) : '1'; ?>"
|
||||
min="1" disabled autocomplete="off">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" name="puissance[]" id="puissance_<?php echo $x; ?>"
|
||||
class="form-control" placeholder="Puissance"
|
||||
@ -203,6 +220,7 @@
|
||||
value="<?php echo esc($val['rate']) ?>" autocomplete="off">
|
||||
<input type="hidden" name="rate_value[]" id="rate_value_<?php echo $x; ?>"
|
||||
class="form-control" value="<?php echo esc($val['rate']) ?>" autocomplete="off">
|
||||
<input type="hidden" id="min_price_<?php echo $x; ?>" name="min_price[]" value="">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -241,14 +259,21 @@
|
||||
<div class="form-group">
|
||||
<label for="discount" class="col-sm-5 control-label">Prix demandé</label>
|
||||
<div class="col-sm-7">
|
||||
<?php
|
||||
$users = session()->get('user');
|
||||
if($users && $users['group_name'] == 'COMMERCIALE'):
|
||||
?>
|
||||
<input type="text" class="form-control" id="discount" name="discount" placeholder="Discount" onkeyup="subAmount()" value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off">
|
||||
<?php else: ?>
|
||||
<input type="text" class="form-control" id="discount" name="discount" readonly value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off">
|
||||
<?php endif; ?>
|
||||
<div id="discount_error" class="alert alert-danger" style="display:none; padding: 8px; margin-bottom: 5px; font-size: 13px;">
|
||||
<i class="fa fa-exclamation-triangle"></i> <span id="discount_error_text"></span>
|
||||
</div>
|
||||
<?php
|
||||
$users = session()->get('user');
|
||||
if($users && $users['group_name'] == 'COMMERCIALE'):
|
||||
?>
|
||||
<input type="number" class="form-control numeric-input" id="discount" name="discount"
|
||||
placeholder="Prix demandé" onkeyup="subAmount()"
|
||||
oninput="validatePrixDemande(this)"
|
||||
value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off">
|
||||
<?php else: ?>
|
||||
<input type="text" class="form-control" id="discount" name="discount" readonly
|
||||
value="<?php echo $order_data['order']['discount'] ?>" autocomplete="off">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -357,10 +382,83 @@ let Imprimente = document.getElementById('Imprimente');
|
||||
let typesCommande = document.getElementById('typesCommande');
|
||||
let documentType = document.getElementById('document_type');
|
||||
|
||||
// ✅ Fonction pour afficher/masquer la colonne quantité
|
||||
function toggleQuantityColumn() {
|
||||
var customerType = $('#customer_type').val();
|
||||
|
||||
if (customerType === 'revendeur') {
|
||||
$('.qty-column').show();
|
||||
$('input[name="qty[]"]').prop('disabled', false);
|
||||
} else {
|
||||
$('.qty-column').hide();
|
||||
$('input[name="qty[]"]').val(1).prop('disabled', true);
|
||||
recalculateAllRows();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Fonction pour recalculer tous les montants
|
||||
function recalculateAllRows() {
|
||||
var tableProductLength = $("#product_info_table tbody tr").length;
|
||||
for (var i = 1; i <= tableProductLength; i++) {
|
||||
if ($("#row_" + i).length > 0) {
|
||||
getTotal(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validatePositiveNumber(input) {
|
||||
let value = parseFloat(input.value);
|
||||
if (isNaN(value) || value < 0) {
|
||||
input.value = '';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function showDiscountError(message) {
|
||||
$('#discount_error_text').text(message);
|
||||
$('#discount_error').slideDown(200);
|
||||
$('#discount').addClass('border-danger');
|
||||
}
|
||||
|
||||
function hideDiscountError() {
|
||||
$('#discount_error').slideUp(200);
|
||||
$('#discount').removeClass('border-danger');
|
||||
}
|
||||
|
||||
function validatePrixDemande(input) {
|
||||
let prixDemande = parseFloat(input.value);
|
||||
let prixAffiche = parseFloat($('#gross_amount').val()) || 0;
|
||||
|
||||
if (isNaN(prixDemande) || prixDemande < 0) {
|
||||
input.value = '';
|
||||
showDiscountError('Le prix demandé ne peut pas être négatif.');
|
||||
setTimeout(hideDiscountError, 3000);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prixDemande > prixAffiche) {
|
||||
showDiscountError('Le prix demandé (' + prixDemande.toFixed(2) + ') ne peut pas être supérieur au prix affiché (' + prixAffiche.toFixed(2) + ').');
|
||||
input.value = prixAffiche;
|
||||
subAmount();
|
||||
setTimeout(hideDiscountError, 4000);
|
||||
return false;
|
||||
}
|
||||
|
||||
hideDiscountError();
|
||||
return true;
|
||||
}
|
||||
|
||||
function preventNegativeInput(e) {
|
||||
if (e.key === '-' || e.key === 'e' || e.key === 'E' || e.key === '+') {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePrintLink() {
|
||||
let type = documentType ? documentType.value : 'facture';
|
||||
|
||||
// Synchroniser l'ancien select (si vous le gardez)
|
||||
if (typesCommande) {
|
||||
if (type === 'facture') {
|
||||
typesCommande.value = 1;
|
||||
@ -371,7 +469,6 @@ function updatePrintLink() {
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour le lien d'impression
|
||||
if (Imprimente) {
|
||||
Imprimente.removeAttribute("href");
|
||||
|
||||
@ -391,17 +488,13 @@ function updatePrintLink() {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Écouter les changements sur le nouveau select
|
||||
if (documentType) {
|
||||
documentType.addEventListener('change', updatePrintLink);
|
||||
// Initialiser au chargement
|
||||
updatePrintLink();
|
||||
}
|
||||
|
||||
// ✅ Garder la compatibilité avec l'ancien système (optionnel)
|
||||
if (typesCommande) {
|
||||
typesCommande.addEventListener('change', function () {
|
||||
// Synchroniser avec le nouveau select
|
||||
if (documentType) {
|
||||
if (typesCommande.value == 1) {
|
||||
documentType.value = 'facture';
|
||||
@ -414,25 +507,72 @@ if (typesCommande) {
|
||||
updatePrintLink();
|
||||
});
|
||||
}
|
||||
typesCommande.addEventListener('change', function () {
|
||||
if (typesCommande.value == 1) {
|
||||
Imprimente.removeAttribute("href");
|
||||
Imprimente.setAttribute("href", base_url +'orders/printDiv/' + idData);
|
||||
} else if(typesCommande.value == 3) {
|
||||
Imprimente.removeAttribute("href");
|
||||
Imprimente.setAttribute("href", base_url +'orders/printDivBL/' + idData);
|
||||
} else {
|
||||
Imprimente.removeAttribute("href");
|
||||
Imprimente.setAttribute("href", base_url +'orders/printDivBLF/' + idData);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function() {
|
||||
$(".select_group").select2();
|
||||
|
||||
$("#mainOrdersNav").addClass('active');
|
||||
$("#manageOrdersNav").addClass('active');
|
||||
|
||||
// ✅ Initialiser l'affichage selon le type de client
|
||||
toggleQuantityColumn();
|
||||
|
||||
// ✅ Écouter les changements du type de client
|
||||
$('#customer_type').on('change', function() {
|
||||
toggleQuantityColumn();
|
||||
});
|
||||
|
||||
// ✅ Gérer les changements de quantité
|
||||
$(document).on('input', 'input[name="qty[]"]', function() {
|
||||
var row_id = $(this).attr('id').replace('qty_', '');
|
||||
var qty = parseInt($(this).val()) || 1;
|
||||
|
||||
if (qty < 1) {
|
||||
$(this).val(1);
|
||||
qty = 1;
|
||||
}
|
||||
|
||||
getTotal(row_id);
|
||||
});
|
||||
|
||||
$(document).on('keydown', '.numeric-input, input[type="number"]', function(e) {
|
||||
preventNegativeInput(e);
|
||||
});
|
||||
|
||||
$(document).on('input', '.numeric-input, input[type="number"]', function() {
|
||||
validatePositiveNumber(this);
|
||||
});
|
||||
|
||||
$("#discount").on('input', function() {
|
||||
validatePrixDemande(this);
|
||||
});
|
||||
|
||||
$("#discount").on('blur', function() {
|
||||
validatePrixDemande(this);
|
||||
checkMinimalPrice();
|
||||
});
|
||||
|
||||
$('form').on('submit', function(e) {
|
||||
let hasNegative = false;
|
||||
$('.numeric-input, input[type="number"]').each(function() {
|
||||
if (parseFloat($(this).val()) < 0) {
|
||||
hasNegative = true;
|
||||
$(this).val('');
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNegative) {
|
||||
alert('Les valeurs négatives ne sont pas autorisées.');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkMinimalPrice()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
var paymentTranche = 1;
|
||||
|
||||
function getMontantTotal() {
|
||||
@ -492,6 +632,10 @@ if (typesCommande) {
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
var customerType = $('#customer_type').val();
|
||||
var qtyColumnVisible = (customerType === 'revendeur') ? '' : 'style="display:none;"';
|
||||
var qtyDisabled = (customerType === 'revendeur') ? '' : 'disabled';
|
||||
|
||||
var html = '<tr id="row_' + row_id + '">' +
|
||||
'<td>' +
|
||||
'<select class="form-control select_group product" data-row-id="' + row_id + '" id="product_' + row_id + '" name="product[]" style="width:100%;" onchange="getProductData(' + row_id + ')">' +
|
||||
@ -502,16 +646,13 @@ if (typesCommande) {
|
||||
if (value.numero_de_moteur) {
|
||||
displayText += ' | ' + value.numero_de_moteur;
|
||||
}
|
||||
if (value.puissance) {
|
||||
displayText += ' | ' + value.puissance;
|
||||
}
|
||||
html += '<option value="' + value.id + '">' + displayText + '</option>';
|
||||
});
|
||||
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
'<td><input type="number" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="Puissance" autocomplete="off" min="1"></td>' +
|
||||
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control" disabled><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"></td>' +
|
||||
html += '</select></td>' +
|
||||
'<td class="qty-column" ' + qtyColumnVisible + '><input type="number" name="qty[]" id="qty_' + row_id + '" class="form-control numeric-input" min="1" value="1" ' + qtyDisabled + ' autocomplete="off"></td>' +
|
||||
'<td><input type="number" name="puissance[]" id="puissance_' + row_id + '" class="form-control" placeholder="CC" min="1" autocomplete="off"></td>' +
|
||||
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control" disabled><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' +
|
||||
'<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control" disabled><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
|
||||
'<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
|
||||
'</tr>';
|
||||
@ -532,10 +673,17 @@ if (typesCommande) {
|
||||
|
||||
function getTotal(row = null) {
|
||||
if (row) {
|
||||
var total = Number($("#rate_value_" + row).val());
|
||||
var rate = Number($("#rate_value_" + row).val()) || 0;
|
||||
var qty = Number($("#qty_" + row).val()) || 1;
|
||||
|
||||
if (rate < 0) rate = 0;
|
||||
if (qty < 1) qty = 1;
|
||||
|
||||
var total = rate * qty;
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row).val(total);
|
||||
$("#amount_value_" + row).val(total);
|
||||
|
||||
subAmount();
|
||||
} else {
|
||||
alert('no row !! please refresh the page');
|
||||
@ -547,7 +695,9 @@ if (typesCommande) {
|
||||
if (product_id == "") {
|
||||
$("#rate_" + row_id).val("");
|
||||
$("#rate_value_" + row_id).val("");
|
||||
$("#min_price_" + row_id).val("");
|
||||
$("#puissance_" + row_id).val("");
|
||||
$("#qty_" + row_id).val("1");
|
||||
$("#amount_" + row_id).val("");
|
||||
$("#amount_value_" + row_id).val("");
|
||||
} else {
|
||||
@ -557,15 +707,25 @@ if (typesCommande) {
|
||||
data: { product_id: product_id },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
$("#rate_" + row_id).val(response.prix_vente);
|
||||
$("#rate_value_" + row_id).val(response.prix_vente);
|
||||
var prixVente = parseFloat(response.prix_vente) || 0;
|
||||
var prixMinimal = parseFloat(response.prix_minimal) || 0;
|
||||
|
||||
$("#puissance_" + row_id).val(response.puissance || '');
|
||||
if (prixVente < 0) prixVente = 0;
|
||||
if (prixMinimal < 0) prixMinimal = 0;
|
||||
|
||||
$("#rate_" + row_id).val(prixVente);
|
||||
$("#rate_value_" + row_id).val(prixVente);
|
||||
$("#min_price_" + row_id).val(prixMinimal);
|
||||
|
||||
var total = Number(response.prix_vente);
|
||||
var puissanceValue = response.puissance || '';
|
||||
$("#puissance_" + row_id).val(puissanceValue);
|
||||
|
||||
var qty = parseInt($("#qty_" + row_id).val()) || 1;
|
||||
var total = prixVente * qty;
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row_id).val(total);
|
||||
$("#amount_value_" + row_id).val(total);
|
||||
|
||||
subAmount();
|
||||
}
|
||||
});
|
||||
@ -582,7 +742,9 @@ if (typesCommande) {
|
||||
var tr = $("#product_info_table tbody tr")[x];
|
||||
var count = $(tr).attr('id');
|
||||
count = count.substring(4);
|
||||
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
|
||||
var amount = Number($("#amount_" + count).val());
|
||||
if (amount < 0) amount = 0;
|
||||
totalSubAmount = Number(totalSubAmount) + amount;
|
||||
}
|
||||
|
||||
totalSubAmount = totalSubAmount.toFixed(2);
|
||||
@ -599,11 +761,17 @@ if (typesCommande) {
|
||||
$("#service_charge").val(service);
|
||||
$("#service_charge_value").val(service);
|
||||
|
||||
// ✅ CORRECTION : net_amount = gross_amount - discount
|
||||
var discount = Number($("#discount").val()) || 0;
|
||||
if (discount < 0) {
|
||||
discount = 0;
|
||||
$("#discount").val('');
|
||||
}
|
||||
|
||||
var grossAmount = Number(totalSubAmount);
|
||||
var netAmount = grossAmount - discount;
|
||||
|
||||
if (netAmount < 0) netAmount = 0;
|
||||
|
||||
netAmount = netAmount.toFixed(2);
|
||||
$("#net_amount").val(netAmount);
|
||||
$("#net_amount_value").val(netAmount);
|
||||
@ -617,7 +785,44 @@ if (typesCommande) {
|
||||
}
|
||||
|
||||
updateMontantTranches();
|
||||
}
|
||||
}
|
||||
|
||||
function checkMinimalPrice() {
|
||||
var discount = Number($("#discount").val()) || 0;
|
||||
|
||||
if (discount < 0) {
|
||||
alert("Le prix demandé ne peut pas être négatif.");
|
||||
$("#discount").val('');
|
||||
subAmount();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (discount === 0) return true;
|
||||
|
||||
var tableProductLength = $("#product_info_table tbody tr").length;
|
||||
var error = false;
|
||||
var messages = [];
|
||||
|
||||
for (var i = 0; i < tableProductLength; i++) {
|
||||
var tr = $("#product_info_table tbody tr")[i];
|
||||
var rowId = $(tr).attr('id').replace('row_', '');
|
||||
var minPrice = Number($("#min_price_" + rowId).val()) || 0;
|
||||
|
||||
if (minPrice > 0 && discount < minPrice) {
|
||||
error = true;
|
||||
var productText = $("#product_" + rowId + " option:selected").text();
|
||||
messages.push("Le prix demandé (" + discount.toFixed(2) + ") pour « " + productText + " » est inférieur au prix minimal (" + minPrice.toFixed(2) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
alert(messages.join("\n"));
|
||||
$("#discount").val('');
|
||||
subAmount();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function paidAmount() {
|
||||
var grandTotal = $("#net_amount_value").val();
|
||||
@ -637,13 +842,10 @@ if (typesCommande) {
|
||||
function getMontantPourTranches() {
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
var grossAmount = parseFloat($("#gross_amount_value").val()) || 0;
|
||||
|
||||
// Si discount existe, on utilise gross_amount - discount
|
||||
// Sinon on utilise gross_amount
|
||||
return discount > 0 ? (grossAmount - discount) : grossAmount;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMontantTranches() {
|
||||
function updateMontantTranches() {
|
||||
var montant = getMontantPourTranches();
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
|
||||
@ -662,14 +864,15 @@ function updateMontantTranches() {
|
||||
} else {
|
||||
calculerTranche2();
|
||||
}
|
||||
}
|
||||
function calculerTranche2() {
|
||||
}
|
||||
|
||||
function calculerTranche2() {
|
||||
var montantTotal = getMontantPourTranches();
|
||||
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
|
||||
var tranche2 = montantTotal - tranche1;
|
||||
if (tranche2 < 0) tranche2 = 0;
|
||||
$("#payment_amount_2").val(tranche2.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
$("#discount").on('keyup', function() {
|
||||
subAmount();
|
||||
|
||||
@ -380,7 +380,8 @@
|
||||
}
|
||||
|
||||
// ✅ FONCTION MODIFIÉE POUR AFFICHER LES NOUVELLES COLONNES
|
||||
$(document).on('click', '.btn-view', function(e) {
|
||||
// ✅ FONCTION MODIFIÉE POUR AFFICHER LA QUANTITÉ
|
||||
$(document).on('click', '.btn-view', function(e) {
|
||||
e.preventDefault();
|
||||
var orderId = $(this).data('order-id');
|
||||
currentOrderId = orderId;
|
||||
@ -428,6 +429,7 @@
|
||||
'<th>Numéro de Série</th>' +
|
||||
'<th>N° de Moteur</th>' +
|
||||
'<th>Châssis</th>' +
|
||||
'<th>Quantité</th>' + // ✅ AJOUT
|
||||
'<th>N° Facture</th>' +
|
||||
'<th>Statut</th>'
|
||||
);
|
||||
@ -436,7 +438,9 @@
|
||||
$headers.append(
|
||||
'<th>Marque</th>' +
|
||||
'<th>Désignation</th>' +
|
||||
'<th>Prix de vente</th>' +
|
||||
'<th>Quantité</th>' + // ✅ AJOUT
|
||||
'<th>Prix unitaire</th>' + // ✅ MODIFIÉ
|
||||
'<th>Montant</th>' + // ✅ AJOUT (prix × quantité)
|
||||
'<th>Prix demandé</th>' +
|
||||
'<th>Remise</th>' +
|
||||
'<th>Statut</th>'
|
||||
@ -465,6 +469,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ RÉCUPÉRATION DE LA QUANTITÉ
|
||||
var qty = parseInt(item.qty) || 1;
|
||||
|
||||
<?php if ($users['group_name'] === 'SECURITE'): ?>
|
||||
// ✅ AFFICHAGE POUR SECURITE
|
||||
$tb.append(
|
||||
@ -474,23 +481,27 @@
|
||||
'<td>' + (product.sku || '') + '</td>' +
|
||||
'<td>' + (product.numero_de_moteur || 'N/A') + '</td>' +
|
||||
'<td>' + (product.chasis || 'N/A') + '</td>' +
|
||||
'<td>' + qty + '</td>' + // ✅ AJOUT
|
||||
'<td>' + (d.bill_no || 'N/A') + '</td>' +
|
||||
'<td>' + statutHtml + '</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
<?php else: ?>
|
||||
// ✅ AFFICHAGE POUR LES AUTRES RÔLES
|
||||
var prixVente = parseFloat(product.prix_vente || 0);
|
||||
var prixDemande = parseFloat(d.discount || 0); // Prix demandé = discount
|
||||
var netAmount = parseFloat(d.net_amount || 0); // Remise = net_amount
|
||||
var prixUnitaire = parseFloat(item.rate || 0); // Prix unitaire depuis l'item
|
||||
var montant = prixUnitaire * qty; // ✅ CALCUL DU MONTANT TOTAL
|
||||
var prixDemande = parseFloat(d.discount || 0);
|
||||
var remise = parseFloat(d.net_amount || 0);
|
||||
|
||||
$tb.append(
|
||||
'<tr>' +
|
||||
'<td>' + (brandName || 'Aucune marque') + '</td>' +
|
||||
'<td>' + (product.name || '') + '</td>' +
|
||||
'<td>' + prixVente.toLocaleString('fr-FR') + ' Ar</td>' +
|
||||
'<td>' + qty + '</td>' +
|
||||
'<td>' + prixUnitaire.toLocaleString('fr-FR') + ' Ar</td>' + // ✅ PRIX UNITAIRE
|
||||
'<td>' + montant.toLocaleString('fr-FR') + ' Ar</td>' + // ✅ MONTANT TOTAL
|
||||
'<td>' + prixDemande.toLocaleString('fr-FR') + ' Ar</td>' +
|
||||
'<td>' + netAmount.toLocaleString('fr-FR') + ' Ar</td>' +
|
||||
'<td>' + remise.toLocaleString('fr-FR') + ' Ar</td>' +
|
||||
'<td>' + statutHtml + '</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
|
||||
@ -1,11 +1,138 @@
|
||||
<!-- application/Views/securite/index.php -->
|
||||
<!-- application/Views/securite/history.php -->
|
||||
<style>
|
||||
.validation-summary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.validation-summary .box-header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.validation-summary .box-title {
|
||||
color: #667eea;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.validation-summary .box-body {
|
||||
background: #ffffff;
|
||||
padding: 35px 25px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-radius: 12px;
|
||||
padding: 25px 15px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-left: 5px solid #2196f3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: #1976d2;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: #1565c0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.badge-validated {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
#historyTable thead {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#historyTable thead th {
|
||||
border: none !important;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#historyTable tbody tr {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#historyTable tbody tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.moto-image {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
color: #667eea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.validation-date {
|
||||
color: #6c757d;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header -->
|
||||
<section class="content-header">
|
||||
<h1>Validation de sortie <small>de matériel</small></h1>
|
||||
<h1>
|
||||
Historique des Livraisons
|
||||
<small>Suivi des sorties validées par magasin</small>
|
||||
</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#"><i class="fa fa-dashboard"></i> Accueil</a></li>
|
||||
<li class="active">Validation des matériels</li>
|
||||
<li><a href="<?= base_url('#') ?>"><i class="fa fa-dashboard"></i> Accueil</a></li>
|
||||
<li><a href="<?= base_url('validateSecurite') ?>"><i class="fa fa-shield"></i> Livraisons</a></li>
|
||||
<li class="active">Historique</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
@ -14,129 +141,603 @@
|
||||
<div id="messages"></div>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
|
||||
<?php elseif (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
|
||||
<div class="alert alert-success alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
<?= session()->getFlashdata('success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header"><h3 class="box-title">Espace validation de sortie de vente</h3></div>
|
||||
<div class="box-body">
|
||||
<table id="manageTable" class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>UGS</th>
|
||||
<th>Désignation</th>
|
||||
<th>Statut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<!-- Statistiques -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="validation-summary">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-bar-chart"></i> Résumé des Livraisons
|
||||
<span id="storeNameDisplay" style="color: #764ba2; margin-left: 10px;"></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<i class="fa fa-check-circle" style="font-size: 40px; color: #2196f3;"></i>
|
||||
<div class="stat-number" id="totalValidations">0</div>
|
||||
<div class="stat-label">Total Livraisons</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<i class="fa fa-calendar" style="font-size: 40px; color: #2196f3;"></i>
|
||||
<div class="stat-number" id="todayValidations">0</div>
|
||||
<div class="stat-label">Livraisons Aujourd'hui</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<i class="fa fa-motorcycle" style="font-size: 40px; color: #2196f3;"></i>
|
||||
<div class="stat-number" id="monthValidations">0</div>
|
||||
<div class="stat-label">Livraisons ce Mois</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="filter-section">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="filterStartDate">Date de début</label>
|
||||
<input type="date" id="filterStartDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="filterEndDate">Date de fin</label>
|
||||
<input type="date" id="filterEndDate" class="form-control">
|
||||
</div>
|
||||
<!-- ✅ FILTRE PAR MAGASIN -->
|
||||
<div class="col-md-3">
|
||||
<label for="filterStore">Magasin</label>
|
||||
<select id="filterStore" class="form-control">
|
||||
<option value="">Tous les magasins</option>
|
||||
<?php if (isset($stores) && is_array($stores)): ?>
|
||||
<?php foreach ($stores as $store): ?>
|
||||
<option value="<?= $store['id'] ?>"><?= $store['name'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label> </label><br>
|
||||
<button id="btnFilter" class="btn btn-primary">
|
||||
<i class="fa fa-filter"></i> Filtrer
|
||||
</button>
|
||||
<button id="btnReset" class="btn btn-warning">
|
||||
<i class="fa fa-refresh"></i> Réinitialiser
|
||||
</button>
|
||||
<button id="btnExport" class="btn btn-success">
|
||||
<i class="fa fa-file-excel-o"></i> Exporter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tableau historique -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-history"></i> Historique des Livraisons
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table id="historyTable" class="table table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>N° Facture</th>
|
||||
<th>Désignation</th>
|
||||
<th>UGS</th>
|
||||
<th>Marque</th>
|
||||
<th>Client</th>
|
||||
<th>Magasin</th>
|
||||
<th>Agent Sécurité</th>
|
||||
<th>Date Validation</th>
|
||||
<th>Statut</th>
|
||||
<th>Quantité</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Les données seront chargées par DataTables -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<!-- Modal Détails -->
|
||||
<div class="modal fade" id="detailsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form id="updateForm" action="<?= base_url('validateSecurite/update') ?>" method="post">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Valider la sortie de la moto</h4>
|
||||
</div>
|
||||
<div class="modal-body row">
|
||||
<div class="col-sm-4 text-center">
|
||||
<img id="motoImage" class="img-responsive img-thumbnail" src="" alt="Image moto">
|
||||
<div class="modal-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<button type="button" class="close" data-dismiss="modal" style="color: white;">×</button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-info-circle"></i> Détails de la Validation
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center">
|
||||
<img id="detailImage" class="img-responsive img-thumbnail" src="" alt="Image moto" style="max-height: 250px;">
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<h5><strong>Nom :</strong> <span id="motoNom"></span></h5>
|
||||
<h5><strong>UGS :</strong> <span id="motoGSU"></span></h5>
|
||||
<h5><strong>FACTURE :</strong> <span id="motoAdditionalInfo"></span></h5>
|
||||
<h5><strong>Client :</strong> <span id="customer_name"></span></h5>
|
||||
<h5><strong>Adresse :</strong> <span id="customer_address"></span></h5>
|
||||
<h5><strong>Téléphone :</strong> <span id="customer_phone"></span></h5>
|
||||
<h5><strong>CIN :</strong> <span id="customer_cin"></span></h5>
|
||||
<div class="col-md-8">
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<i class="fa fa-motorcycle"></i> Informations Produit
|
||||
</h4>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>N° Facture:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailBillNo"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Désignation:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailDesignation"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>UGS:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailUGS"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Marque:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailMarque"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>N° de Série:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailSerie"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Magasin:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailStore"></span></div>
|
||||
</div>
|
||||
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin: 20px 0;">
|
||||
<i class="fa fa-user"></i> Informations Client
|
||||
</h4>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Nom:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailClientName"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Téléphone:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailClientPhone"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Adresse:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailClientAddress"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>CIN:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailClientCIN"></span></div>
|
||||
</div>
|
||||
|
||||
<h4 style="border-bottom: 2px solid #667eea; padding-bottom: 10px; margin: 20px 0;">
|
||||
<i class="fa fa-shield"></i> Informations Validation
|
||||
</h4>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Agent Sécurité:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailAgent" class="agent-name"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Date Validation:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailDateValidation"></span></div>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-xs-6"><strong>Statut:</strong></div>
|
||||
<div class="col-xs-6"><span id="detailStatut"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="hidden" name="status" value="SORTIE">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
|
||||
<button type="submit" class="btn btn-success">Valider</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i> Fermer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
$("#securiteNav").addClass('active');
|
||||
|
||||
// ===== Initialisation DataTable =====$.extend(true, $.fn.dataTable.defaults, {
|
||||
|
||||
// Configuration DataTable 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"
|
||||
}
|
||||
sProcessing: "Traitement en cours...",
|
||||
sSearch: "Rechercher :",
|
||||
sLengthMenu: "Afficher _MENU_ éléments",
|
||||
sInfo: "Affichage de _START_ à _END_ sur _TOTAL_ éléments",
|
||||
sInfoEmpty: "Aucun élément à afficher",
|
||||
sInfoFiltered: "(filtré de _MAX_ éléments au total)",
|
||||
sLoadingRecords: "Chargement en cours...",
|
||||
sZeroRecords: "Aucune validation trouvée",
|
||||
sEmptyTable: "Aucune donnée disponible",
|
||||
oPaginate: {
|
||||
sFirst: "Premier",
|
||||
sPrevious: "Précédent",
|
||||
sNext: "Suivant",
|
||||
sLast: "Dernier"
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#manageTable').DataTable({
|
||||
ajax: {
|
||||
url: '<?= base_url('validateSecurite/fetchSecuriteData') ?>',
|
||||
type: 'GET',
|
||||
dataSrc: 'data'
|
||||
},
|
||||
columns: [
|
||||
{ data: 'image', title: 'Image', orderable: false },
|
||||
{ data: 'ugs', title: 'UGS' },
|
||||
{ data: 'designation', title: 'Désignation' },
|
||||
{ data: 'statut', title: 'Statut', orderable: false },
|
||||
{ data: 'action', title: 'Action', orderable: false }
|
||||
],
|
||||
order: [[1, 'asc']]
|
||||
});
|
||||
|
||||
// ===== Edition / validation =====
|
||||
function editFunc(id) {
|
||||
$.post('<?= base_url('validateSecurite/fetchSecuriteDataById') ?>/'+id, function(resp) {
|
||||
$('#motoImage').attr('src', resp.image);
|
||||
$('#motoNom').text(resp.nom);
|
||||
$('#motoGSU').text(resp.ugs);
|
||||
$('#motoAdditionalInfo').text(resp.bill_no);
|
||||
$('#customer_name').text(resp.customer_name);
|
||||
$('#customer_address').text(resp.customer_address);
|
||||
$('#customer_phone').text(resp.customer_phone);
|
||||
$('#customer_cin').text(resp.customer_cin);
|
||||
$('#editModal').modal('show');
|
||||
// ✅ INITIALISER LES DATES À AUJOURD'HUI PAR DÉFAUT
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
|
||||
$('#updateForm').off('submit').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.post(this.action+'/'+id, $(this).serialize(), function(res) {
|
||||
$('#editModal').modal('hide');
|
||||
$('#manageTable').DataTable().ajax.reload(null, false);
|
||||
$('#messages').html(
|
||||
'<div class="alert alert-'+(res.success?'success':'danger')+'">'+res.messages+'</div>'
|
||||
);
|
||||
}, 'json');
|
||||
// ✅ CHARGER LES STATISTIQUES DU MOIS AU DÉMARRAGE
|
||||
loadStatisticsForCurrentMonth();
|
||||
|
||||
// ✅ INITIALISATION DATATABLE
|
||||
var historyTable = $('#historyTable').DataTable({
|
||||
ajax: {
|
||||
url: '<?= base_url('reports/detail/fetchSecuritePerformances') ?>',
|
||||
type: 'GET',
|
||||
data: function(d) {
|
||||
d.startDate = $('#filterStartDate').val();
|
||||
d.endDate = $('#filterEndDate').val();
|
||||
d.store_id = $('#filterStore').val();
|
||||
|
||||
console.log('📤 Filtres envoyés:', {
|
||||
startDate: d.startDate,
|
||||
endDate: d.endDate,
|
||||
store_id: d.store_id
|
||||
});
|
||||
},
|
||||
dataSrc: function(json) {
|
||||
console.log('📥 Données reçues:', json.data.length, 'lignes');
|
||||
return json.data;
|
||||
},
|
||||
error: function(xhr, error, code) {
|
||||
console.error('❌ Erreur DataTables:', error);
|
||||
alert('Erreur lors du chargement des données');
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{ data: 0, orderable: false, render: function(data) {
|
||||
return '<img src="' + data + '" class="moto-image" alt="Moto">';
|
||||
}},
|
||||
{ data: 1 },
|
||||
{ data: 2 },
|
||||
{ data: 3 },
|
||||
{ data: 4 },
|
||||
{ data: 5 },
|
||||
{ data: 7, render: function(data) {
|
||||
return '<span class="badge badge-info">' + data + '</span>';
|
||||
}},
|
||||
{ data: 6, render: function(data) {
|
||||
return '<span class="agent-name">' + data + '</span>';
|
||||
}},
|
||||
{ data: 8, render: function(data) {
|
||||
return '<span class="validation-date">' + data + '</span>';
|
||||
}},
|
||||
{ data: 9, orderable: false },
|
||||
{ data: 10, orderable: true, className: 'text-center', render: function(data) {
|
||||
return '<span class="badge badge-primary">' + data + '</span>';
|
||||
}}
|
||||
],
|
||||
order: [[8, 'desc']],
|
||||
pageLength: 10,
|
||||
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, "Tout"]]
|
||||
});
|
||||
|
||||
// ✅ BOUTON FILTRER - RECHARGE TABLEAU + STATISTIQUES
|
||||
$('#btnFilter').on('click', function() {
|
||||
console.log('🔍 Bouton Filtrer cliqué');
|
||||
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
var storeId = $('#filterStore').val();
|
||||
|
||||
console.log('Dates:', startDate, 'à', endDate);
|
||||
console.log('Store ID:', storeId || 'Tous');
|
||||
|
||||
historyTable.ajax.reload();
|
||||
|
||||
// ✅ RECHARGER LES STATISTIQUES SELON LA PÉRIODE ET LE MAGASIN FILTRÉS
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, storeId);
|
||||
});
|
||||
|
||||
// ✅ BOUTON RÉINITIALISER
|
||||
$('#btnReset').on('click', function() {
|
||||
console.log('🔄 Réinitialisation des filtres');
|
||||
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#filterStartDate').val(today);
|
||||
$('#filterEndDate').val(today);
|
||||
$('#filterStore').val('');
|
||||
$('#storeNameDisplay').text('');
|
||||
|
||||
historyTable.ajax.reload();
|
||||
loadStatisticsForCurrentMonth();
|
||||
});
|
||||
|
||||
// ✅ CHANGEMENT DE MAGASIN - METTRE À JOUR LES STATS
|
||||
$('#filterStore').on('change', function() {
|
||||
var storeId = $(this).val();
|
||||
var storeName = $(this).find('option:selected').text();
|
||||
var startDate = $('#filterStartDate').val();
|
||||
var endDate = $('#filterEndDate').val();
|
||||
|
||||
console.log('🏪 Magasin changé:', storeName);
|
||||
|
||||
// Afficher le nom du magasin dans le titre
|
||||
if (storeId) {
|
||||
$('#storeNameDisplay').text('- ' + storeName);
|
||||
} else {
|
||||
$('#storeNameDisplay').text('');
|
||||
}
|
||||
|
||||
// Recharger automatiquement le tableau et les stats
|
||||
historyTable.ajax.reload();
|
||||
|
||||
if (startDate && endDate) {
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, storeId);
|
||||
} else {
|
||||
loadStatisticsForCurrentMonth();
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ FONCTION : CHARGER LES STATISTIQUES DU MOIS EN COURS
|
||||
function loadStatisticsForCurrentMonth() {
|
||||
console.log('📊 Chargement des statistiques du mois en cours...');
|
||||
|
||||
var now = new Date();
|
||||
var firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
var lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
var startDate = firstDay.toISOString().split('T')[0];
|
||||
var endDate = lastDay.toISOString().split('T')[0];
|
||||
|
||||
loadStatisticsForFilteredPeriod(startDate, endDate, '');
|
||||
}
|
||||
|
||||
// ✅ FONCTION : CHARGER LES STATISTIQUES POUR UNE PÉRIODE ET UN MAGASIN DONNÉS
|
||||
function loadStatisticsForFilteredPeriod(startDate, endDate, storeId) {
|
||||
console.log('📊 Chargement des statistiques pour:', startDate, 'au', endDate, 'Store ID:', storeId || 'TOUS');
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('reports/detail/fetchSecuritePerformances') ?>',
|
||||
type: 'GET',
|
||||
data: {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
store_id: storeId
|
||||
},
|
||||
success: function(response) {
|
||||
console.log('✅ Statistiques chargées:', response.data.length, 'lignes');
|
||||
calculateStatistics(response.data, startDate, endDate, storeId);
|
||||
},
|
||||
error: function(xhr, error) {
|
||||
console.error('❌ Erreur chargement statistiques:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ FONCTION POUR CALCULER LES STATISTIQUES (AVEC FILTRAGE PAR MAGASIN)
|
||||
function calculateStatistics(data, filterStartDate, filterEndDate, filterStoreId) {
|
||||
var today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
var todayCount = 0;
|
||||
var monthCount = 0;
|
||||
|
||||
var filteredMonth = null;
|
||||
var filteredYear = null;
|
||||
|
||||
if (filterStartDate && filterEndDate) {
|
||||
var startDateObj = new Date(filterStartDate);
|
||||
var endDateObj = new Date(filterEndDate);
|
||||
|
||||
if (startDateObj.getMonth() === endDateObj.getMonth() &&
|
||||
startDateObj.getFullYear() === endDateObj.getFullYear()) {
|
||||
filteredMonth = startDateObj.getMonth();
|
||||
filteredYear = startDateObj.getFullYear();
|
||||
}
|
||||
}
|
||||
|
||||
$.each(data, function(i, item) {
|
||||
var itemDateStr = item[8]; // Date de validation
|
||||
|
||||
if (itemDateStr && itemDateStr !== 'N/A') {
|
||||
try {
|
||||
var date;
|
||||
|
||||
if (itemDateStr.includes('/')) {
|
||||
var parts = itemDateStr.split(' ')[0].split('/');
|
||||
date = new Date(parts[2], parts[1] - 1, parts[0]);
|
||||
} else {
|
||||
date = new Date(itemDateStr);
|
||||
}
|
||||
|
||||
if (!isNaN(date.getTime())) {
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
// Compter aujourd'hui
|
||||
if (date.getTime() === today.getTime()) {
|
||||
todayCount++;
|
||||
}
|
||||
|
||||
// Compter le mois filtré (ou mois en cours)
|
||||
if (filteredMonth !== null) {
|
||||
if (date.getMonth() === filteredMonth && date.getFullYear() === filteredYear) {
|
||||
monthCount++;
|
||||
}
|
||||
} else {
|
||||
if (date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear()) {
|
||||
monthCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Date invalide:', itemDateStr, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ METTRE À JOUR LES COMPTEURS
|
||||
$('#totalValidations').text(monthCount);
|
||||
$('#todayValidations').text(todayCount);
|
||||
$('#monthValidations').text(monthCount);
|
||||
|
||||
console.log('📈 Statistiques:', {
|
||||
store_id: filterStoreId || 'TOUS',
|
||||
total: monthCount,
|
||||
aujourd_hui: todayCount,
|
||||
ce_mois: monthCount,
|
||||
periode_filtree: filterStartDate + ' au ' + filterEndDate
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ BOUTON EXPORT EXCEL
|
||||
$('#btnExport').on('click', function() {
|
||||
var wb = XLSX.utils.book_new();
|
||||
var data = [];
|
||||
|
||||
data.push([
|
||||
'N° Facture',
|
||||
'Désignation',
|
||||
'UGS',
|
||||
'Marque',
|
||||
'Client',
|
||||
'Magasin',
|
||||
'Agent Sécurité',
|
||||
'Date Validation',
|
||||
'Statut',
|
||||
'Quantité'
|
||||
]);
|
||||
|
||||
historyTable.rows({ search: 'applied' }).every(function() {
|
||||
var rowData = this.data();
|
||||
data.push([
|
||||
rowData[1],
|
||||
rowData[2],
|
||||
rowData[3],
|
||||
rowData[4],
|
||||
rowData[5],
|
||||
rowData[7],
|
||||
rowData[6],
|
||||
rowData[8],
|
||||
'VALIDÉE',
|
||||
rowData[10]
|
||||
]);
|
||||
});
|
||||
|
||||
var ws = XLSX.utils.aoa_to_sheet(data);
|
||||
|
||||
ws['!cols'] = [
|
||||
{ wch: 15 }, { wch: 30 }, { wch: 15 }, { wch: 20 }, { wch: 25 },
|
||||
{ wch: 20 }, { wch: 25 }, { wch: 20 }, { wch: 12 }, { wch: 10 }
|
||||
];
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, "Historique");
|
||||
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
var filename = 'historique_livraisons_' + today + '.xlsx';
|
||||
|
||||
XLSX.writeFile(wb, filename);
|
||||
|
||||
console.log('✅ Export Excel réalisé:', filename);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// ✅ FONCTION POUR AFFICHER LES DÉTAILS
|
||||
function viewDetails(id) {
|
||||
$.get('<?= base_url('reports/detail/getSecuriteValidationDetails') ?>/' + id, function(data) {
|
||||
$('#detailImage').attr('src', data[0]);
|
||||
$('#detailBillNo').text(data[1]);
|
||||
$('#detailDesignation').text(data[2]);
|
||||
$('#detailUGS').text(data[3]);
|
||||
$('#detailMarque').text(data[4]);
|
||||
$('#detailSerie').text(data[3]);
|
||||
$('#detailStore').text(data[7]);
|
||||
$('#detailClientName').text(data[5]);
|
||||
$('#detailClientPhone').text(data[12]);
|
||||
$('#detailClientAddress').text(data[13]);
|
||||
$('#detailClientCIN').text(data[14]);
|
||||
$('#detailAgent').text(data[6]);
|
||||
$('#detailDateValidation').text(formatDate(data[8]));
|
||||
$('#detailStatut').html(data[9]);
|
||||
|
||||
var additionalInfo = '';
|
||||
if (data[15] && data[15] !== 'N/A') {
|
||||
additionalInfo += '<div class="row" style="margin-bottom: 15px;">';
|
||||
additionalInfo += '<div class="col-xs-6"><strong>N° de Moteur:</strong></div>';
|
||||
additionalInfo += '<div class="col-xs-6"><span>' + data[15] + '</span></div>';
|
||||
additionalInfo += '</div>';
|
||||
}
|
||||
if (data[16] && data[16] !== 'N/A') {
|
||||
additionalInfo += '<div class="row" style="margin-bottom: 15px;">';
|
||||
additionalInfo += '<div class="col-xs-6"><strong>N° de Châssis:</strong></div>';
|
||||
additionalInfo += '<div class="col-xs-6"><span>' + data[16] + '</span></div>';
|
||||
additionalInfo += '</div>';
|
||||
}
|
||||
|
||||
if (additionalInfo) {
|
||||
$('#detailSerie').closest('.row').after(additionalInfo);
|
||||
}
|
||||
|
||||
$('#detailsModal').modal('show');
|
||||
}, 'json');
|
||||
}
|
||||
</script>
|
||||
|
||||
// ✅ FONCTION POUR FORMATER LA DATE
|
||||
function formatDate(dateString) {
|
||||
if (!dateString || dateString === 'N/A') return 'N/A';
|
||||
|
||||
try {
|
||||
var date;
|
||||
|
||||
if (dateString.includes('/')) {
|
||||
var parts = dateString.split(' ');
|
||||
var dateParts = parts[0].split('/');
|
||||
var timeParts = parts[1] ? parts[1].split(':') : ['00', '00'];
|
||||
date = new Date(dateParts[2], dateParts[1] - 1, dateParts[0], timeParts[0], timeParts[1]);
|
||||
} else {
|
||||
date = new Date(dateString);
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return dateString;
|
||||
}
|
||||
|
||||
var options = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
};
|
||||
return date.toLocaleDateString('fr-FR', options);
|
||||
} catch (e) {
|
||||
console.warn('Erreur formatage date:', dateString, e);
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -145,6 +145,15 @@
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('createEncaissement', $user_permission) || in_array('updateEncaissement', $user_permission) || in_array('viewEncaissement', $user_permission) || in_array('deleteEncaissement', $user_permission)): ?>
|
||||
<li id="storeNav">
|
||||
<a href="<?php echo base_url('encaissements/') ?>">
|
||||
<i class="fa fa-money"></i> <span>Autres Encaissements </span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('createRecouvrement', $user_permission) || in_array('viewRecouvrement', $user_permission) || in_array('updateRecouvrement', $user_permission) || in_array('deleteRecouvrement', $user_permission)): ?>
|
||||
<li id="recouvrement">
|
||||
<a href="<?php echo base_url('recouvrement/') ?>">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user