27102025
This commit is contained in:
parent
1c92093b90
commit
67b33ad2e6
@ -56,6 +56,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
$routes->get('/ventes/show/(:num)', [Auth::class, 'getSingle']);
|
||||
$routes->post('/ventes/moreimage/(:num)', [Auth::class, 'uploadImagePub']);
|
||||
$routes->post('/ventes/moreimage/supp/(:num)', [Auth::class, 'delete']);
|
||||
|
||||
|
||||
/**
|
||||
* route to logout
|
||||
@ -176,6 +177,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
// $routes->get('generateqrcode/(:num)', [QrCodeController::class, 'generate']);
|
||||
$routes->post('assign_store', [ProductCOntroller::class, 'assign_store']);
|
||||
$routes->post('createByExcel', [ProductCOntroller::class, 'createByExcel']);
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
@ -261,6 +263,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
|
||||
$routes->group('/notifications', function ($routes) {
|
||||
$routes->get('/', [NotificationController::class, 'getNotification']);
|
||||
$routes->post('markAsRead/(:num)', [NotificationController::class, 'markAsRead']);
|
||||
$routes->post('markAllAsRead', [NotificationController::class, 'markAllAsRead']);
|
||||
});
|
||||
// routes for sortie caisse
|
||||
$routes->group('/sortieCaisse', function ($routes) {
|
||||
@ -288,19 +291,37 @@ $routes->group('/sortieCaisse', function ($routes) {
|
||||
$routes->post('updateRemise/(:num)', [RemiseController::class, 'updateRemise']);
|
||||
});
|
||||
|
||||
// avance
|
||||
$routes->group('/avances', function ($routes) {
|
||||
$routes->get('/', [AvanceController::class, 'index']);
|
||||
$routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']);
|
||||
$routes->get('fetchCompletedAvances', [AvanceController::class, 'fetchCompletedAvances']);
|
||||
$routes->get('fetchIncompleteAvances', [AvanceController::class, 'fetchIncompleteAvances']);
|
||||
$routes->get('fetchAvanceBecameOrder', 'AvanceController::fetchAvanceBecameOrder');
|
||||
$routes->get('fetchExpiredAvance', [AvanceController::class, 'fetcheExpiredAvance']);
|
||||
$routes->get('fetchSingleAvance/(:num)', [AvanceController::class, 'fetchSingleAvance']);
|
||||
$routes->post('createAvance', [AvanceController::class, 'createAvance']);
|
||||
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
|
||||
$routes->post('updateAvance/(:num)', [AvanceController::class, 'updateAvance']);
|
||||
});
|
||||
// avance
|
||||
$routes->group('/avances', function ($routes) {
|
||||
$routes->get('/', [AvanceController::class, 'index']);
|
||||
|
||||
// ✅ Routes pour récupérer les données (GET)
|
||||
$routes->get('fetchAvanceData', [AvanceController::class, 'fetchAvanceData']);
|
||||
$routes->get('fetchAvanceBecameOrder', [AvanceController::class, 'fetchAvanceBecameOrder']);
|
||||
$routes->get('fetchExpiredAvance', [AvanceController::class, 'fetchExpiredAvance']);
|
||||
|
||||
// Routes pour une 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
|
||||
$routes->post('createAvance', [AvanceController::class, 'createAvance']);
|
||||
$routes->post('updateAvance', [AvanceController::class, 'updateAvance']);
|
||||
$routes->post('deleteAvance', [AvanceController::class, 'removeAvance']);
|
||||
$routes->post('notifyPrintInvoice', [AvanceController::class, 'notifyPrintInvoice']);
|
||||
|
||||
// ✅ AJOUTER CETTE ROUTE MANQUANTE
|
||||
$routes->post('processExpiredAvances', [AvanceController::class, 'processExpiredAvances']);
|
||||
|
||||
// ✅ Route CRON (optionnel)
|
||||
$routes->get('checkDeadlineAlerts', [AvanceController::class, 'checkDeadlineAlerts']);
|
||||
|
||||
$routes->post('payAvance', 'AvanceController::payAvance');
|
||||
$routes->get('forceConvertToOrder/(:num)', 'AvanceController::forceConvertToOrder/$1');
|
||||
$routes->post('checkAndConvertCompleted', 'AvanceController::checkAndConvertCompleted');
|
||||
});
|
||||
// historique
|
||||
$routes->group('historique', ['filter' => 'auth'], static function ($routes) {
|
||||
$routes->get('/', 'HistoriqueController::index');
|
||||
|
||||
@ -12,7 +12,6 @@ use CodeIgniter\Logger\LoggerInterface;
|
||||
abstract class AdminController extends BaseController
|
||||
{
|
||||
protected $permission = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (empty(session()->get('user'))) {
|
||||
@ -20,15 +19,15 @@ abstract class AdminController extends BaseController
|
||||
} else {
|
||||
$userIfo = session()->get('user');
|
||||
$userId = $userIfo['id'];
|
||||
|
||||
|
||||
$Groups = new Groups();
|
||||
$group_data = $Groups->getUserGroupByUserId($userId);
|
||||
|
||||
|
||||
$this->permission = unserialize($group_data['permission']);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* finction to verify role of users
|
||||
* @return mixed
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,10 @@ class Dashboard extends AdminController
|
||||
|
||||
public function index()
|
||||
{
|
||||
// === 🔥 Récupérer l'utilisateur en premier ===
|
||||
$session = session();
|
||||
$user_id = $session->get('user');
|
||||
|
||||
$productModel = new Products();
|
||||
$orderModel = new Orders();
|
||||
$userModel = new Users();
|
||||
@ -65,49 +69,82 @@ 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 ===
|
||||
$total_mvola = $mv1_orders + $mv2_orders + $mv_avances;
|
||||
$total_espece = $es1_orders + $es2_orders + $es_avances;
|
||||
$total_vb = $vb1_orders + $vb2_orders + $vb_avances;
|
||||
$total = $total_orders + $total_avances;
|
||||
// === 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;
|
||||
|
||||
// === AJUSTEMENTS AVEC RECOUVREMENTS ET SORTIES (PAR MODE DE PAIEMENT) ===
|
||||
$total_mvola_final = $total_mvola -
|
||||
$me -
|
||||
$mb +
|
||||
$bm -
|
||||
$total_sortie_mvola;
|
||||
|
||||
$total_espece_final = $total_espece +
|
||||
$me +
|
||||
$be -
|
||||
$total_sortie_espece;
|
||||
|
||||
$total_virement_bancaire_final = $total_vb -
|
||||
$be -
|
||||
$bm +
|
||||
$mb -
|
||||
$total_sortie_virement;
|
||||
// === AJUSTEMENTS AVEC RECOUVREMENTS ET SORTIES ===
|
||||
$total_mvola_final = $total_mvola_brut - $me - $mb + $bm - $total_sortie_mvola;
|
||||
$total_espece_final = $total_espece_brut + $me + $be - $total_sortie_espece;
|
||||
$total_virement_bancaire_final = $total_vb_brut - $be - $bm + $mb - $total_sortie_virement;
|
||||
|
||||
// === CALCUL DU TOTAL GÉNÉRAL ===
|
||||
// ✅ CORRECTION: Ne PAS additionner les recouvrements au total
|
||||
// Les recouvrements sont des transferts internes, pas des entrées d'argent
|
||||
$total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement;
|
||||
$total_final = $total - $total_sortie_global;
|
||||
|
||||
// check avance expired
|
||||
$avance = new Avance();
|
||||
$avance->checkExpiredAvance();
|
||||
|
||||
$total_final = $total_brut - $total_sortie_global;
|
||||
|
||||
// ✅ MODIFICATION : La caissière voit EXACTEMENT les mêmes montants que Direction/Conseil
|
||||
$data = [
|
||||
// === POUR DIRECTION/CONSEIL (AVEC TOUS LES AJUSTEMENTS) ===
|
||||
'total' => $total_final,
|
||||
'total_mvola' => $total_mvola_final,
|
||||
'total_espece' => $total_espece_final,
|
||||
'total_virement_bancaire' => $total_virement_bancaire_final,
|
||||
'user_permission' => $this->permission,
|
||||
|
||||
// === POUR CAISSIÈRE (MÊME CALCUL QUE DIRECTION) ===
|
||||
'total_caisse' => $total_final, // ← Identique à 'total'
|
||||
'total_mvola_caisse' => $total_mvola_final, // ← Identique à 'total_mvola'
|
||||
'total_espece_caisse' => $total_espece_final, // ← Identique à 'total_espece'
|
||||
'total_vb_caisse' => $total_virement_bancaire_final, // ← Identique à 'total_virement_bancaire'
|
||||
|
||||
// === DÉTAIL POUR LA CAISSIÈRE ===
|
||||
'total_orders_only' => $total_orders, // Ventes complètes uniquement
|
||||
'total_avances' => $total_avances, // Avances uniquement
|
||||
|
||||
// ✅ Détails des sorties
|
||||
'total_sorties' => $total_sortie_global,
|
||||
'total_sortie_espece' => $total_sortie_espece,
|
||||
'total_sortie_mvola' => $total_sortie_mvola,
|
||||
'total_sortie_virement' => $total_sortie_virement,
|
||||
|
||||
// ✅ Détails des recouvrements
|
||||
'recouvrement_me' => $me, // Mvola → Espèce
|
||||
'recouvrement_be' => $be, // Banque → Espèce
|
||||
'recouvrement_bm' => $bm, // Banque → Mvola
|
||||
'recouvrement_mb' => $mb, // Mvola → Banque
|
||||
'total_recouvrements' => $me + $be + $bm + $mb,
|
||||
|
||||
// Détail avances par mode de paiement
|
||||
'total_avances_mvola' => $mv_avances,
|
||||
'total_avances_espece' => $es_avances,
|
||||
'total_avances_virement' => $vb_avances,
|
||||
|
||||
// Détail orders par mode de paiement
|
||||
'total_mvola_orders' => $mv1_orders + $mv2_orders,
|
||||
'total_espece_orders' => $es1_orders + $es2_orders,
|
||||
'total_vb_orders' => $vb1_orders + $vb2_orders,
|
||||
|
||||
// ✅ Montants bruts (avant recouvrements et sorties)
|
||||
'total_brut' => $total_brut,
|
||||
'total_mvola_brut' => $total_mvola_brut,
|
||||
'total_espece_brut' => $total_espece_brut,
|
||||
'total_vb_brut' => $total_vb_brut,
|
||||
];
|
||||
|
||||
$data['total_products'] = $productModel->countTotalProducts();
|
||||
// === ✅ Compter les produits selon le store de l'utilisateur ===
|
||||
$data['total_products'] = $productModel->countProductsByUserStore();
|
||||
|
||||
// === ✅ Récupérer le nom du store pour l'affichage ===
|
||||
$isAdmin = in_array($user_id['group_name'], ['DAF', 'Direction']);
|
||||
|
||||
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';
|
||||
} else {
|
||||
$data['store_name'] = 'Tous les magasins';
|
||||
}
|
||||
|
||||
$data['total_paid_orders'] = $orderModel->countTotalPaidOrders();
|
||||
$data['total_users'] = $userModel->countTotalUsers();
|
||||
$data['total_stores'] = $storeModel->countTotalStores();
|
||||
@ -175,16 +212,14 @@ class Dashboard extends AdminController
|
||||
|
||||
$data['count_id'] = $countId;
|
||||
|
||||
// Check if the user is an Conseil
|
||||
$session = session();
|
||||
$user_id = $session->get('user');
|
||||
$data['is_admin'] = false;
|
||||
$data['isCommercial'] = false;
|
||||
$data['isChef'] = false;
|
||||
$data['isCaissier'] = false;
|
||||
$data['isMecanicien'] = false;
|
||||
$data['isSecurite'] = false;
|
||||
|
||||
if ($user_id['group_name'] == "Direction" || $user_id['group_name'] == "Conseil") {
|
||||
if ($user_id['group_name'] == "Direction" || $user_id['group_name'] == "DAF") {
|
||||
$data['is_admin'] = true;
|
||||
}
|
||||
|
||||
@ -203,6 +238,9 @@ class Dashboard extends AdminController
|
||||
if ($user_id['group_name'] == "MECANICIEN") {
|
||||
$data['isMecanicien'] = true;
|
||||
}
|
||||
if ($user_id['group_name'] == "Sécurité" || $user_id['group_name'] == "SECURITE") {
|
||||
$data['isSecurite'] = true;
|
||||
}
|
||||
|
||||
$data['page_title'] = 'Dashboard';
|
||||
$data['marques_total'] = json_encode($orderModel->getTotalProductvente());
|
||||
|
||||
@ -43,4 +43,25 @@ class NotificationController extends AdminController
|
||||
|
||||
$Notification->insertNotification($data);
|
||||
}
|
||||
|
||||
// Marquer toutes les notifications comme lues pour l'utilisateur connecté
|
||||
public function markAllAsRead()
|
||||
{
|
||||
$Notification = new Notification();
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
|
||||
// Mettre à jour toutes les notifications non lues pour ce store et ce groupe
|
||||
$builder = $Notification->builder();
|
||||
$builder->where('store_id', $users['store_id'])
|
||||
->groupStart()
|
||||
->where('forgroup', $users['group_name'])
|
||||
->orWhere('forgroup', strtolower('TOUS'))
|
||||
->groupEnd()
|
||||
->where('is_read', 0)
|
||||
->set(['is_read' => 1])
|
||||
->update();
|
||||
|
||||
return $this->response->setJSON(['status' => 'success']);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -82,7 +82,15 @@ class ProductCOntroller extends AdminController
|
||||
return "$name";
|
||||
}
|
||||
|
||||
$data = $Products->getProductData();
|
||||
// ✅ Utiliser la nouvelle méthode qui filtre automatiquement par rôle et store
|
||||
$data = $Products->getProductDataByRole();
|
||||
|
||||
// ✅ Debug : Logs pour vérifier le filtrage
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
log_message('debug', '=== fetchProductData ===');
|
||||
log_message('debug', 'User: ' . ($user['username'] ?? 'N/A') . ' (Group: ' . ($user['group_name'] ?? 'N/A') . ', Store: ' . ($user['store_id'] ?? 'N/A') . ')');
|
||||
log_message('debug', 'Products found: ' . count($data));
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
// Gestion du nom du magasin
|
||||
@ -139,7 +147,7 @@ class ProductCOntroller extends AdminController
|
||||
'<div class="no-image">Aucune image</div>';
|
||||
|
||||
$result['data'][$key] = [
|
||||
$imageHtml, // Correction : utiliser $imageHtml au lieu de $value['image']
|
||||
$imageHtml,
|
||||
convertString($value['sku']),
|
||||
$value['name'],
|
||||
$value['prix_vente'],
|
||||
|
||||
@ -4,13 +4,14 @@ use App\Models\Users;
|
||||
use App\Models\Avance;
|
||||
use App\Models\AlertMail;
|
||||
|
||||
/**
|
||||
* Vérifier les deadlines et envoyer des alertes email
|
||||
*/
|
||||
function checkDeadlineAlerts()
|
||||
{
|
||||
log_message('info', "=== DÉBUT checkDeadlineAlerts ===");
|
||||
|
||||
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt';
|
||||
|
||||
// On enlève la vérification de 24h pour s'assurer que le script tourne quotidiennement
|
||||
file_put_contents($cacheFile, time());
|
||||
|
||||
$avanceModel = new Avance();
|
||||
@ -20,15 +21,16 @@ function checkDeadlineAlerts()
|
||||
$today = date('Y-m-d');
|
||||
log_message('info', "Date du jour: {$today}");
|
||||
|
||||
// Modification pour vérifier les avances dans 0-3 jours
|
||||
// Récupération des avances dans 0-3 jours
|
||||
$avances = $avanceModel
|
||||
->where('DATE(deadline) >=', $today) // Inclut le jour même
|
||||
->where('DATE(deadline) >=', $today)
|
||||
->where('DATE(deadline) <=', date('Y-m-d', strtotime('+3 days')))
|
||||
->where('active', 1)
|
||||
->findAll();
|
||||
|
||||
log_message('info', "Nombre d'avances trouvées (0-3 jours): " . count($avances));
|
||||
|
||||
// Récupération des utilisateurs DAF
|
||||
$users = $usersModel->select('users.email, users.firstname, users.lastname')
|
||||
->join('user_group', 'user_group.user_id = users.id')
|
||||
->join('groups', 'groups.id = user_group.group_id')
|
||||
@ -56,7 +58,7 @@ function checkDeadlineAlerts()
|
||||
|
||||
log_message('info', "Avance ID: {$avance['avance_id']}, Deadline: {$deadline}, Jours restants: {$daysLeft}");
|
||||
|
||||
// Modification des types d'alerte pour 0, 1, 2, 3 jours
|
||||
// Détermination du type d'alerte
|
||||
$alertType = match($daysLeft) {
|
||||
3 => 'deadline_3_days',
|
||||
2 => 'deadline_2_days',
|
||||
@ -83,31 +85,35 @@ function checkDeadlineAlerts()
|
||||
continue;
|
||||
}
|
||||
|
||||
// Message modifié pour inclure le cas du jour même
|
||||
// Construction du message
|
||||
$urgencyText = $daysLeft === 0 ? "ÉCHÉANCE AUJOURD'HUI" : "{$daysLeft} jour(s) restant(s)";
|
||||
$message = "
|
||||
<h3>⚠️ URGENT : Avance approchant de la deadline</h3>
|
||||
<p><strong>ID Avance :</strong> {$avance['avance_id']}</p>
|
||||
<p><strong>Client :</strong> {$avance['customer_name']}</p>
|
||||
<p><strong>Montant avance :</strong> " . number_format($avance['avance_amount'], 0, ',', ' ') . " Ar</p>
|
||||
<p><strong>Montant dû :</strong> " . number_format($avance['amount_due'], 0, ',', ' ') . " Ar</p>
|
||||
<p><strong>Deadline :</strong> {$deadline}</p>
|
||||
<p><strong>Statut :</strong> <span style='color: red; font-weight: bold;'>{$urgencyText}</span></p>
|
||||
<p><strong>Téléphone client :</strong> {$avance['customer_phone']}</p>
|
||||
<p><strong>Adresse client :</strong> {$avance['customer_address']}</p>
|
||||
<hr>
|
||||
<p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p>
|
||||
<html>
|
||||
<body style='font-family: Arial, sans-serif; color: #333;'>
|
||||
<h3 style='color: #d9534f;'>⚠️ URGENT : Avance approchant de la deadline</h3>
|
||||
<p><strong>ID Avance :</strong> {$avance['avance_id']}</p>
|
||||
<p><strong>Client :</strong> {$avance['customer_name']}</p>
|
||||
<p><strong>Montant avance :</strong> " . number_format($avance['avance_amount'], 0, ',', ' ') . " Ar</p>
|
||||
<p><strong>Montant dû :</strong> " . number_format($avance['amount_due'], 0, ',', ' ') . " Ar</p>
|
||||
<p><strong>Deadline :</strong> {$deadline}</p>
|
||||
<p><strong>Statut :</strong> <span style='color: red; font-weight: bold;'>{$urgencyText}</span></p>
|
||||
<p><strong>Téléphone client :</strong> {$avance['customer_phone']}</p>
|
||||
<p><strong>Adresse client :</strong> {$avance['customer_address']}</p>
|
||||
<hr style='border: 1px solid #ddd;'>
|
||||
<p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
";
|
||||
|
||||
$emailsSent = 0;
|
||||
$subject = $daysLeft === 0
|
||||
? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI"
|
||||
: "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)";
|
||||
|
||||
foreach ($emails as $to) {
|
||||
log_message('info', "Tentative d'envoi email à: {$to}");
|
||||
|
||||
$subject = $daysLeft === 0
|
||||
? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI"
|
||||
: "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)";
|
||||
|
||||
if (sendEmailInBackground($to, $subject, $message)) {
|
||||
if (sendEmailWithBrevo($to, $subject, $message)) {
|
||||
$emailsSent++;
|
||||
log_message('info', "Email envoyé avec succès à: {$to}");
|
||||
} else {
|
||||
@ -115,6 +121,7 @@ function checkDeadlineAlerts()
|
||||
}
|
||||
}
|
||||
|
||||
// Enregistrement de l'alerte si au moins un email a été envoyé
|
||||
if ($emailsSent > 0) {
|
||||
log_message('info', "Insertion alerte pour avance_id={$avance['avance_id']} avec type {$alertType}");
|
||||
$alertMailModel->insert([
|
||||
@ -128,49 +135,182 @@ function checkDeadlineAlerts()
|
||||
log_message('error', "Aucun email envoyé pour avance_id={$avance['avance_id']} avec type {$alertType}");
|
||||
}
|
||||
}
|
||||
|
||||
checkAndConvertCompletedAvances();
|
||||
|
||||
log_message('info', "=== FIN checkDeadlineAlerts ===");
|
||||
|
||||
// ✅ NOUVELLE FONCTIONNALITÉ : Gérer les avances expirées
|
||||
handleExpiredAvances();
|
||||
}
|
||||
|
||||
function sendEmailInBackground($to, $subject, $message)
|
||||
/**
|
||||
* ✅ NOUVELLE FONCTION : Gérer automatiquement les avances expirées
|
||||
* - Libérer les produits (remettre en stock)
|
||||
* - Désactiver les avances
|
||||
*/
|
||||
function handleExpiredAvances()
|
||||
{
|
||||
log_message('info', "=== DÉBUT handleExpiredAvances ===");
|
||||
|
||||
$avanceModel = new Avance();
|
||||
$productsModel = new \App\Models\Products();
|
||||
|
||||
$today = date('Y-m-d');
|
||||
log_message('info', "Vérification des avances expirées au: {$today}");
|
||||
|
||||
// Récupérer les avances expirées et encore actives
|
||||
$expiredAvances = $avanceModel
|
||||
->where('DATE(deadline) <', $today)
|
||||
->where('active', 1)
|
||||
->where('is_order', 0)
|
||||
->findAll();
|
||||
|
||||
log_message('info', "Nombre d'avances expirées trouvées: " . count($expiredAvances));
|
||||
|
||||
if (empty($expiredAvances)) {
|
||||
log_message('info', "Aucune avance expirée à traiter");
|
||||
log_message('info', "=== FIN handleExpiredAvances ===");
|
||||
return;
|
||||
}
|
||||
|
||||
$processedCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
foreach ($expiredAvances as $avance) {
|
||||
try {
|
||||
log_message('info', "Traitement avance expirée ID: {$avance['avance_id']}, Client: {$avance['customer_name']}, Deadline: {$avance['deadline']}");
|
||||
|
||||
// ✅ Désactiver l'avance
|
||||
$updateResult = $avanceModel->update($avance['avance_id'], ['active' => 0]);
|
||||
|
||||
if (!$updateResult) {
|
||||
log_message('error', "Échec désactivation avance ID: {$avance['avance_id']}");
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
log_message('info', "Avance ID {$avance['avance_id']} désactivée avec succès");
|
||||
|
||||
// ✅ Libérer le produit UNIQUEMENT pour les avances "sur terre" avec product_id
|
||||
if ($avance['type_avance'] === 'terre' && !empty($avance['product_id'])) {
|
||||
$productUpdateResult = $productsModel->update($avance['product_id'], ['product_sold' => 0]);
|
||||
|
||||
if ($productUpdateResult) {
|
||||
log_message('info', "Produit ID {$avance['product_id']} libéré (remis en stock)");
|
||||
} else {
|
||||
log_message('warning', "Échec libération produit ID: {$avance['product_id']}");
|
||||
}
|
||||
} elseif ($avance['type_avance'] === 'mere') {
|
||||
log_message('info', "Avance 'sur mer' - Pas de produit à libérer (product_name: {$avance['product_name']})");
|
||||
} else {
|
||||
log_message('info', "Pas de product_id à libérer pour avance ID: {$avance['avance_id']}");
|
||||
}
|
||||
|
||||
$processedCount++;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "Erreur traitement avance expirée ID {$avance['avance_id']}: " . $e->getMessage());
|
||||
$errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log_message('info', "Avances expirées traitées: {$processedCount}, Erreurs: {$errorCount}");
|
||||
log_message('info', "=== FIN handleExpiredAvances ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Vérifier et convertir automatiquement les avances complètes en commandes
|
||||
* À appeler dans checkDeadlineAlerts() ou via un cron job
|
||||
*/
|
||||
function checkAndConvertCompletedAvances()
|
||||
{
|
||||
log_message('info', "=== DÉBUT checkAndConvertCompletedAvances ===");
|
||||
|
||||
$avanceModel = new \App\Models\Avance();
|
||||
|
||||
// Récupérer toutes les avances complètes non encore converties
|
||||
$completedAvances = $avanceModel
|
||||
->where('amount_due', 0)
|
||||
->where('is_order', 0) // Pas encore converties
|
||||
->where('active', 1) // Encore actives
|
||||
->findAll();
|
||||
|
||||
log_message('info', "Avances complètes trouvées : " . count($completedAvances));
|
||||
|
||||
$convertedCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
foreach ($completedAvances as $avance) {
|
||||
log_message('info', "Traitement avance complète ID: {$avance['avance_id']}, Client: {$avance['customer_name']}");
|
||||
|
||||
$order_id = $avanceModel->convertToOrder($avance['avance_id']);
|
||||
|
||||
if ($order_id) {
|
||||
$convertedCount++;
|
||||
log_message('info', "✅ Avance {$avance['avance_id']} convertie en commande {$order_id}");
|
||||
} else {
|
||||
$errorCount++;
|
||||
log_message('error', "❌ Échec conversion avance {$avance['avance_id']}");
|
||||
}
|
||||
}
|
||||
|
||||
log_message('info', "Avances converties : {$convertedCount}, Erreurs : {$errorCount}");
|
||||
log_message('info', "=== FIN checkAndConvertCompletedAvances ===");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Envoyer un email via Brevo
|
||||
*/
|
||||
function sendEmailWithBrevo($to, $subject, $message)
|
||||
{
|
||||
try {
|
||||
log_message('info', "Préparation envoi email à: {$to}");
|
||||
log_message('info', "Préparation envoi email via Brevo à: {$to}");
|
||||
|
||||
$email = \Config\Services::email();
|
||||
|
||||
// Configuration Brevo depuis le fichier .env
|
||||
$config = [
|
||||
'protocol' => 'smtp',
|
||||
'SMTPHost' => 'smtp.gmail.com',
|
||||
'SMTPUser' => 'rey342505@gmail.com',
|
||||
'SMTPPass' => 'loirqovmfuxnasrm',
|
||||
'SMTPPort' => 587,
|
||||
'SMTPCrypto' => 'tls',
|
||||
'mailType' => 'html',
|
||||
'charset' => 'utf-8',
|
||||
'newline' => "\r\n"
|
||||
'protocol' => env('email.protocol', 'smtp'),
|
||||
'SMTPHost' => env('email.SMTPHost', 'smtp-relay.brevo.com'),
|
||||
'SMTPUser' => env('email.SMTPUser'),
|
||||
'SMTPPass' => env('email.SMTPPass'),
|
||||
'SMTPPort' => env('email.SMTPPort', 587),
|
||||
'SMTPCrypto' => env('email.SMTPCrypto', 'tls'),
|
||||
'mailType' => 'html',
|
||||
'charset' => 'utf-8',
|
||||
'newline' => "\r\n",
|
||||
'wordWrap' => true,
|
||||
'validation' => true
|
||||
];
|
||||
|
||||
log_message('info', "Configuration Brevo - Host: {$config['SMTPHost']}, Port: {$config['SMTPPort']}, User: {$config['SMTPUser']}");
|
||||
|
||||
$email->initialize($config);
|
||||
|
||||
$email->setFrom('rey342505@gmail.com', 'Système Motorbike - Alertes Avances');
|
||||
// Utilisation de l'email configuré dans .env
|
||||
$fromEmail = env('email.fromEmail', 'noreply@motorbike.mg');
|
||||
$fromName = env('email.fromName', 'Système Motorbike - Alertes');
|
||||
|
||||
$email->setFrom($fromEmail, $fromName);
|
||||
$email->setTo($to);
|
||||
$email->setSubject($subject);
|
||||
$email->setMessage($message);
|
||||
|
||||
log_message('info', "Configuration email terminée, tentative d'envoi...");
|
||||
log_message('info', "Configuration email Brevo terminée, tentative d'envoi...");
|
||||
|
||||
if (!$email->send()) {
|
||||
$debugInfo = $email->printDebugger(['headers']);
|
||||
log_message('error', "Erreur email à {$to}: " . print_r($debugInfo, true));
|
||||
$debugInfo = $email->printDebugger(['headers', 'subject', 'body']);
|
||||
log_message('error', "Erreur email Brevo à {$to}: " . print_r($debugInfo, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
log_message('info', "Email envoyé avec succès à: {$to}");
|
||||
log_message('info', "Email envoyé avec succès via Brevo à: {$to}");
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "Exception email à {$to}: " . $e->getMessage());
|
||||
log_message('error', "Exception email Brevo à {$to}: " . $e->getMessage());
|
||||
log_message('error', "Stack trace: " . $e->getTraceAsString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -11,17 +11,15 @@ 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' // Ajout du champ type et deadline
|
||||
'type_avance','type_payment', 'deadline','commentaire','product_name'
|
||||
];
|
||||
|
||||
public function createAvance(array $data) {
|
||||
try {
|
||||
// Si la date de création n'est pas définie, on prend aujourd'hui
|
||||
if (empty($data['avance_date'])) {
|
||||
$data['avance_date'] = date('Y-m-d');
|
||||
}
|
||||
|
||||
// Calcul de la deadline en fonction du type
|
||||
if (!empty($data['type'])) {
|
||||
if (strtolower($data['type']) === 'avance sur terre') {
|
||||
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
|
||||
@ -43,7 +41,6 @@ class Avance extends Model {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Recalcul de la deadline si le type change
|
||||
if (!empty($data['type']) && !empty($data['avance_date'])) {
|
||||
if (strtolower($data['type']) === 'avance sur terre') {
|
||||
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
|
||||
@ -58,7 +55,6 @@ class Avance extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
// 📌 Le reste de tes fonctions restent inchangées
|
||||
public function getAllAvanceData(int $id=null) {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
@ -115,48 +111,47 @@ class Avance extends Model {
|
||||
}
|
||||
|
||||
public function fetchSingleAvance(int $avance_id){
|
||||
return $this->where('avance_id',$avance_id)->first();
|
||||
return $this->select('avances.*, products.name as product_name_db, products.prix_vente as product_price')
|
||||
->join('products', 'products.id = avances.product_id', 'left')
|
||||
->where('avances.avance_id', $avance_id)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function removeAvance(int $avance_id){
|
||||
return $this->delete($avance_id);
|
||||
}
|
||||
|
||||
// ✅ CORRECTION : getTotalAvance pour la caissière
|
||||
public function getTotalAvance() {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
if($isAdmin) {
|
||||
try {
|
||||
return $this->select('SUM(avance_amount) AS ta')
|
||||
->where('is_order', 0)
|
||||
->get()
|
||||
->getRowObject();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return $this->select('SUM(avance_amount) AS ta')
|
||||
->where('is_order', 0)
|
||||
->where('store_id',$users['store_id'])
|
||||
->get()
|
||||
->getRowObject();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
|
||||
return false;
|
||||
|
||||
try {
|
||||
$builder = $this->select('SUM(avance_amount) AS ta')
|
||||
->where('is_order', 0)
|
||||
->where('active', 1); // ✅ Ajout du filtre active
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']); // ✅ Filtre par store pour caissière
|
||||
}
|
||||
|
||||
return $builder->get()->getRowObject();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
|
||||
return (object) ['ta' => 0]; // ✅ Retourner un objet avec ta = 0 en cas d'erreur
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière
|
||||
public function getPaymentModesAvance()
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
if ($isAdmin) {
|
||||
return $this->db->table('avances')
|
||||
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,
|
||||
@ -164,9 +159,29 @@ class Avance extends Model {
|
||||
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
|
||||
')
|
||||
->where('active', 1)
|
||||
->get()
|
||||
->getRowObject();
|
||||
} else {
|
||||
->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());
|
||||
return (object) [
|
||||
'total' => 0,
|
||||
'total_mvola' => 0,
|
||||
@ -176,8 +191,6 @@ class Avance extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getAllAvanceData1(int $id=null) {
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
@ -221,7 +234,9 @@ class Avance extends Model {
|
||||
}
|
||||
try {
|
||||
return $this
|
||||
->where('is_order',0)
|
||||
->where('is_order',1) // ✅ Correction: devrait être 1, pas 0
|
||||
->where('active',1) // ✅ Ajout du filtre active
|
||||
->where('store_id',$users['store_id']) // ✅ Ajout du filtre store
|
||||
->orderBy('avance_date', 'DESC')
|
||||
->findAll();
|
||||
} catch (\Exception $e) {
|
||||
@ -297,70 +312,343 @@ class Avance extends Model {
|
||||
|
||||
foreach ($avances as $avance) {
|
||||
$this->update($avance['avance_id'], ['active' => '0']);
|
||||
$productModel->update($avance['product_id'], ['product_sold' => 0]);
|
||||
if (!empty($avance['product_id'])) { // ✅ Vérifier que product_id existe
|
||||
$productModel->update($avance['product_id'], ['product_sold' => 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Récupérer les avances qui arrivent à échéance dans X jours
|
||||
|
||||
public function getAvancesNearDeadline($days = 3)
|
||||
{
|
||||
$alertDate = date('Y-m-d', strtotime("+{$days} days"));
|
||||
|
||||
return $this->select('avances.*, users.store_id')
|
||||
->join('users', 'users.id = avances.user_id')
|
||||
->where('avances.is_order', 0)
|
||||
->where('avances.active', 1)
|
||||
->where('avances.amount_due >', 0)
|
||||
->where('DATE(avances.deadline)', $alertDate)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
public function getIncompleteAvances(int $id = null)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due >', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
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'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$builder->where('user_id', $id);
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
public function markAsPrinted($avance_id)
|
||||
{
|
||||
try {
|
||||
return $this->update($avance_id, [
|
||||
'is_printed' => 1
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur markAsPrinted: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marquer une avance comme non imprimée (quand elle est modifiée)
|
||||
*/
|
||||
public function getAvancesNearDeadline($days = 3)
|
||||
public function markAsNotPrinted($avance_id)
|
||||
{
|
||||
$alertDate = date('Y-m-d', strtotime("+{$days} days"));
|
||||
try {
|
||||
return $this->update($avance_id, [
|
||||
'is_printed' => 0,
|
||||
'last_modified_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur markAsNotPrinted: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si une avance a déjà été imprimée
|
||||
*/
|
||||
public function isPrinted($avance_id)
|
||||
{
|
||||
try {
|
||||
$avance = $this->find($avance_id);
|
||||
return $avance ? (bool)$avance['is_printed'] : false;
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur isPrinted: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Récupérer un produit avec le nom de sa marque
|
||||
* @param int $product_id
|
||||
* @return array|null
|
||||
*/
|
||||
public function getProductWithBrand($product_id)
|
||||
{
|
||||
try {
|
||||
return $this->select('products.*, brands.name as brand_name')
|
||||
->join('brands', 'brands.id = products.marque', 'left')
|
||||
->where('products.id', $product_id)
|
||||
->first();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Erreur getProductWithBrand: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// À ajouter dans App\Models\Avance.php
|
||||
|
||||
/**
|
||||
* ✅ Convertir une avance complète en commande
|
||||
* Appelé automatiquement quand amount_due atteint 0
|
||||
*
|
||||
* @param int $avance_id
|
||||
* @return int|false ID de la commande créée ou false
|
||||
*/
|
||||
public function convertToOrder(int $avance_id)
|
||||
{
|
||||
try {
|
||||
$avance = $this->find($avance_id);
|
||||
|
||||
if (!$avance) {
|
||||
log_message('error', "Avance introuvable : {$avance_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ MODIFICATION PRINCIPALE : Vérifier que c'est bien une avance sur TERRE
|
||||
if ($avance['type_avance'] !== 'terre') {
|
||||
log_message('info', "Avance {$avance_id} de type '{$avance['type_avance']}' - Conversion ignorée (seules les avances TERRE sont converties)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ Vérifier que l'avance est bien complète
|
||||
if ((float)$avance['amount_due'] > 0) {
|
||||
log_message('warning', "Avance TERRE {$avance_id} non complète (amount_due={$avance['amount_due']})");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ Vérifier qu'elle n'a pas déjà été convertie
|
||||
if ((int)$avance['is_order'] === 1) {
|
||||
log_message('info', "Avance TERRE {$avance_id} déjà convertie en commande");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ Vérifier que le produit existe (obligatoire pour avance TERRE)
|
||||
if (empty($avance['product_id'])) {
|
||||
log_message('error', "Avance TERRE {$avance_id} sans product_id - Impossible de convertir");
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$db->transStart();
|
||||
|
||||
// ✅ 1. Créer la commande
|
||||
$orderModel = new \App\Models\Orders();
|
||||
$bill_no = 'BILAV-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
|
||||
|
||||
$orderData = [
|
||||
'bill_no' => $bill_no,
|
||||
'customer_name' => $avance['customer_name'],
|
||||
'customer_address' => $avance['customer_address'],
|
||||
'customer_phone' => $avance['customer_phone'],
|
||||
'customer_cin' => $avance['customer_cin'],
|
||||
'date_time' => date('Y-m-d H:i:s'),
|
||||
'gross_amount' => $avance['gross_amount'],
|
||||
'net_amount' => $avance['gross_amount'],
|
||||
'discount' => 0,
|
||||
'paid_status' => 2, // En attente validation caissière
|
||||
'user_id' => $avance['user_id'],
|
||||
'store_id' => $avance['store_id'],
|
||||
'service_charge_rate' => 0,
|
||||
'vat_charge_rate' => 0,
|
||||
'vat_charge' => 0,
|
||||
'tranche_1' => $avance['avance_amount'],
|
||||
'tranche_2' => null,
|
||||
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
|
||||
'order_payment_mode_1' => null,
|
||||
];
|
||||
|
||||
$order_id = $orderModel->insert($orderData);
|
||||
|
||||
if (!$order_id) {
|
||||
throw new \Exception("Échec création commande pour avance TERRE {$avance_id}");
|
||||
}
|
||||
|
||||
log_message('info', "✅ Commande {$bill_no} créée depuis avance TERRE {$avance_id}");
|
||||
|
||||
// ✅ 2. Créer les items de commande
|
||||
$orderItemModel = new \App\Models\OrderItems();
|
||||
$productModel = new \App\Models\Products();
|
||||
|
||||
$product = $productModel->find($avance['product_id']);
|
||||
|
||||
if ($product) {
|
||||
$orderItemModel->insert([
|
||||
'order_id' => $order_id,
|
||||
'product_id' => $avance['product_id'],
|
||||
'rate' => $avance['gross_amount'],
|
||||
'qty' => 1,
|
||||
'amount' => $avance['gross_amount'],
|
||||
]);
|
||||
|
||||
log_message('info', "Item ajouté : produit {$avance['product_id']} (TERRE)");
|
||||
|
||||
// ✅ Le produit reste marqué comme vendu (product_sold = 1)
|
||||
// Il sera géré dans la commande maintenant
|
||||
} else {
|
||||
log_message('warning', "Produit {$avance['product_id']} introuvable pour avance TERRE {$avance_id}");
|
||||
}
|
||||
|
||||
// ✅ 3. Marquer l'avance comme convertie
|
||||
$this->update($avance_id, [
|
||||
'is_order' => 1,
|
||||
'active' => 0, // Désactiver l'avance (elle devient commande)
|
||||
]);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
if ($db->transStatus() === false) {
|
||||
log_message('error', "Transaction échouée pour avance TERRE {$avance_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ 4. Notification à la caissière
|
||||
$notificationController = new \App\Controllers\NotificationController();
|
||||
$notificationController->createNotification(
|
||||
"Nouvelle commande issue d'une avance TERRE complète - {$bill_no}",
|
||||
"Caissière",
|
||||
(int)$avance['store_id'],
|
||||
"orders"
|
||||
);
|
||||
|
||||
log_message('info', "✅ Avance TERRE {$avance_id} convertie en commande {$order_id} ({$bill_no})");
|
||||
|
||||
return $order_id;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "Erreur conversion avance TERRE→commande : " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Hook appelé automatiquement lors du paiement d'une avance
|
||||
* Intégrer ceci dans votre fonction de paiement existante
|
||||
*/
|
||||
public function afterPayment(int $avance_id)
|
||||
{
|
||||
$avance = $this->find($avance_id);
|
||||
|
||||
return $this->select('avances.*, users.store_id')
|
||||
->join('users', 'users.id = avances.user_id')
|
||||
->where('avances.is_order', 0)
|
||||
->where('avances.active', 1)
|
||||
->where('avances.amount_due >', 0)
|
||||
->where('DATE(avances.deadline)', $alertDate)
|
||||
->findAll();
|
||||
if (!$avance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ✅ Si l'avance est maintenant complète ET que c'est une avance TERRE
|
||||
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'terre') {
|
||||
log_message('info', "💰 Avance TERRE {$avance_id} complète ! Conversion automatique en commande...");
|
||||
return $this->convertToOrder($avance_id);
|
||||
}
|
||||
|
||||
// ✅ Si c'est une avance MER complète, on ne fait rien (elle reste dans la liste)
|
||||
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'mere') {
|
||||
log_message('info', "💰 Avance MER {$avance_id} complète ! Elle reste dans la liste des avances.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Avances incomplètes (reste à payer > 0 et non transformées en commande)
|
||||
public function getIncompleteAvances(int $id = null)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
/**
|
||||
* ✅ Générer un numéro de facture unique
|
||||
*/
|
||||
private function generateBillNumber($store_id)
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Récupérer le dernier numéro pour ce store
|
||||
$query = $db->query(
|
||||
"SELECT bill_no FROM orders WHERE store_id = ? ORDER BY id DESC LIMIT 1",
|
||||
[$store_id]
|
||||
);
|
||||
|
||||
$result = $query->getRow();
|
||||
|
||||
if ($result && preg_match('/(\d+)$/', $result->bill_no, $matches)) {
|
||||
$lastNumber = intval($matches[1]);
|
||||
$newNumber = $lastNumber + 1;
|
||||
} else {
|
||||
$newNumber = 1;
|
||||
}
|
||||
|
||||
// Format: BILL-STORE{store_id}-{number}
|
||||
return 'BILL-STORE' . $store_id . '-' . str_pad($newNumber, 5, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ Récupérer toutes les avances complètes non converties
|
||||
*/
|
||||
public function getCompletedNotConverted()
|
||||
{
|
||||
return $this->where('amount_due', 0)
|
||||
->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due >', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
->where('type_avance', 'terre') // ✅ Uniquement TERRE à convertir
|
||||
->findAll();
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$builder->where('user_id', $id);
|
||||
/**
|
||||
* ✅ NOUVELLE MÉTHODE : Récupérer les avances MER complètes (pour statistiques)
|
||||
*/
|
||||
public function getCompletedMerAvances()
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('amount_due', 0)
|
||||
->where('active', 1)
|
||||
->where('type_avance', 'mere');
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
// Avances complètes (reste à payer = 0 et non transformées en commande)
|
||||
public function getCompletedAvances(int $id = null)
|
||||
{
|
||||
$session = session();
|
||||
$users = $session->get('user');
|
||||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('is_order', 0)
|
||||
->where('active', 1)
|
||||
->where('amount_due', 0);
|
||||
|
||||
if (!$isAdmin) {
|
||||
$builder->where('store_id', $users['store_id']);
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
$builder->where('user_id', $id);
|
||||
}
|
||||
|
||||
return $builder->orderBy('avance_date', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,14 +6,46 @@ use CodeIgniter\Model;
|
||||
|
||||
class Products extends Model
|
||||
{
|
||||
/**
|
||||
* table products
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'products';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = ['name', 'sku', 'price', 'product_sold', 'qty', 'image', 'description', 'numero_de_moteur', 'marque', 'chasis', 'store_id', 'availability', 'is_piece', 'prix_vente', 'date_arivage', 'puissance', 'cler', 'categorie_id', 'etats','type', 'infoManquekit', 'info', 'infoManque'];
|
||||
|
||||
/**
|
||||
* ✅ NOUVELLE MÉTHODE : Récupérer les produits selon le rôle et le store de l'utilisateur
|
||||
* @param int|null $id
|
||||
* @return array|object|null
|
||||
*/
|
||||
public function getProductDataByRole(int $id = null)
|
||||
{
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
// Vérifier si l'utilisateur est admin (Conseil ou Direction)
|
||||
$isAdmin = in_array($user['group_name'], ['Conseil', 'Direction']);
|
||||
|
||||
$builder = $this->where('is_piece', 0)
|
||||
->where('product_sold', 0);
|
||||
|
||||
// ✅ Si pas admin ET a un store_id valide, filtrer par son magasin
|
||||
if (!$isAdmin) {
|
||||
// ✅ Si l'utilisateur n'a pas de store_id (NULL ou 0), ne retourner aucun produit
|
||||
if (empty($user['store_id']) || $user['store_id'] == 0) {
|
||||
// Retourner une requête impossible pour avoir 0 résultats
|
||||
$builder->where('id', -1);
|
||||
} else {
|
||||
// Filtrer par le store_id de l'utilisateur
|
||||
$builder->where('store_id', $user['store_id']);
|
||||
}
|
||||
}
|
||||
|
||||
// Si un ID spécifique est demandé
|
||||
if ($id) {
|
||||
return $builder->where('id', $id)->first();
|
||||
}
|
||||
|
||||
return $builder->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the brand data
|
||||
* @param int $id
|
||||
@ -22,7 +54,6 @@ class Products extends Model
|
||||
public function getProductData(int $id = null)
|
||||
{
|
||||
if ($id) {
|
||||
|
||||
return $this->where('id', $id)->first();
|
||||
}
|
||||
|
||||
@ -32,17 +63,12 @@ class Products extends Model
|
||||
])->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
|
||||
public function getProductData2(int $id)
|
||||
{
|
||||
$builder = $this->where('is_piece', 0)
|
||||
->where('product_sold', 0)
|
||||
->where('store_id', $id);
|
||||
|
||||
// if ($id != 0) {
|
||||
// $builder = $builder->where('store_id', $id);
|
||||
// }
|
||||
|
||||
return $builder->join('brands', 'brands.id = products.marque')
|
||||
->orderBy('products.id', 'DESC')
|
||||
->select('brands.name as brand_name,COUNT( products.id) as total_product, products.store_id as store_id,products.*')
|
||||
@ -50,15 +76,12 @@ class Products extends Model
|
||||
->findAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getProductData3(int $id)
|
||||
{
|
||||
if ($id == 0) {
|
||||
return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
// Fetch all products, ordered by ID descending
|
||||
return $this->where('is_piece', 0)->where('product_sold', 0)->where('store_id', $id)->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
@ -80,37 +103,21 @@ class Products extends Model
|
||||
$builder->where("id NOT IN ($subQuery)", null, false);
|
||||
}
|
||||
|
||||
// Si on modifie et qu'on veut inclure le produit actuel dans la liste
|
||||
if ($currentProductId) {
|
||||
$builder->orWhere('id', $currentProductId);
|
||||
}
|
||||
|
||||
return $builder->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get active products (availability = 1)
|
||||
* @return array
|
||||
*/
|
||||
public function getActiveProductData()
|
||||
{
|
||||
return $this->where('is_piece', 0)->orderBy('id', 'DESC')->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigner un utilisateur à un magasin
|
||||
*
|
||||
* @param int|null $productid ID de l'utilisateur
|
||||
* @param int|null $storeid ID du magasin
|
||||
* @return bool Résultat de l'opération (true si success, false sinon)
|
||||
*/
|
||||
public function assignToStore($productid = null, $storeid = null)
|
||||
{
|
||||
// Vérifie si l'utilisateur et le magasin sont fournis
|
||||
if (!is_null($productid) && !is_null($storeid)) {
|
||||
// Mise à jour du champ store_id pour l'utilisateur spécifié
|
||||
$this->db->table('products')
|
||||
->where('id', $productid)
|
||||
->update(['store_id' => $storeid]);
|
||||
@ -118,36 +125,19 @@ class Products extends Model
|
||||
return true;
|
||||
}
|
||||
|
||||
// Si $userid ou $storeid est null, l'opération échoue
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* create new product
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $data)
|
||||
{
|
||||
return $this->insert($data) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* update existing product
|
||||
* @param array $data
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function updateProduct(array $data, int $id)
|
||||
{
|
||||
return $this->update($id, $data) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove existing product
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove(int $id)
|
||||
{
|
||||
return $this->delete($id) ? true : false;
|
||||
@ -157,23 +147,18 @@ class Products extends Model
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Sous-requête pour obtenir les product_id dans avances actives
|
||||
$subQuery = $db->table('avances')
|
||||
->select('product_id')
|
||||
->where('active', 1)
|
||||
->where('is_order', 0)
|
||||
->getCompiledSelect();
|
||||
|
||||
// Compter les produits disponibles
|
||||
return $this->where('is_piece', 0)
|
||||
->where('product_sold', 0)
|
||||
->where("id NOT IN ($subQuery)", null, false)
|
||||
->countAllResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* count all products including sold and reserved (méthode originale si besoin)
|
||||
*/
|
||||
|
||||
public function countAllProductsIncludingSold()
|
||||
{
|
||||
return $this->countAll();
|
||||
@ -185,7 +170,6 @@ class Products extends Model
|
||||
$total = 0.0;
|
||||
|
||||
foreach ($productIds as $id) {
|
||||
// Récupère le prix du produit courant
|
||||
$row = $this->select('price')
|
||||
->where('id', $id)
|
||||
->first();
|
||||
@ -197,7 +181,6 @@ class Products extends Model
|
||||
|
||||
return $total;
|
||||
} catch (\Throwable $th) {
|
||||
// Loger l’erreur ici si besoin : log_message('error', $th->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -207,12 +190,45 @@ class Products extends Model
|
||||
$product = $this->where('id', $id)->first();
|
||||
|
||||
if ($product && isset($product['name'])) {
|
||||
return $product['name']; // ou un autre champ selon le vrai nom
|
||||
return $product['name'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Compter les produits par store selon le rôle de l'utilisateur
|
||||
* @return int
|
||||
*/
|
||||
public function countProductsByUserStore()
|
||||
{
|
||||
$session = session();
|
||||
$user = $session->get('user');
|
||||
|
||||
// Vérifier si l'utilisateur est admin
|
||||
$isAdmin = in_array($user['group_name'], ['DAF', 'Direction']);
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Sous-requête pour exclure les produits en avance
|
||||
$subQuery = $db->table('avances')
|
||||
->select('product_id')
|
||||
->where('active', 1)
|
||||
->where('is_order', 0)
|
||||
->getCompiledSelect();
|
||||
|
||||
$builder = $this->where('is_piece', 0)
|
||||
->where('product_sold', 0)
|
||||
->where("id NOT IN ($subQuery)", null, false);
|
||||
|
||||
// Si pas admin ET a un store_id valide, filtrer par son magasin
|
||||
if (!$isAdmin && !empty($user['store_id']) && $user['store_id'] != 0) {
|
||||
$builder->where('store_id', $user['store_id']);
|
||||
} elseif (!$isAdmin) {
|
||||
// Utilisateur sans store = 0 produits
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $builder->countAllResults();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -54,6 +54,10 @@
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<!-- Small boxes (Stat box) -->
|
||||
@ -709,128 +713,175 @@
|
||||
|
||||
|
||||
<?php if ($isCaissier === true): ?>
|
||||
<!-- <div class="content-wrapper"> -->
|
||||
<!-- <h5>Votre statistique de vente</h5> -->
|
||||
<!-- performance content wraper: <div class="content-wrapper"> -->
|
||||
<div class=" container-fluid row">
|
||||
<!-- total en caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<!-- small box -->
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale CAISSE</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-credit-card"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Section des totaux caisse -->
|
||||
<div class="container-fluid row">
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_caisse au lieu de total -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale CAISSE</p>
|
||||
</div>
|
||||
<!-- total mvola -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<!-- small box -->
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_mvola, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale MVOLA</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-mobile-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-credit-card"></i>
|
||||
</div>
|
||||
<!-- Total en espece -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<!-- small box -->
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_espece, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale en espece</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa fa-usd"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_mvola_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_mvola_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale MVOLA</p>
|
||||
</div>
|
||||
<!-- Total en virement bancaire -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<!-- small box -->
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_virement_bancaire, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale en banque</p>
|
||||
<div class="icon">
|
||||
<i class="fa fa-mobile-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_espece_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_espece_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale en espèce</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-usd"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ MODIFIÉ : Utiliser total_vb_caisse -->
|
||||
<div class="col-lg-3 col-xs-6">
|
||||
<div class="small-box" style="background-color: #A9A9A9;">
|
||||
<div class="inner">
|
||||
<h2><?php echo number_format($total_vb_caisse, 0, '.', ' '); ?>Ar</h2>
|
||||
<p>Totale en banque</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-credit-card-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU : Afficher le 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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ NOUVEAU : 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">
|
||||
<div class="box box-info">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title"><i class="fa fa-info-circle"></i> Détail des Avances par Mode de Paiement</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header"><?php echo number_format($total_avances_mvola, 0, '.', ' '); ?> Ar</h5>
|
||||
<span class="description-text">MVOLA</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-credit-card-alt"></i>
|
||||
<div class="col-sm-4">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header"><?php echo number_format($total_avances_espece, 0, '.', ' '); ?> Ar</h5>
|
||||
<span class="description-text">ESPÈCE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="description-block">
|
||||
<h5 class="description-header"><?php echo number_format($total_avances_virement, 0, '.', ' '); ?> Ar</h5>
|
||||
<span class="description-text">BANQUE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="content-header">
|
||||
<h1> Rapport de Performance du Caissier</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#"><i class="fa fa-home"></i> Accueil</a></li>
|
||||
<li class="active" onclick="window.history.back()" style="cursor: pointer;"> Rapports</li>
|
||||
</ol>
|
||||
</section>
|
||||
<h1>Rapport de Performance du Caissier</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#"><i class="fa fa-home"></i> Accueil</a></li>
|
||||
<li class="active" onclick="window.history.back()" style="cursor: pointer;">Rapports</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<!-- Product Details -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12 col-lg-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer
|
||||
🔍</button>
|
||||
<button id="ExportBTN1" class="btn btn-success w-100">Exporter
|
||||
📤</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="caissierperf" class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="me">MVOLA & Espèce</th>
|
||||
<th id="bm">Banque & MVOLA</th>
|
||||
<th id="be">Banque & Espèce</th>
|
||||
<th id="mb">MVOLA & Banque</th>
|
||||
<th id="mr">Montant total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<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-body">
|
||||
<div class="row g-3 align-items-center mb-4" style="margin: 5px 0 5px 5px;">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Date de début</label>
|
||||
<input type="date" id="startDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">Date de fin</label>
|
||||
<input type="date" id="endDate" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<br>
|
||||
<button id="filteredB1" class="btn btn-primary w-100">Filtrer 🔍</button>
|
||||
<button id="ExportBTN1" class="btn btn-success w-100">Exporter 📤</button>
|
||||
</div>
|
||||
</div>
|
||||
<table id="caissierperf" class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="me">MVOLA & Espèce</th>
|
||||
<th id="bm">Banque & MVOLA</th>
|
||||
<th id="be">Banque & Espèce</th>
|
||||
<th id="mb">MVOLA & Banque</th>
|
||||
<th id="mr">Montant total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div style="width: 80%; margin: auto;">
|
||||
<canvas id="salesChart"></canvas>
|
||||
</div>
|
||||
<!-- /.content -->
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="width: 80%; margin: auto;">
|
||||
<canvas id="salesChart"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script>
|
||||
@ -909,6 +960,171 @@
|
||||
</script>
|
||||
<!-- </div> -->
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- securite -->
|
||||
|
||||
<!-- Dashboard pour Sécurité -->
|
||||
<!-- Dashboard pour Sécurité -->
|
||||
<?php if ($isSecurite === true): ?>
|
||||
<!-- Styles spécifiques pour Sécurité -->
|
||||
<style>
|
||||
.security-dashboard {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.security-dashboard .content-header {
|
||||
margin: 0 !important;
|
||||
padding: 15px !important;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.security-dashboard .content {
|
||||
margin: 0 !important;
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.security-dashboard .row {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.security-dashboard .col-lg-3,
|
||||
.security-dashboard .col-md-4,
|
||||
.security-dashboard .col-sm-6,
|
||||
.security-dashboard .col-xs-12 {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 15px !important;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box {
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.3s ease;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0px 6px 15px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.security-dashboard .small-box .inner {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box .inner h3 {
|
||||
font-size: 38px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box .inner p {
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box .icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 70px;
|
||||
opacity: 0.3;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box-footer {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
margin-top: 10px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.security-dashboard .small-box-footer:hover {
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.security-dashboard .bg-aqua {
|
||||
background: linear-gradient(135deg, #00c0ef 0%, #0099cc 100%);
|
||||
}
|
||||
|
||||
.security-dashboard .box {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.security-dashboard .box-header.with-border {
|
||||
border-bottom: 2px solid #3c8dbc;
|
||||
}
|
||||
|
||||
.security-dashboard .box-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.security-dashboard .col-md-12 {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<h3><?php echo $total_products ?></h3>
|
||||
<p>Total Produits</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-bag"></i>
|
||||
</div>
|
||||
<a href="<?php echo base_url('products/') ?>" class="small-box-footer">
|
||||
Plus d'information <i class="fa fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
<!-- Section informative pour la sécurité -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">
|
||||
<i class="fa fa-shield"></i> Informations Sécurité
|
||||
</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<p>
|
||||
<strong>Bienvenue sur votre tableau de bord sécurité.</strong>
|
||||
</p>
|
||||
<p>
|
||||
Vous avez accès à la consultation du nombre total de produits en stock.
|
||||
Pour plus de détails, cliquez sur "Plus d'information" dans la carte ci-dessus.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<!-- mecanicien -->
|
||||
@ -972,6 +1188,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
<script>
|
||||
|
||||
@ -1,96 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Log in</title>
|
||||
<!-- Tell the browser to be responsive to screen width -->
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<title>Connexion | Motorbike</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') ?>">
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/font-awesome/css/font-awesome.min.css') ?>">
|
||||
|
||||
<!-- Ionicons -->
|
||||
<link rel="stylesheet" href="<?php echo base_url('assets/bower_components/Ionicons/css/ionicons.min.css') ?>">
|
||||
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="<?php echo base_url('assets/dist/css/AdminLTE.min.css') ?>">
|
||||
<!-- iCheck -->
|
||||
<link rel="stylesheet" href="<?php echo base_url('assets/plugins/iCheck/square/blue.css') ?>">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<!-- Google Font -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
background: linear-gradient(135deg, #e9ecef, #dee2e6);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
display: flex;
|
||||
width: 720px;
|
||||
max-width: 95%;
|
||||
background: #fff;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
|
||||
animation: fadeIn 0.8s ease;
|
||||
}
|
||||
|
||||
/* Partie gauche : image moto */
|
||||
.login-image {
|
||||
flex: 1;
|
||||
background: url('https://lh3.googleusercontent.com/p/AF1QipN4iewRbD9iIfbsvyPTD2SGUkxyi952uG30pHD9=s1360-w1360-h1020') center/cover no-repeat;
|
||||
/* Alternative possible :
|
||||
https://images.unsplash.com/photo-1520975661595-6453be3f7070?auto=format&fit=crop&w=1000&q=80
|
||||
(casque de moto)
|
||||
*/
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-image::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.login-image h1 {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 1.8em;
|
||||
font-weight: 600;
|
||||
color: #ffcc00;
|
||||
}
|
||||
|
||||
.login-image p {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 1em;
|
||||
max-width: 250px;
|
||||
margin: 10px auto 0;
|
||||
line-height: 1.4;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
/* Partie droite : formulaire */
|
||||
.login-form {
|
||||
flex: 1;
|
||||
padding: 35px 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-form h2 {
|
||||
font-weight: 600;
|
||||
margin-bottom: 25px;
|
||||
color: #007bff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.input-group .form-control {
|
||||
border-radius: 30px;
|
||||
padding: 10px 20px;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
border-radius: 30px 0 0 30px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ccc;
|
||||
border-right: none;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
border-radius: 30px;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-weight: 600;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.checkbox label {
|
||||
font-weight: 400;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-card {
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
}
|
||||
.login-image {
|
||||
height: 160px;
|
||||
}
|
||||
.login-form {
|
||||
padding: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="hold-transition login-page">
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<a href=""><b>Login</b></a>
|
||||
</div>
|
||||
<!-- /.login-logo -->
|
||||
<div class="login-box-body">
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= session()->getFlashdata('error') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<body>
|
||||
<div class="login-card">
|
||||
|
||||
<form action="<?= base_url('login') ?>" method="post">
|
||||
<div class="form-group has-feedback">
|
||||
<input type="email" class="form-control" name="email" id="email" placeholder="Email" autocomplete="off">
|
||||
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
|
||||
<!-- Image + message -->
|
||||
<div class="login-image">
|
||||
<div>
|
||||
<h1>Bienvenue chez Motorbike</h1>
|
||||
<p>Gérez vos avances et opérations avec passion pour la moto.</p>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="password" class="form-control" name="password" id="password" placeholder="Password" autocomplete="off">
|
||||
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<div class="checkbox icheck">
|
||||
<label>
|
||||
<input type="checkbox"> Remember Me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire -->
|
||||
<div class="login-form">
|
||||
<h2>Connexion</h2>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= session()->getFlashdata('error') ?>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col-xs-4">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-flat">Sign In</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?= base_url('login') ?>" method="post">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-envelope"></i></span>
|
||||
<input type="email" class="form-control" name="email" placeholder="Email" required>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
|
||||
<input type="password" class="form-control" name="password" placeholder="Mot de passe" required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox"> Se souvenir de moi</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">Se connecter</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.login-box-body -->
|
||||
</div>
|
||||
<!-- /.login-box -->
|
||||
|
||||
<!-- jQuery 3 -->
|
||||
<script src="<?php echo base_url('assets/bower_components/jquery/dist/jquery.min.js') ?>"></script>
|
||||
<!-- Bootstrap 3.3.7 -->
|
||||
<script src="<?php echo base_url('assets/bower_components/bootstrap/dist/js/bootstrap.min.js') ?>"></script>
|
||||
<!-- iCheck -->
|
||||
<script src="<?php echo base_url('assets/plugins/iCheck/icheck.min.js') ?>"></script>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$('input').iCheck({
|
||||
checkboxClass: 'icheckbox_square-blue',
|
||||
radioClass: 'iradio_square-blue',
|
||||
increaseArea: '20%' // optional
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="<?php echo base_url('assets/bower_components/jquery/dist/jquery.min.js') ?>"></script>
|
||||
<script src="<?php echo base_url('assets/bower_components/bootstrap/dist/js/bootstrap.min.js') ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,4 +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>
|
||||
@ -27,21 +33,21 @@
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($errors = session()->getFlashdata('errors')): ?>
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<?php if (is_array($errors)): ?>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= esc($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<?= esc($errors) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<?php if (is_array($errors)): ?>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= esc($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<?= esc($errors) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Ajouter une commande</h3>
|
||||
@ -53,7 +59,6 @@
|
||||
<?= $validation->listErrors() ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
<div class="box-body">
|
||||
<div class="form-group">
|
||||
@ -99,10 +104,10 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label"
|
||||
<label for="customer_cin" class="col-sm-5 control-label"
|
||||
style="text-align:left;">CIN du client</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="customer_phone" name="customer_cin"
|
||||
<input type="text" class="form-control" id="customer_cin" name="customer_cin"
|
||||
value="<?= old('customer_cin') ?>" required placeholder="Entrer le CIN du client"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
@ -113,11 +118,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50%">Produit</th>
|
||||
<!-- <th style="width:10%">Quantité</th> -->
|
||||
<th style="width:10%">Prix unitaire</th>
|
||||
<th style="width:20%">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>
|
||||
<tbody>
|
||||
@ -134,15 +136,14 @@
|
||||
|
||||
<td>
|
||||
<input type="text" name="rate[]" id="rate_1" class="form-control" disabled
|
||||
autocomplete="off" value="<?= $pu ?>">
|
||||
autocomplete="off" value="<?= $pu ?>" min="0">
|
||||
<input type="hidden" name="rate_value[]" value="<?= $pu ?>"
|
||||
id="rate_value_1" class="form-control" autocomplete="off">
|
||||
<input type="hidden" id="min_price_1" name="min_price[]" value="">
|
||||
<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">
|
||||
disabled autocomplete="off" min="0">
|
||||
<input type="hidden" name="amount_value[]" id="amount_value_1"
|
||||
class="form-control" autocomplete="off">
|
||||
</td>
|
||||
@ -151,47 +152,40 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="hidden" name="qty[]" value="1" id="qty_1" max="<//?= $totalqtt ?>"
|
||||
<input type="hidden" name="qty[]" value="1" id="qty_1" min="1"
|
||||
class="form-control valueqty" required onkeyup="getTotal(1)">
|
||||
<br /> <br />
|
||||
<div class="col-md-6 col-xs-12 pull pull-right">
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label">Montant brut</label>
|
||||
<label for="gross_amount" class="col-sm-5 control-label">Prix affiché</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="gross_amount" name="gross_amount"
|
||||
disabled autocomplete="off">
|
||||
disabled autocomplete="off" min="0">
|
||||
<input type="hidden" class="form-control" id="gross_amount_value"
|
||||
name="gross_amount_value" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="discount" class="col-sm-5 control-label">Rabais</label>
|
||||
<label for="discount" class="col-sm-5 control-label">Prix demandé</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="discount" name="discount" placeholder="Discount"
|
||||
onkeyup="subAmount()" autocomplete="off">
|
||||
</div>
|
||||
<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>
|
||||
<input type="number" class="form-control numeric-input" id="discount" name="discount"
|
||||
placeholder="Prix demandé" min="0" step="0.01"
|
||||
onkeyup="subAmount()" oninput="validatePrixDemande(this)" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="net_amount" class="col-sm-5 control-label">Montant Net</label>
|
||||
<label for="net_amount" class="col-sm-5 control-label">Remise</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="hidden" class="form-control" id="net_amount_value"
|
||||
name="net_amount" hidden autocomplete="off">
|
||||
<input type="text" class="form-control" id="net_amount" name="net_amount"
|
||||
disabled autocomplete="off">
|
||||
disabled autocomplete="off" min="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="form-group">
|
||||
<label for="paid_status" class="col-sm-5 control-label">Mode de paiement</label>
|
||||
<div class="col-sm-7">
|
||||
<select type="text" class="form-control" id="payment_mode" name="payment_mode">
|
||||
<option value="1">MVOLA</option>
|
||||
<option value="2">Virement Bancaire</option>
|
||||
<option value="3">En espece</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="box-footer">
|
||||
<input type="hidden" name="service_charge_rate"
|
||||
value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off">
|
||||
@ -208,9 +202,71 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var base_url = "<?php echo base_url(); ?>";
|
||||
|
||||
// ✅ Fonction pour valider les nombres positifs
|
||||
function validatePositiveNumber(input) {
|
||||
let value = parseFloat(input.value);
|
||||
|
||||
// Si la valeur est négative ou NaN, on la remet à 0
|
||||
if (isNaN(value) || value < 0) {
|
||||
input.value = '';
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
|
||||
// Vérifier si négatif
|
||||
if (isNaN(prixDemande) || prixDemande < 0) {
|
||||
input.value = '';
|
||||
showDiscountError('Le prix demandé ne peut pas être négatif.');
|
||||
setTimeout(hideDiscountError, 3000);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier si supérieur au prix affiché
|
||||
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;
|
||||
}
|
||||
|
||||
// Si tout est valide, masquer l'erreur
|
||||
hideDiscountError();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ✅ Fonction pour empêcher la saisie de caractères négatifs
|
||||
function preventNegativeInput(e) {
|
||||
// Empêche la saisie du signe moins (-)
|
||||
if (e.key === '-' || e.key === 'e' || e.key === 'E' || e.key === '+') {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
getTotal(1);
|
||||
$(".select_group").select2();
|
||||
@ -218,6 +274,48 @@
|
||||
$("#mainOrdersNav").addClass('active');
|
||||
$("#addOrderNav").addClass('active');
|
||||
|
||||
// ✅ Appliquer la validation sur tous les champs numériques
|
||||
$(document).on('keydown', '.numeric-input, input[type="number"]', function(e) {
|
||||
preventNegativeInput(e);
|
||||
});
|
||||
|
||||
$(document).on('input', '.numeric-input, input[type="number"]', function() {
|
||||
validatePositiveNumber(this);
|
||||
});
|
||||
|
||||
// ✅ Validation sur le champ discount
|
||||
$("#discount").on('input', function() {
|
||||
validatePrixDemande(this);
|
||||
});
|
||||
|
||||
$("#discount").on('blur', function() {
|
||||
validatePrixDemande(this);
|
||||
checkMinimalPrice();
|
||||
});
|
||||
|
||||
// Bloquer la soumission du formulaire
|
||||
$('form').on('submit', function(e) {
|
||||
// Vérifier tous les champs numériques avant soumission
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
// Add new row in the table
|
||||
$("#add_row").unbind('click').bind('click', function () {
|
||||
var table = $("#product_info_table");
|
||||
@ -239,8 +337,8 @@
|
||||
|
||||
html += '</select>' +
|
||||
'</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><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>' +
|
||||
'<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
|
||||
'</tr>';
|
||||
|
||||
@ -257,24 +355,18 @@
|
||||
return false;
|
||||
});
|
||||
|
||||
// Vérifier lors de la saisie du rabais
|
||||
$("#discount").on('blur', function() {
|
||||
checkMinimalPrice();
|
||||
});
|
||||
|
||||
// Bloquer la soumission du formulaire
|
||||
$('form').on('submit', function(e) {
|
||||
if (!checkMinimalPrice()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
}); // /document
|
||||
|
||||
function getTotal(row = null) {
|
||||
if (row) {
|
||||
var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val());
|
||||
var rate = Number($("#rate_value_" + row).val());
|
||||
var qty = Number($("#qty_" + row).val());
|
||||
|
||||
// ✅ Vérifier que les valeurs sont positives
|
||||
if (rate < 0) rate = 0;
|
||||
if (qty < 0) qty = 0;
|
||||
|
||||
var total = rate * qty;
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row).val(total);
|
||||
$("#amount_value_" + row).val(total);
|
||||
@ -306,15 +398,21 @@
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function (response) {
|
||||
// setting the rate value into the rate input field
|
||||
$("#rate_" + row_id).val(response.prix_vente);
|
||||
$("#rate_value_" + row_id).val(response.prix_vente);
|
||||
$("#min_price_" + row_id).val(response.prix_minimal); // ✅ Stockage du prix minimal
|
||||
// ✅ S'assurer que les prix ne sont pas négatifs
|
||||
var prixVente = parseFloat(response.prix_vente) || 0;
|
||||
var prixMinimal = parseFloat(response.prix_minimal) || 0;
|
||||
|
||||
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);
|
||||
|
||||
$("#qty_" + row_id).val(1);
|
||||
$("#qty_value_" + row_id).val(1);
|
||||
|
||||
var total = Number(response.prix_vente) * 1;
|
||||
var total = prixVente * 1;
|
||||
total = total.toFixed(2);
|
||||
$("#amount_" + row_id).val(total);
|
||||
$("#amount_value_" + row_id).val(total);
|
||||
@ -337,7 +435,10 @@
|
||||
var count = $(tr).attr('id');
|
||||
count = count.substring(4);
|
||||
|
||||
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
|
||||
var amount = Number($("#amount_" + count).val());
|
||||
// ✅ Vérifier que le montant n'est pas négatif
|
||||
if (amount < 0) amount = 0;
|
||||
totalSubAmount = Number(totalSubAmount) + amount;
|
||||
}
|
||||
|
||||
totalSubAmount = totalSubAmount.toFixed(2);
|
||||
@ -362,9 +463,17 @@
|
||||
var totalAmount = (Number(totalSubAmount));
|
||||
totalAmount = totalAmount.toFixed(2);
|
||||
|
||||
var discount = $("#discount").val();
|
||||
var discount = Number($("#discount").val()) || 0;
|
||||
// ✅ S'assurer que le rabais n'est pas négatif
|
||||
if (discount < 0) {
|
||||
discount = 0;
|
||||
$("#discount").val('');
|
||||
}
|
||||
|
||||
if (discount) {
|
||||
var grandTotal = Number(totalAmount) - Number(discount);
|
||||
// ✅ S'assurer que le total n'est pas négatif
|
||||
if (grandTotal < 0) grandTotal = 0;
|
||||
grandTotal = grandTotal.toFixed(2);
|
||||
$("#net_amount").val(grandTotal);
|
||||
$("#net_amount_value").val(grandTotal);
|
||||
@ -378,6 +487,14 @@
|
||||
function checkMinimalPrice() {
|
||||
var discount = Number($("#discount").val()) || 0;
|
||||
|
||||
// ✅ Vérifier que le rabais n'est pas négatif
|
||||
if (discount < 0) {
|
||||
alert("Le prix demandé ne peut pas être négatif.");
|
||||
$("#discount").val('');
|
||||
subAmount();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si pas de rabais, pas de vérification nécessaire
|
||||
if (discount === 0) return true;
|
||||
|
||||
@ -394,7 +511,7 @@
|
||||
if (minPrice > 0 && discount < minPrice) {
|
||||
error = true;
|
||||
var productText = $("#product_" + rowId + " option:selected").text();
|
||||
messages.push("Le rabais (" + discount + ") pour « " + productText + " » est inférieur au prix minimal (" + minPrice + ")");
|
||||
messages.push("Le prix demandé (" + discount.toFixed(2) + ") pour « " + productText + " » est inférieur au prix minimal (" + minPrice.toFixed(2) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,9 +547,20 @@
|
||||
|
||||
net_amount.value = (gross_amount_value.value - remise.value);
|
||||
net_amount_value.value = (gross_amount_value.value - remise.value);
|
||||
|
||||
remise.addEventListener('input', function () {
|
||||
net_amount.value = (gross_amount_value.value - remise.value);
|
||||
net_amount_value.value = (gross_amount_value.value - remise.value);
|
||||
subAmount();
|
||||
// ✅ Validation du prix demandé vs prix affiché
|
||||
if (validatePrixDemande(this)) {
|
||||
var discountValue = parseFloat(this.value) || 0;
|
||||
var grossValue = parseFloat(gross_amount_value.value) || 0;
|
||||
var netValue = grossValue - discountValue;
|
||||
|
||||
// ✅ S'assurer que le net amount n'est pas négatif
|
||||
if (netValue < 0) netValue = 0;
|
||||
|
||||
net_amount.value = netValue.toFixed(2);
|
||||
net_amount_value.value = netValue.toFixed(2);
|
||||
subAmount();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -22,26 +22,70 @@
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="alert alert-success alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<?php echo session()->getFlashdata('success'); ?>
|
||||
</div>
|
||||
<?php elseif (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-error alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<?php echo session()->getFlashdata('error'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($errors = session()->getFlashdata('errors')): ?>
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<?php if (is_array($errors)): ?>
|
||||
<ul>
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?= esc($error) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<?= esc($errors) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ✅ VÉRIFIER SI LA COMMANDE EST MODIFIABLE
|
||||
// Seuls les statuts 1 (Validé) et 3 (Validé et Livré) sont NON modifiables
|
||||
$is_editable = isset($is_editable) ? $is_editable : true;
|
||||
$paid_status = $order_data['order']['paid_status'] ?? 2;
|
||||
?>
|
||||
|
||||
<?php if (!$is_editable): ?>
|
||||
<!-- ✅ ALERTE SI NON MODIFIABLE -->
|
||||
<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><i class="fa fa-lock"></i> Cette commande ne peut plus être modifiée</strong><br>
|
||||
Elle a été <?php echo ($paid_status == 1) ? 'validée' : 'validée et livrée'; ?>.
|
||||
Vous pouvez uniquement consulter les informations.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title">Mise à jour de commande</h3>
|
||||
<h3 class="box-title">
|
||||
<?php echo $is_editable ? 'Mise à jour de commande' : 'Consultation de commande'; ?>
|
||||
</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<form role="form" action="<?php base_url('orders/create') ?>" method="post" class="form-horizontal">
|
||||
<form role="form"
|
||||
action="<?php base_url('orders/create') ?>"
|
||||
method="post"
|
||||
class="form-horizontal"
|
||||
<?php echo !$is_editable ? 'onsubmit="return false;"' : ''; ?>>
|
||||
<div class="box-body">
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="date" class="col-sm-12 control-label">Date: <?php echo date('Y-m-d') ?></label>
|
||||
</div>
|
||||
@ -54,7 +98,7 @@
|
||||
<div class="form-group">
|
||||
<label for="types" class="col-sm-5 control-label" style="text-align:left;">Types</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="" id="typesCommande" class="form-control">
|
||||
<select name="" id="typesCommande" class="form-control" <?php echo !$is_editable ? 'disabled' : ''; ?>>
|
||||
<option value="1">Facture</option>
|
||||
<option value="2">Bon de Livraison & Facture</option>
|
||||
<option value="3">Bon de Livraison</option>
|
||||
@ -65,33 +109,60 @@
|
||||
<div class="form-group">
|
||||
<label for="customer_name" class="col-sm-5 control-label" style="text-align:left;">Nom du client</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="customer_name" name="customer_name" placeholder="Enter Customer Name" value="<?php echo $order_data['order']['customer_name'] ?>" autocomplete="off" />
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="customer_name"
|
||||
name="customer_name"
|
||||
placeholder="Enter Customer Name"
|
||||
value="<?php echo $order_data['order']['customer_name'] ?>"
|
||||
autocomplete="off"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : ''; ?> />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Adresse du client</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="customer_address" name="customer_address" placeholder="Enter Customer Address" value="<?php echo $order_data['order']['customer_address'] ?>" autocomplete="off">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="customer_address"
|
||||
name="customer_address"
|
||||
placeholder="Enter Customer Address"
|
||||
value="<?php echo $order_data['order']['customer_address'] ?>"
|
||||
autocomplete="off"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">Téléphone du client</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="customer_phone" name="customer_phone" placeholder="Enter Customer Phone" value="<?php echo $order_data['order']['customer_phone'] ?>" autocomplete="off">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="customer_phone"
|
||||
name="customer_phone"
|
||||
placeholder="Enter Customer Phone"
|
||||
value="<?php echo $order_data['order']['customer_phone'] ?>"
|
||||
autocomplete="off"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label" style="text-align:left;">CIN du client</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="customer_cin" name="customer_cin" placeholder="Enter Customer CIN" value="<?php echo $order_data['order']['customer_cin'] ?>" autocomplete="off">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="customer_cin"
|
||||
name="customer_cin"
|
||||
placeholder="Enter Customer CIN"
|
||||
value="<?php echo $order_data['order']['customer_cin'] ?>"
|
||||
autocomplete="off"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br /> <br />
|
||||
<table class="table table-bordered" id="product_info_table">
|
||||
<thead>
|
||||
@ -100,38 +171,80 @@
|
||||
<th style="width:10%">Quantité</th>
|
||||
<th style="width:10%">Prix unitaire</th>
|
||||
<th style="width:20%">Montant</th>
|
||||
<th style="width:10%"><button type="button" id="add_row" class="btn btn-default"><i class="fa fa-plus"></i></button></th>
|
||||
<th style="width:10%">
|
||||
<?php if ($is_editable): ?>
|
||||
<button type="button" id="add_row" class="btn btn-default"><i class="fa fa-plus"></i></button>
|
||||
<?php endif; ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
<?php if (isset($order_data['order_item'])): ?>
|
||||
<?php $x = 1; ?>
|
||||
<?php foreach ($order_data['order_item'] as $key => $val): ?>
|
||||
<?php //print_r($v);
|
||||
?>
|
||||
<tr id="row_<?php echo $x; ?>">
|
||||
<td>
|
||||
<select class="form-control select_group product" data-row-id="row_<?php echo $x; ?>" id="product_<?php echo $x; ?>" name="product[]" style="width:100%;" onchange="getProductData(<?php echo $x; ?>)" required>
|
||||
<select class="form-control select_group product"
|
||||
data-row-id="row_<?php echo $x; ?>"
|
||||
id="product_<?php echo $x; ?>"
|
||||
name="product[]"
|
||||
style="width:100%;"
|
||||
onchange="getProductData(<?php echo $x; ?>)"
|
||||
<?php echo !$is_editable ? 'disabled' : 'required'; ?>>
|
||||
<option value=""></option>
|
||||
<?php foreach ($products as $k => $v): ?>
|
||||
<option value="<?php echo $v['id'] ?>" <?php if ($val['product_id'] == $v['id']) {
|
||||
echo "selected='selected'";
|
||||
} ?>><?php echo $v['name'] ?></option>
|
||||
<option value="<?php echo $v['id'] ?>" <?php if ($val['product_id'] == $v['id']) { echo "selected='selected'"; } ?>><?php echo $v['name'] ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="hidden" name="qty[]" id="qty_<//?php echo $x; ?>" class="form-control" required onkeyup="getTotal(<?php echo $x; ?>)" value="<//?php echo $val['qty'] ?>" autocomplete="off"></td>
|
||||
<td>
|
||||
<input type="text" name="rate[]" id="rate_<?php echo $x; ?>" class="form-control" disabled value="<?php echo $val['rate'] ?>" autocomplete="off">
|
||||
<input type="hidden" name="rate_value[]" id="rate_value_<?php echo $x; ?>" class="form-control" value="<?php echo $val['rate'] ?>" autocomplete="off">
|
||||
<input type="number"
|
||||
name="qty[]"
|
||||
id="qty_<?php echo $x; ?>"
|
||||
class="form-control"
|
||||
onkeyup="getTotal(<?php echo $x; ?>)"
|
||||
value="<?php echo $val['qty'] ?>"
|
||||
autocomplete="off"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : 'required'; ?>>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="amount[]" id="amount_<?php echo $x; ?>" class="form-control" disabled value="<?php echo $val['amount'] ?>" autocomplete="off">
|
||||
<input type="hidden" name="amount_value[]" id="amount_value_<?php echo $x; ?>" class="form-control" value="<?php echo $val['amount'] ?>" autocomplete="off">
|
||||
<input type="text"
|
||||
name="rate[]"
|
||||
id="rate_<?php echo $x; ?>"
|
||||
class="form-control"
|
||||
disabled
|
||||
value="<?php echo $val['rate'] ?>"
|
||||
autocomplete="off">
|
||||
<input type="hidden"
|
||||
name="rate_value[]"
|
||||
id="rate_value_<?php echo $x; ?>"
|
||||
class="form-control"
|
||||
value="<?php echo $val['rate'] ?>"
|
||||
autocomplete="off">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
name="amount[]"
|
||||
id="amount_<?php echo $x; ?>"
|
||||
class="form-control"
|
||||
disabled
|
||||
value="<?php echo $val['amount'] ?>"
|
||||
autocomplete="off">
|
||||
<input type="hidden"
|
||||
name="amount_value[]"
|
||||
id="amount_value_<?php echo $x; ?>"
|
||||
class="form-control"
|
||||
value="<?php echo $val['amount'] ?>"
|
||||
autocomplete="off">
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($is_editable): ?>
|
||||
<button type="button" class="btn btn-default" onclick="removeRow('<?php echo $x; ?>')">
|
||||
<i class="fa fa-close"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><button type="button" class="btn btn-default" onclick="removeRow('<?php echo $x; ?>')"><i class="fa fa-close"></i></button></td>
|
||||
</tr>
|
||||
<?php $x++; ?>
|
||||
<?php endforeach; ?>
|
||||
@ -144,63 +257,80 @@
|
||||
<div class="col-md-6 col-xs-12 pull pull-right">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="gross_amount" class="col-sm-5 control-label">Montant brut</label>
|
||||
<label for="gross_amount" class="col-sm-5 control-label">Prix affiché</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="gross_amount" name="gross_amount" disabled value="<?php echo $order_data['order']['gross_amount'] ?>" autocomplete="off">
|
||||
<input type="hidden" class="form-control" id="gross_amount_value" name="gross_amount_value" value="<?php echo $order_data['order']['gross_amount'] ?>" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <//?php if ($is_service_enabled == true): ?>
|
||||
<div class="form-group">
|
||||
<label for="service_charge" class="col-sm-5 control-label">S-Charge <//?php echo $company_data['service_charge_value'] ?> %</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="service_charge" name="service_charge" disabled value="<//?php echo $order_data['order']['service_charge_rate'] ?>" autocomplete="off">
|
||||
|
||||
<input type="hidden" class="form-control" id="service_charge_value" name="service_charge_value" value="<//?php echo $order_data['order']['service_charge_rate'] ?>" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<//?php endif; ?> -->
|
||||
<!-- <//?php if ($is_vat_enabled == true): ?>
|
||||
<div class="form-group">
|
||||
<label for="vat_charge" class="col-sm-5 control-label">T.V.A <//?php echo $company_data['vat_charge_value'] ?> %</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="vat_charge" name="vat_charge" disabled value="<//?php echo $order_data['order']['vat_charge'] ?>" autocomplete="off">
|
||||
<input type="hidden" class="form-control" id="vat_charge_value" name="vat_charge_value" value="<//?php echo $order_data['order']['vat_charge'] ?>" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<//?php endif; ?> -->
|
||||
<div class="form-group">
|
||||
<label for="discount" class="col-sm-5 control-label">Rabais</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>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="net_amount" class="col-sm-5 control-label">Montant net</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" id="net_amount" name="net_amount" disabled value="<?php echo $order_data['order']['net_amount'] ?>" autocomplete="off">
|
||||
<input type="hidden" class="form-control" id="net_amount_value" name="net_amount_value" value="<?php echo $order_data['order']['net_amount'] ?>" autocomplete="off">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="gross_amount"
|
||||
name="gross_amount"
|
||||
disabled
|
||||
value="<?php echo $order_data['order']['gross_amount'] ?>"
|
||||
autocomplete="off">
|
||||
<input type="hidden"
|
||||
class="form-control"
|
||||
id="gross_amount_value"
|
||||
name="gross_amount_value"
|
||||
value="<?php echo $order_data['order']['gross_amount'] ?>"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ ALERTE VISUELLE INTÉGRÉE -->
|
||||
<div id="price_alert" style="display: none; margin-bottom: 15px;">
|
||||
<div class="col-sm-offset-5 col-sm-7">
|
||||
<div style="background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 10px 15px; border-radius: 4px; animation: slideDown 0.3s ease-out;">
|
||||
<i class="fa fa-exclamation-triangle" style="margin-right: 8px;"></i>
|
||||
<strong>Attention !</strong> <span id="price_alert_message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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' && $is_editable):
|
||||
?>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="net_amount" class="col-sm-5 control-label">Remise</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="net_amount"
|
||||
name="net_amount"
|
||||
disabled
|
||||
value="<?php echo $order_data['order']['net_amount'] ?>"
|
||||
autocomplete="off">
|
||||
<input type="hidden"
|
||||
class="form-control"
|
||||
id="net_amount_value"
|
||||
name="net_amount_value"
|
||||
value="<?php echo $order_data['order']['net_amount'] ?>"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-group">
|
||||
<label for="paid_status" class="col-sm-5 control-label">Mode de paiement</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="payment_mode" name="payment_mode">
|
||||
<option value="MVOLA" <//?= ($order_data['order']['order_payment_mode'] == "MVOLA") ? 'selected' : '' ?>>MVOLA</option>
|
||||
<option value="Virement Bancaire" <//?= ($order_data['order']['order_payment_mode'] == "Virement Bancaire") ? 'selected' : '' ?>>Virement Bancaire</option>
|
||||
<option value="En espèce" <//?= ($order_data['order']['order_payment_mode'] == "En espèce") ? 'selected' : '' ?>>En espèce</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> -->
|
||||
<?php
|
||||
$users = session()->get('user');
|
||||
if ($users && $users['group_name'] !== 'COMMERCIALE'):
|
||||
@ -209,14 +339,16 @@
|
||||
<div class="form-group">
|
||||
<label for="paid_status" class="col-sm-5 control-label">Tranche de paiement</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="payment_mode" name="payment_mode">
|
||||
<select class="form-control"
|
||||
id="payment_mode"
|
||||
name="payment_mode"
|
||||
<?php echo !$is_editable ? 'disabled' : ''; ?>>
|
||||
<option value="1" selected>une tranche</option>
|
||||
<option value="2">deux tranches</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ AJOUTEZ CE CHAMP AVANT LES TRANCHES -->
|
||||
<div class="form-group" id="montant_reference" style="display: none">
|
||||
<label class="col-sm-5 control-label">Montant à répartir</label>
|
||||
<div class="col-sm-7">
|
||||
@ -230,84 +362,57 @@
|
||||
<div class="form-group" id="paid_status_1" style="display: none">
|
||||
<label for="paid_status_1" class="col-sm-5 control-label">Tranche 1</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" id="payment_mode_1" name="order_payment_mode_1">
|
||||
<select class="form-control"
|
||||
id="payment_mode_1"
|
||||
name="order_payment_mode_1"
|
||||
<?php echo !$is_editable ? 'disabled' : ''; ?>>
|
||||
<option value="MVOLA">MVOLA</option>
|
||||
<option value="Virement Bancaire">Virement Bancaire</option>
|
||||
<option value="En espèce">En espèce</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" class="form-control" id="payment_amount_1"
|
||||
name="tranche_1" placeholder="Montant" onkeyup="calculerTranche2()">
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="payment_amount_1"
|
||||
name="tranche_1"
|
||||
placeholder="Montant"
|
||||
onkeyup="calculerTranche2()"
|
||||
<?php echo !$is_editable ? 'disabled readonly' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="paid_status_2" style="display: none">
|
||||
<label for="paid_status_2" class="col-sm-5 control-label">Tranche 2 (Reste)</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" id="payment_mode_2" name="order_payment_mode_2">
|
||||
<select class="form-control"
|
||||
id="payment_mode_2"
|
||||
name="order_payment_mode_2"
|
||||
<?php echo !$is_editable ? 'disabled' : ''; ?>>
|
||||
<option value="MVOLA">MVOLA</option>
|
||||
<option value="Virement Bancaire">Virement Bancaire</option>
|
||||
<option value="En espèce">En espèce</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" class="form-control" id="payment_amount_2"
|
||||
name="tranche_2" placeholder="Montant" readonly>
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="payment_amount_2"
|
||||
name="tranche_2"
|
||||
placeholder="Montant"
|
||||
readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var paymentTranche = 1;
|
||||
var netAmount = parseFloat($('#net_amount_value').val()) || 0;
|
||||
|
||||
// Initialisation : définir la première tranche avec le montant total
|
||||
$('#payment_amount_1').val(netAmount);
|
||||
|
||||
function addPaymentTranche(paymentTranche) {
|
||||
if (parseInt(paymentTranche) === 2) {
|
||||
$("#paid_status_1").show();
|
||||
$("#paid_status_2").show();
|
||||
|
||||
// Calculer la deuxième tranche
|
||||
var amount1 = parseFloat($('#payment_amount_1').val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
} else {
|
||||
$("#paid_status_1").show();
|
||||
$("#paid_status_2").hide();
|
||||
$('#payment_mode_2').val('');
|
||||
}
|
||||
}
|
||||
|
||||
// Écouter le changement de la sélection pour afficher les tranches
|
||||
$("#payment_mode").on("change", function() {
|
||||
addPaymentTranche($(this).val());
|
||||
});
|
||||
|
||||
// Écouter la modification du montant de la première tranche
|
||||
$('#payment_amount_1').on("input", function() {
|
||||
var amount1 = parseFloat($(this).val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
});
|
||||
|
||||
addPaymentTranche(paymentTranche);
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$users = session()->get('user');
|
||||
if ($users && $users['group_name'] !== 'COMMERCIALE'):
|
||||
?>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="paid_status" class="col-sm-5 control-label">Statut payant</label>
|
||||
<div class="col-sm-7">
|
||||
<select type="text" class="form-control" id="paid_status" name="paid_status">
|
||||
<select type="text"
|
||||
class="form-control"
|
||||
id="paid_status"
|
||||
name="paid_status"
|
||||
<?php echo !$is_editable ? 'disabled' : ''; ?>>
|
||||
<option value="1">Validé</option>
|
||||
<option value="2">Refusé</option>
|
||||
</select>
|
||||
@ -320,12 +425,15 @@
|
||||
<!-- /.box-body -->
|
||||
|
||||
<div class="box-footer">
|
||||
|
||||
<input type="hidden" name="service_charge_rate" value="<?php echo $company_data['service_charge_value'] ?>" autocomplete="off">
|
||||
<input type="hidden" name="vat_charge_rate" value="<?php echo $company_data['vat_charge_value'] ?>" autocomplete="off">
|
||||
|
||||
<a target="__blank" id="Imprimente" href="<?php echo base_url() . 'orders/printDiv/' . $order_data['order']['id'] ?>" class="btn btn-default">Imprimer</a>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
|
||||
<?php if ($is_editable): ?>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?php echo base_url('orders/') ?>" class="btn btn-warning">Retour</a>
|
||||
</div>
|
||||
</form>
|
||||
@ -336,16 +444,42 @@
|
||||
<!-- col-md-12 -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
|
||||
</section>
|
||||
<!-- /.content -->
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
|
||||
<style>
|
||||
/* ✅ Animation pour l'alerte */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ✅ Style pour l'input en erreur */
|
||||
.input-error {
|
||||
border: 2px solid #dc3545 !important;
|
||||
box-shadow: 0 0 8px rgba(220, 53, 69, 0.5) !important;
|
||||
animation: pulse 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 8px rgba(220, 53, 69, 0.5); }
|
||||
50% { box-shadow: 0 0 15px rgba(220, 53, 69, 0.8); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
var base_url = "<?php echo base_url(); ?>";
|
||||
var idData = "<?php echo $order_data['order']['id']; ?>";
|
||||
var is_editable = <?php echo $is_editable ? 'true' : 'false'; ?>;
|
||||
|
||||
let Imprimente = document.getElementById('Imprimente');
|
||||
let typesCommande = document.getElementById('typesCommande');
|
||||
|
||||
@ -362,14 +496,76 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 🔹 FONCTION D'ALERTE VISUELLE
|
||||
// ============================================
|
||||
function showPriceAlert(message) {
|
||||
const alertBox = $('#price_alert');
|
||||
const discountInput = $('#discount');
|
||||
|
||||
$('#price_alert_message').text(message);
|
||||
alertBox.slideDown(300);
|
||||
discountInput.addClass('input-error');
|
||||
|
||||
setTimeout(function() {
|
||||
alertBox.slideUp(300);
|
||||
discountInput.removeClass('input-error');
|
||||
}, 3500);
|
||||
}
|
||||
|
||||
function hidePriceAlert() {
|
||||
$('#price_alert').slideUp(300);
|
||||
$('#discount').removeClass('input-error');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔹 VALIDATION DU PRIX DEMANDÉ
|
||||
// ============================================
|
||||
function validateDiscount() {
|
||||
if (!is_editable) return true; // ✅ Pas de validation si non éditable
|
||||
|
||||
var discount = parseFloat($('#discount').val());
|
||||
var grossAmount = parseFloat($('#gross_amount_value').val()) || 0;
|
||||
|
||||
if (discount < 0) {
|
||||
showPriceAlert("Le prix demandé ne peut pas être négatif !");
|
||||
$('#discount').val('');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (discount > grossAmount) {
|
||||
showPriceAlert("Le prix demandé ne peut pas dépasser le prix affiché (" + grossAmount.toFixed(2) + ") !");
|
||||
$('#discount').val(grossAmount.toFixed(2));
|
||||
subAmount();
|
||||
return false;
|
||||
}
|
||||
|
||||
hidePriceAlert();
|
||||
return true;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".select_group").select2();
|
||||
|
||||
$("#mainOrdersNav").addClass('active');
|
||||
$("#manageOrdersNav").addClass('active');
|
||||
|
||||
// ✅ Désactiver toutes les interactions si non éditable
|
||||
if (!is_editable) {
|
||||
$('input, select, textarea').prop('disabled', true);
|
||||
$('#add_row').hide();
|
||||
$('.btn-default[onclick^="removeRow"]').hide();
|
||||
}
|
||||
|
||||
// ✅ Validation en temps réel du prix demandé
|
||||
if (is_editable) {
|
||||
$('#discount').on('keyup change', function() {
|
||||
validateDiscount();
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔹 AJOUT : GESTION DES TRANCHES DE PAIEMENT
|
||||
// 🔹 GESTION DES TRANCHES DE PAIEMENT
|
||||
// ============================================
|
||||
var paymentTranche = 1;
|
||||
var netAmount = parseFloat($('#net_amount_value').val()) || 0;
|
||||
@ -390,17 +586,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Sélection du mode de paiement
|
||||
$("#payment_mode").on("change", function() {
|
||||
addPaymentTranche($(this).val());
|
||||
updateMontantTranches(); // ✅ pour mise à jour automatique
|
||||
if (is_editable) {
|
||||
addPaymentTranche($(this).val());
|
||||
updateMontantTranches();
|
||||
}
|
||||
});
|
||||
|
||||
// Modification du montant de la première tranche
|
||||
$('#payment_amount_1').on("input", function() {
|
||||
var amount1 = parseFloat($(this).val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
if (is_editable) {
|
||||
var amount1 = parseFloat($(this).val()) || 0;
|
||||
var amount2 = netAmount - amount1;
|
||||
$('#payment_amount_2').val(amount2);
|
||||
}
|
||||
});
|
||||
|
||||
addPaymentTranche(paymentTranche);
|
||||
@ -408,51 +606,54 @@
|
||||
// ============================================
|
||||
// 🔹 TABLEAU DE PRODUITS
|
||||
// ============================================
|
||||
$("#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;
|
||||
if (is_editable) {
|
||||
$("#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;
|
||||
|
||||
$.ajax({
|
||||
url: base_url + '/orders/getTableProductRow/',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
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) {
|
||||
html += '<option value="' + value.id + '">' + value.name + '</option>';
|
||||
});
|
||||
$.ajax({
|
||||
url: base_url + '/orders/getTableProductRow/',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
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) {
|
||||
html += '<option value="' + value.id + '">' + value.name + '</option>';
|
||||
});
|
||||
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
'<td><input type="number" name="qty[]" id="qty_' + row_id + '" class="form-control" onkeyup="getTotal(' + row_id + ')"></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>' +
|
||||
'<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>';
|
||||
html += '</select>' +
|
||||
'</td>' +
|
||||
'<td><input type="number" name="qty[]" id="qty_' + row_id + '" class="form-control" onkeyup="getTotal(' + row_id + ')"></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>' +
|
||||
'<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>';
|
||||
|
||||
if (count_table_tbody_tr >= 1) {
|
||||
$("#product_info_table tbody tr:last").after(html);
|
||||
} else {
|
||||
$("#product_info_table tbody").html(html);
|
||||
if (count_table_tbody_tr >= 1) {
|
||||
$("#product_info_table tbody tr:last").after(html);
|
||||
} else {
|
||||
$("#product_info_table tbody").html(html);
|
||||
}
|
||||
|
||||
$(".product").select2();
|
||||
}
|
||||
});
|
||||
|
||||
$(".product").select2();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}); // /document.ready
|
||||
|
||||
|
||||
// ============================================
|
||||
// 🔹 CALCUL DU TOTAL
|
||||
// ============================================
|
||||
function getTotal(row = null) {
|
||||
if (!is_editable) return; // ✅ Bloquer si non éditable
|
||||
|
||||
if (row) {
|
||||
var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val());
|
||||
total = total.toFixed(2);
|
||||
@ -466,6 +667,8 @@
|
||||
|
||||
// 🔹 OBTENIR LES DONNÉES PRODUIT
|
||||
function getProductData(row_id) {
|
||||
if (!is_editable) return; // ✅ Bloquer si non éditable
|
||||
|
||||
var product_id = $("#product_" + row_id).val();
|
||||
if (product_id == "") {
|
||||
$("#rate_" + row_id).val("");
|
||||
@ -498,6 +701,8 @@
|
||||
// 🔹 CALCUL DU MONTANT TOTAL (AVEC TVA, REMISE, ETC.)
|
||||
// ============================================
|
||||
function subAmount() {
|
||||
if (!is_editable) return; // ✅ Bloquer si non éditable
|
||||
|
||||
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; ?>;
|
||||
|
||||
@ -529,10 +734,12 @@
|
||||
|
||||
var discount = $("#discount").val();
|
||||
if (discount) {
|
||||
var grandTotal = Number(totalAmount) - Number(discount);
|
||||
grandTotal = grandTotal.toFixed(2);
|
||||
$("#net_amount").val(grandTotal);
|
||||
$("#net_amount_value").val(grandTotal);
|
||||
if (validateDiscount()) {
|
||||
var grandTotal = Number(totalAmount) - Number(discount);
|
||||
grandTotal = grandTotal.toFixed(2);
|
||||
$("#net_amount").val(grandTotal);
|
||||
$("#net_amount_value").val(grandTotal);
|
||||
}
|
||||
} else {
|
||||
$("#net_amount").val(totalAmount);
|
||||
$("#net_amount_value").val(totalAmount);
|
||||
@ -546,7 +753,6 @@
|
||||
$("#remaining_value").val(remaning.toFixed(2));
|
||||
}
|
||||
|
||||
// ✅ Mise à jour automatique des tranches à chaque recalcul
|
||||
updateMontantTranches();
|
||||
}
|
||||
|
||||
@ -554,6 +760,8 @@
|
||||
// 🔹 AUTRES FONCTIONS
|
||||
// ============================================
|
||||
function paidAmount() {
|
||||
if (!is_editable) return;
|
||||
|
||||
var grandTotal = $("#net_amount_value").val();
|
||||
if (grandTotal) {
|
||||
var dueAmount = Number($("#net_amount_value").val()) - Number($("#paid_amount").val());
|
||||
@ -564,12 +772,14 @@
|
||||
}
|
||||
|
||||
function removeRow(tr_id) {
|
||||
if (!is_editable) return; // ✅ Bloquer si non éditable
|
||||
|
||||
$("#product_info_table tbody tr#row_" + tr_id).remove();
|
||||
subAmount();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔹 GESTION MONTANT DE TRANCHES (FONCTIONS NOUVELLES)
|
||||
// 🔹 GESTION MONTANT DE TRANCHES
|
||||
// ============================================
|
||||
function getMontantPourTranches() {
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
@ -578,6 +788,8 @@
|
||||
}
|
||||
|
||||
function updateMontantTranches() {
|
||||
if (!is_editable) return;
|
||||
|
||||
var montant = getMontantPourTranches();
|
||||
var discount = parseFloat($("#discount").val()) || 0;
|
||||
|
||||
@ -596,6 +808,8 @@
|
||||
}
|
||||
|
||||
function calculerTranche2() {
|
||||
if (!is_editable) return;
|
||||
|
||||
var montantTotal = getMontantPourTranches();
|
||||
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
|
||||
var tranche2 = montantTotal - tranche1;
|
||||
@ -603,14 +817,16 @@
|
||||
$("#payment_amount_2").val(tranche2.toFixed(2));
|
||||
}
|
||||
|
||||
$("#discount").on('keyup', function() {
|
||||
updateMontantTranches();
|
||||
});
|
||||
if (is_editable) {
|
||||
$("#discount").on('keyup', function() {
|
||||
updateMontantTranches();
|
||||
});
|
||||
}
|
||||
|
||||
const net_amount_value = document.getElementById('net_amount_value');
|
||||
const net_amount = document.getElementById('net_amount');
|
||||
const payment_amount_1 = document.getElementById('payment_amount_1');
|
||||
payment_amount_1.value = net_amount.value;
|
||||
|
||||
|
||||
</script>
|
||||
if (payment_amount_1 && net_amount) {
|
||||
payment_amount_1.value = net_amount.value;
|
||||
}
|
||||
</script>
|
||||
@ -395,4 +395,6 @@ document.querySelector('form').addEventListener('submit', function(e) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
@ -160,9 +160,8 @@
|
||||
<th>Prix</th>
|
||||
<th>Magasin</th>
|
||||
<th>Disponibilité</th>
|
||||
<?php if (in_array('updateProduct', $user_permission) || in_array('deleteProduct', $user_permission)): ?>
|
||||
<th>Action</th>
|
||||
<?php endif; ?>
|
||||
<!-- ✅ MODIFICATION PRINCIPALE : Toujours afficher la colonne Action -->
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
@ -336,7 +335,7 @@
|
||||
},
|
||||
{ data: 4 }, // Magasin
|
||||
{ data: 5 }, // Disponibilité
|
||||
{ data: 6 } // Actions
|
||||
{ data: 6 } // Actions - ✅ Toujours présent maintenant
|
||||
],
|
||||
'columnDefs': [{
|
||||
targets: 3,
|
||||
|
||||
@ -905,6 +905,7 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -19,7 +19,12 @@
|
||||
<span id="notificationCount" class="badge badge-warning navbar-badge"></span>
|
||||
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"
|
||||
style="width: 400px; padding: 5%; max-height: 500px; overflow: auto; margin-right: 5px;">
|
||||
<span class="dropdown-header" id="notificationHeader">0 Notifications</span>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 0 10px;">
|
||||
<span class="dropdown-header" id="notificationHeader" style="padding: 0;">0 Notifications</span>
|
||||
<button id="markAllAsReadBtn" class="btn btn-sm btn-primary" style="font-size: 12px; padding: 4px 10px;">
|
||||
<i class="fa fa-check"></i> Marquer tout comme lu
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div id="notificationList"></div>
|
||||
<div class="dropdown-divider"></div>
|
||||
@ -41,6 +46,15 @@
|
||||
.icon-unread {
|
||||
color: #007bff; /* bleu */
|
||||
}
|
||||
|
||||
/* Style du bouton */
|
||||
#markAllAsReadBtn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#markAllAsReadBtn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@ -86,8 +100,10 @@ function fetchNotifications() {
|
||||
// Badge pour non lues
|
||||
if (notificationCount > 0) {
|
||||
$('#notificationCount').text(notificationCount).show();
|
||||
$('#markAllAsReadBtn').show();
|
||||
} else {
|
||||
$('#notificationCount').hide();
|
||||
$('#markAllAsReadBtn').hide();
|
||||
}
|
||||
|
||||
// Ajouter l'événement clic pour marquer comme lu
|
||||
@ -115,8 +131,30 @@ function fetchNotifications() {
|
||||
});
|
||||
}
|
||||
|
||||
// Fonction pour marquer toutes les notifications comme lues
|
||||
function markAllAsRead() {
|
||||
fetch("<?= base_url('notifications/markAllAsRead') ?>", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log("All notifications marked as read:", data);
|
||||
// Rafraîchir les notifications immédiatement
|
||||
fetchNotifications();
|
||||
})
|
||||
.catch(error => console.error("Error:", error));
|
||||
}
|
||||
|
||||
// Attacher l'événement au bouton
|
||||
$(document).on('click', '#markAllAsReadBtn', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
markAllAsRead();
|
||||
});
|
||||
|
||||
// Rafraîchir toutes les 10 secondes
|
||||
setInterval(fetchNotifications, 10000);
|
||||
// Premier chargement
|
||||
fetchNotifications();
|
||||
</script>
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user