Sarobidy22 2 months ago
parent
commit
1c92093b90
  1. 12
      app/Config/Routes.php
  2. 135
      app/Controllers/AvanceController.php
  3. 108
      app/Controllers/Dashboard.php
  4. 6
      app/Controllers/NotificationController.php
  5. 777
      app/Controllers/OrderController.php
  6. 89
      app/Controllers/ProductCOntroller.php
  7. 82
      app/Controllers/RecouvrementController.php
  8. 60
      app/Controllers/RemiseController.php
  9. 75
      app/Controllers/ReportController.php
  10. 1177
      app/Controllers/SortieCaisseController.php
  11. 2
      app/Models/Avance.php
  12. 10
      app/Models/Notification.php
  13. 233
      app/Models/Orders.php
  14. 23
      app/Models/Products.php
  15. 2
      app/Models/Recouvrement.php
  16. 92
      app/Models/Remise.php
  17. 17
      app/Models/Reports.php
  18. 78
      app/Models/SortieCaisse.php
  19. 450
      app/Views/avances/avance.php
  20. 126
      app/Views/demande/index.php
  21. 179
      app/Views/orders/create.php
  22. 93
      app/Views/orders/createbyid.php
  23. 219
      app/Views/orders/edit.php
  24. 183
      app/Views/orders/index.php
  25. 41
      app/Views/products/create.php
  26. 234
      app/Views/products/editbackup.php
  27. 1
      app/Views/securite/index.php
  28. 1136
      app/Views/sortieCaisse/index.php
  29. 125
      app/Views/templates/header_menu.php

12
app/Config/Routes.php

@ -184,6 +184,7 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->group('/orders', function ($routes) {
$routes->get('/', [OrderController::class, 'index']);
$routes->get('fetchOrdersData', [OrderController::class, 'fetchOrdersData']);
$routes->get('editAsNew/(:num)', 'OrderController::editAsNew/$1');
$routes->get('create', [OrderController::class, 'create']);
$routes->post('create', [OrderController::class, 'create']);
$routes->post('getProductValueById', [OrderController::class, 'getProductValueById']);
@ -194,11 +195,12 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('printDivBL/(:num)', [OrderController::class, 'print7']);
$routes->get('printDivBLF/(:num)', [OrderController::class, 'print31']);
$routes->post('remove', [OrderController::class, 'remove']);
$routes->post('markAsDelivered', [OrderController::class, 'markAsDelivered']); // ← AJOUTEZ CETTE LIGNE ICI
$routes->get('lookOrder/(:num)', [OrderController::class, 'lookOrder']);
$routes->get('createFromEspace/(:num)', [OrderController::class, 'createById']);
$routes->get('resrevation', [ReservationController::class, 'index']);
});
});
/**
* route for the reports
*/
@ -267,13 +269,19 @@ $routes->group('', ['filter' => 'auth'], function ($routes) {
$routes->get('fetchSortieCaisseData1', [SortieCaisseController::class, 'fetchSortieCaisseData1']);
$routes->post('fetchSortieCaisseSingle/(:num)', [SortieCaisseController::class, 'fetchSortieCaisseSingle']);
$routes->post('createSortieCaisse', [SortieCaisseController::class, 'createSortieCaisse']);
// $routes->post('delete', [RecouvrementController::class, 'removeRecouvrement']);
$routes->post('markAsPaid/(:num)', 'SortieCaisseController::markAsPaid/$1');
$routes->post('updateSortieCaisse/(:num)', [SortieCaisseController::class, 'updateSortieCaisse']);
$routes->post('validateSortieCaisse/(:num)', [SortieCaisseController::class, 'validateSortieCaisse']);
// ✅ Routes d'exportation
$routes->get('exportExcel', [SortieCaisseController::class, 'exportExcel']);
$routes->get('exportCsv', [SortieCaisseController::class, 'exportCsv']);
$routes->get('exportWithFilters', [SortieCaisseController::class, 'exportWithFilters']);
});
// remise
$routes->group('/remise', function ($routes) {
$routes->get('', [RemiseController::class, 'index']);
$routes->get('/', [RemiseController::class, 'index']);
$routes->get('fetchRemiseData', [RemiseController::class, 'fetchRemiseData']);
// $routes->post('delete', [RecouvrementController::class, 'removeRecouvrement']);

135
app/Controllers/AvanceController.php

@ -63,6 +63,7 @@ class AvanceController extends AdminController
. '<i class="fa fa-eye"></i></a>';
}
return $buttons;
}
@ -70,12 +71,17 @@ class AvanceController extends AdminController
{
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date']));
// ✅ Afficher product_name si disponible, sinon récupérer depuis la BDD
$productName = !empty($value['product_name'])
? $value['product_name']
: $product->getProductNameById($value['product_id']);
if ($isAdmin) {
return [
$value['customer_name'],
$value['customer_phone'],
$value['customer_address'],
$product->getProductNameById($value['product_id']),
$productName, // ✅ Utiliser la variable
number_format((int)$value['gross_amount'], 0, ',', ' '),
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
@ -85,7 +91,7 @@ class AvanceController extends AdminController
} elseif ($isCommerciale || $isCaissier) {
return [
$value['avance_id'],
$product->getProductNameById($value['product_id']),
$productName, // ✅ Utiliser la variable
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
@ -370,16 +376,30 @@ class AvanceController extends AdminController
$Products = new Products();
$Notification = new NotificationController();
// ✅ Récupérer le type AVANT la validation
$type_avance = $this->request->getPost('type_avance');
// ✅ Validation conditionnelle
$validation = \Config\Services::validation();
$validation->setRules([
$baseRules = [
'customer_name_avance' => 'required|min_length[2]',
'customer_phone_avance' => 'required',
'customer_address_avance' => 'required',
'customer_cin_avance' => 'required',
'id_product' => 'required|numeric',
'gross_amount' => 'required|numeric|greater_than[0]',
'avance_amount' => 'required|numeric|greater_than[0]',
'type_avance' => 'required|in_list[terre,mere]'
]);
'type_avance' => 'required|in_list[terre,mere]',
'type_payment' => 'required'
];
if ($type_avance === 'mere') {
$baseRules['product_name_text'] = 'required|min_length[2]';
} else {
$baseRules['id_product'] = 'required|numeric';
}
$validation->setRules($baseRules);
if (!$validation->withRequest($this->request)->run()) {
return $this->response->setJSON([
@ -390,16 +410,16 @@ class AvanceController extends AdminController
$avance_date = date('Y-m-d H:i:s');
// Calcul automatique de la deadline selon le type d'avance
$type_avance = $this->request->getPost('type_avance');
// Calcul de la deadline
if ($type_avance === 'terre') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +15 days'));
} elseif ($type_avance === 'mere') {
$deadline = date('Y-m-d', strtotime($avance_date . ' +2 months'));
} else {
$deadline = null; // fallback si jamais
$deadline = null;
}
// Préparer les données communes
$data = [
'type_avance' => $type_avance,
'type_payment' => $this->request->getPost('type_payment'),
@ -411,16 +431,30 @@ class AvanceController extends AdminController
'deadline' => $deadline,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'product_id' => (int)$this->request->getPost('id_product'),
'gross_amount' => (float)$this->request->getPost('gross_amount'),
'avance_amount' => (float)$this->request->getPost('avance_amount'),
'amount_due' => (float)$this->request->getPost('amount_due'),
'amount_due' => (float)$this->request->getPost('gross_amount') - (float)$this->request->getPost('avance_amount'),
'is_order' => 0,
'active' => 1,
];
// ✅ Ajouter le produit selon le type
if ($type_avance === 'mere') {
$data['product_name'] = $this->request->getPost('product_name_text');
// $data['product_id'] = null;
$data['commentaire'] = $this->request->getPost('commentaire');
} else {
$data['product_id'] = (int)$this->request->getPost('id_product');
$data['product_name'] = null;
$data['commentaire'] = null;
}
if ($avance_id = $Avance->createAvance($data)) {
// ✅ Marquer le produit comme vendu UNIQUEMENT pour "terre"
if ($type_avance === 'terre' && !empty($data['product_id'])) {
$Products->update($data['product_id'], ['product_sold' => 1]);
}
$Notification->createNotification(
'Une nouvelle avance a été créée',
@ -444,10 +478,11 @@ class AvanceController extends AdminController
log_message('error', "Erreur création avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue'
'messages' => 'Une erreur interne est survenue: ' . $e->getMessage()
]);
}
}
public function updateAvance()
{
$this->verifyRole('updateAvance');
@ -467,18 +502,31 @@ class AvanceController extends AdminController
$Products = new Products();
$Notification = new NotificationController();
// Validation des données (avec les vrais noms des champs du formulaire)
// ✅ Récupérer le type AVANT la validation
$type_avance = $this->request->getPost('type_avance_edit');
// ✅ Validation conditionnelle selon le type
$validation = \Config\Services::validation();
$validation->setRules([
$baseRules = [
'id' => 'required|numeric',
'customer_name_avance_edit' => 'required|min_length[2]',
'customer_phone_avance_edit' => 'required',
'customer_address_avance_edit' => 'required',
'customer_cin_avance_edit' => 'required',
'id_product_edit' => 'required|numeric',
'gross_amount_edit' => 'required|numeric|greater_than[0]',
'avance_amount_edit' => 'required|numeric|greater_than[0]',
'type_avance_edit' => 'required|in_list[terre,mere]'
]);
'type_avance_edit' => 'required|in_list[terre,mere]',
'type_payment_edit' => 'required'
];
if ($type_avance === 'mere') {
$baseRules['product_name_text_edit'] = 'required|min_length[2]';
} else {
$baseRules['id_product_edit'] = 'required|numeric';
}
$validation->setRules($baseRules);
if (!$validation->withRequest($this->request)->run()) {
return $this->response->setJSON([
@ -487,7 +535,7 @@ class AvanceController extends AdminController
]);
}
$avance_id = $this->request->getPost('id'); // Changement ici
$avance_id = $this->request->getPost('id');
// Vérifier si l'avance existe
$existingAvance = $Avance->fetchSingleAvance($avance_id);
@ -510,10 +558,8 @@ class AvanceController extends AdminController
}
// Recalculer la deadline si le type d'avance a changé
$type_avance = $this->request->getPost('type_avance_edit'); // Changement ici
$current_deadline = $existingAvance['deadline'];
// Si le type a changé, recalculer la deadline
if ($type_avance !== $existingAvance['type_avance']) {
if ($type_avance === 'terre') {
$current_deadline = date('Y-m-d', strtotime($existingAvance['avance_date'] . ' +15 days'));
@ -523,34 +569,54 @@ class AvanceController extends AdminController
}
$old_product_id = $existingAvance['product_id'];
$new_product_id = (int)$this->request->getPost('id_product_edit'); // Changement ici
// Préparer les données communes
$data = [
'type_avance' => $type_avance,
'type_payment' => $this->request->getPost('type_payment_edit'), // Changement ici
'customer_name' => $this->request->getPost('customer_name_avance_edit'), // Changement ici
'customer_address' => $this->request->getPost('customer_address_avance_edit'), // Changement ici
'customer_phone' => $this->request->getPost('customer_phone_avance_edit'), // Changement ici
'customer_cin' => $this->request->getPost('customer_cin_avance_edit'), // Changement ici
'type_payment' => $this->request->getPost('type_payment_edit'),
'customer_name' => $this->request->getPost('customer_name_avance_edit'),
'customer_address' => $this->request->getPost('customer_address_avance_edit'),
'customer_phone' => $this->request->getPost('customer_phone_avance_edit'),
'customer_cin' => $this->request->getPost('customer_cin_avance_edit'),
'deadline' => $current_deadline,
'product_id' => $new_product_id,
'gross_amount' => (float)$this->request->getPost('gross_amount_edit'), // Changement ici
'avance_amount' => (float)$this->request->getPost('avance_amount_edit'), // Changement ici
'amount_due' => (float)$this->request->getPost('amount_due_edit'), // Changement ici
'gross_amount' => (float)$this->request->getPost('gross_amount_edit'),
'avance_amount' => (float)$this->request->getPost('avance_amount_edit'),
'amount_due' => (float)$this->request->getPost('amount_due_edit'),
];
// ✅ Gérer le produit selon le type
if ($type_avance === 'mere') {
$data['product_name'] = $this->request->getPost('product_name_text_edit');
$data['product_id'] = null; // ⚠️ Sera NULL si la colonne l'accepte
$data['commentaire'] = $this->request->getPost('commentaire_edit');
$new_product_id = null;
} else {
$new_product_id = (int)$this->request->getPost('id_product_edit');
$data['product_id'] = $new_product_id;
$data['product_name'] = null;
$data['commentaire'] = null;
}
// Mettre à jour l'avance
if ($Avance->updateAvance($avance_id, $data)) {
// Gérer le changement de produit si nécessaire
if ($old_product_id !== $new_product_id) {
// ✅ Gérer le changement de produit UNIQUEMENT pour "terre"
if ($type_avance === 'terre') {
if ($old_product_id && $old_product_id !== $new_product_id) {
// Libérer l'ancien produit
$Products->update($old_product_id, ['product_sold' => 0]);
}
if ($new_product_id) {
// Marquer le nouveau produit comme vendu
$Products->update($new_product_id, ['product_sold' => 1]);
}
} else {
// Si on passe de "terre" à "mere", libérer l'ancien produit
if ($old_product_id && $existingAvance['type_avance'] === 'terre') {
$Products->update($old_product_id, ['product_sold' => 0]);
}
}
// Créer une notification
$Notification->createNotification(
'Une avance a été modifiée',
"Conseil",
@ -574,11 +640,12 @@ class AvanceController extends AdminController
log_message('error', "Erreur modification avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue'
'messages' => 'Une erreur interne est survenue: ' . $e->getMessage()
]);
}
}
public function removeAvance()
{
$this->verifyRole('deleteAvance');

108
app/Controllers/Dashboard.php

@ -10,6 +10,7 @@ use App\Models\Stores;
use App\Models\Users;
use App\Models\Recouvrement;
use App\Models\SortieCaisse;
class Dashboard extends AdminController
{
@ -32,71 +33,78 @@ class Dashboard extends AdminController
$totalRecouvrement = $Recouvrement->getTotalRecouvrements();
$sortieCaisse = new SortieCaisse();
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
$total_sortie_caisse1= $total_sortie_caisse->mr;
// Recouvrements
$total_recouvrement_me = $totalRecouvrement->me;
$total_recouvrement_bm = $totalRecouvrement->bm;
$total_recouvrement_be = $totalRecouvrement->be;
$total_recouvrement_mb = $totalRecouvrement->mb;
// === EXTRACTION DES SORTIES PAR MODE DE PAIEMENT ===
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
// === TOTAUX RECOUVREMENT ===
$me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
$bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
$be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
$mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
// Avances
$Avance = new Avance();
$totalAvance = $Avance->getTotalAvance();
$paymentDataAvance = $Avance->getPaymentModesAvance();
// Initialisation des totaux Orders (en utilisant les bonnes propriétés)
$total_orders = isset($paymentData->total) ? $paymentData->total : 0;
$total_mvola1_orders = isset($paymentData->total_mvola1) ? $paymentData->total_mvola1 : 0;
$total_mvola2_orders = isset($paymentData->total_mvola2) ? $paymentData->total_mvola2 : 0;
$total_espece1_orders = isset($paymentData->total_espece1) ? $paymentData->total_espece1 : 0;
$total_espece2_orders = isset($paymentData->total_espece2) ? $paymentData->total_espece2 : 0;
$total_virement_bancaire1_orders = isset($paymentData->total_virement_bancaire1) ? $paymentData->total_virement_bancaire1 : 0;
$total_virement_bancaire2_orders = isset($paymentData->total_virement_bancaire2) ? $paymentData->total_virement_bancaire2 : 0;
// Initialisation des totaux Avances (utiliser les bonnes propriétés de getPaymentModesAvance)
$total_avances = isset($paymentDataAvance->total) ? $paymentDataAvance->total : 0;
$total_mvola_avances = isset($paymentDataAvance->total_mvola) ? $paymentDataAvance->total_mvola : 0;
$total_espece_avances = isset($paymentDataAvance->total_espece) ? $paymentDataAvance->total_espece : 0;
$total_virement_bancaire_avances = isset($paymentDataAvance->total_virement_bancaire) ? $paymentDataAvance->total_virement_bancaire : 0;
// Addition Orders + Avances
// === TOTAUX PAIEMENTS ORDERS ===
$total_orders = isset($paymentData->total) ? (float) $paymentData->total : 0;
$mv1_orders = isset($paymentData->total_mvola1) ? (float) $paymentData->total_mvola1 : 0;
$mv2_orders = isset($paymentData->total_mvola2) ? (float) $paymentData->total_mvola2 : 0;
$es1_orders = isset($paymentData->total_espece1) ? (float) $paymentData->total_espece1 : 0;
$es2_orders = isset($paymentData->total_espece2) ? (float) $paymentData->total_espece2 : 0;
$vb1_orders = isset($paymentData->total_virement_bancaire1) ? (float) $paymentData->total_virement_bancaire1 : 0;
$vb2_orders = isset($paymentData->total_virement_bancaire2) ? (float) $paymentData->total_virement_bancaire2 : 0;
// === TOTAUX PAIEMENTS AVANCES ===
$total_avances = isset($paymentDataAvance->total) ? (float) $paymentDataAvance->total : 0;
$mv_avances = isset($paymentDataAvance->total_mvola) ? (float) $paymentDataAvance->total_mvola : 0;
$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;
// Combiner les totaux par type de paiement
$total_mvola1 = $total_mvola1_orders;
$total_mvola2 = $total_mvola2_orders;
$total_espece1 = $total_espece1_orders;
$total_espece2 = $total_espece2_orders;
$total_virement_bancaire1 = $total_virement_bancaire1_orders;
$total_virement_bancaire2 = $total_virement_bancaire2_orders;
// Totaux combinés Orders
$total_mvola_orders_combined = $total_mvola1 + $total_mvola2;
$total_espece_orders_combined = $total_espece1 + $total_espece2;
$total_virement_bancaire_orders_combined = $total_virement_bancaire1 + $total_virement_bancaire2;
// Totaux finaux (Orders + Avances)
$total_mvola = $total_mvola_orders_combined + $total_mvola_avances;
$total_espece = $total_espece_orders_combined + $total_espece_avances;
$total_virement_bancaire = $total_virement_bancaire_orders_combined + $total_virement_bancaire_avances;
// Ajustements avec les recouvrements et sorties caisse
$total_mvola_final = $total_mvola - $total_recouvrement_me - $total_recouvrement_mb + $total_recouvrement_bm;
$total_espece_final = $total_espece + $total_recouvrement_me + $total_recouvrement_be - $total_sortie_caisse1;
$total_virement_bancaire_final = $total_virement_bancaire - $total_recouvrement_be - $total_recouvrement_bm + $total_recouvrement_mb;
// === 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;
// === 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();
$data = [
'total' => $total,
'total' => $total_final,
'total_mvola' => $total_mvola_final,
'total_espece' => $total_espece_final,
'total_virement_bancaire' => $total_virement_bancaire_final,
'user_permission' => $this->permission,
];
$data['total_products'] = $productModel->countTotalProducts();
@ -144,10 +152,10 @@ class Dashboard extends AdminController
// filter to keep non empty array
$filteredArray = array_filter($newData, function($item) {
return !empty($item); // Keep only non-empty arrays
return !empty($item);
});
// Re-index the array (optional, if you want sequential keys)
// Re-index the array
$userWhoSoldProducts = array_values($filteredArray);
// Count occurrences of each userId
@ -166,6 +174,7 @@ class Dashboard extends AdminController
}
$data['count_id'] = $countId;
// Check if the user is an Conseil
$session = session();
$user_id = $session->get('user');
@ -174,6 +183,7 @@ class Dashboard extends AdminController
$data['isChef'] = false;
$data['isCaissier'] = false;
$data['isMecanicien'] = false;
if ($user_id['group_name'] == "Direction" || $user_id['group_name'] == "Conseil") {
$data['is_admin'] = true;
}
@ -193,9 +203,11 @@ class Dashboard extends AdminController
if ($user_id['group_name'] == "MECANICIEN") {
$data['isMecanicien'] = true;
}
$data['page_title'] = 'Dashboard';
$data['marques_total'] = json_encode($orderModel->getTotalProductvente());
$data['marques'] = json_encode($Brancds->getName());
$Orders = new Orders();
$Products = new Products();
$Stores = new Stores();

6
app/Controllers/NotificationController.php

@ -11,23 +11,23 @@ class NotificationController extends AdminController
parent::__construct();
}
// Récupérer toutes les notifications selon les règles définies dans le modèle
public function getNotification()
{
$Notification = new Notification();
$notifications = $Notification->getNotifications();
return $this->response->setJSON($notifications);
}
// Marquer une notification comme lue
public function markAsRead(int $id)
{
$Notification = new Notification();
$Notification->markAsRead($id);
return $this->response->setJSON(['status' => 'success']);
}
// Créer une nouvelle notification
public function createNotification(string $message, string $group, ?int $store_id, ?string $link)
{
$Notification = new Notification();

777
app/Controllers/OrderController.php

File diff suppressed because it is too large

89
app/Controllers/ProductCOntroller.php

@ -159,6 +159,7 @@ class ProductCOntroller extends AdminController
$Category = new Category();
$Stores = new Stores();
$Notification = new NotificationController();
$Fourchette = new \App\Models\FourchettePrix();
$this->verifyRole('createProduct');
$data['page_title'] = $this->pageTitle;
@ -178,8 +179,16 @@ class ProductCOntroller extends AdminController
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
$upload_image = '';
$prix_min = (float)$this->request->getPost('price_min');
$prix_vente = (float)$this->request->getPost('price_vente');
// --- Vérification côté serveur ---
if ($prix_min > $prix_vente) {
session()->setFlashdata('errors', 'Le prix de vente ne peut pas être inférieur au prix minimal.');
return redirect()->to('/products/create')->withInput();
}
$upload_image = '';
$file = $this->request->getFile('product_image');
if ($file && $file->isValid() && !$file->hasMoved()) {
$uploadResult = $this->uploadImage();
@ -188,7 +197,6 @@ class ProductCOntroller extends AdminController
}
}
$product_sold = 0;
$availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0;
@ -204,7 +212,7 @@ class ProductCOntroller extends AdminController
'chasis' => $this->request->getPost('chasis'),
'store_id' => (int)$this->request->getPost('store'),
'availability' => $availability,
'prix_vente' => $this->request->getPost('price_vente'),
'prix_vente' => $prix_vente,
'date_arivage' => $this->request->getPost('datea'),
'puissance' => $this->request->getPost('puissance'),
'cler' => $this->request->getPost('cler'),
@ -213,7 +221,7 @@ class ProductCOntroller extends AdminController
'infoManquekit' => $this->request->getPost('infoManquekit'),
'info' => $this->request->getPost('info'),
'infoManque' => $this->request->getPost('infoManque'),
'product_sold' => $product_sold,
'product_sold' => 0,
'type' => $this->request->getPost('type')
];
@ -221,19 +229,23 @@ class ProductCOntroller extends AdminController
$productId = $Products->insert($data);
if ($productId) {
$data_fourchette = [
$Fourchette->createFourchettePrix([
'product_id' => $productId,
'prix_minimal' => $this->request->getPost('price_min'),
];
$Fourchette = new FourchettePrix();
'prix_minimal' => $prix_min,
]);
$Fourchette->createFourchettePrix($data_fourchette);
session()->setFlashdata('success', 'Créé avec succès');
$Notification->createNotification("Un nouveau Produit a été crée", "COMMERCIALE",$store_id1,'product/');
$Notification->createNotification(
"Un nouveau Produit a été créé",
"COMMERCIALE",
$store_id1,
'product/'
);
return redirect()->to('/products');
} else {
session()->setFlashdata('errors', 'Error occurred while creating the product');
return redirect()->to('products/create');
session()->setFlashdata('errors', 'Erreur lors de la création du produit');
return redirect()->to('products/create')->withInput();
}
} else {
$data = [
@ -248,6 +260,7 @@ class ProductCOntroller extends AdminController
}
}
private function uploadImage()
{
$uploadPath = 'assets/images/product_image';
@ -284,9 +297,10 @@ class ProductCOntroller extends AdminController
$Products = new Products();
$Stores = new Stores();
$Category = new Category();
$this->verifyRole('updateProduct');
$data['page_title'] = $this->pageTitle;
$Brands = new Brands();
$FourchettePrix = new \App\Models\FourchettePrix(); // Modèle FourchettePrix
$this->verifyRole('updateProduct');
$validation = \Config\Services::validation();
$validation->setRules([
@ -294,7 +308,13 @@ class ProductCOntroller extends AdminController
'marque' => 'required',
]);
// Récupérer produit et prix minimal
$product_data = $Products->getProductData($id);
$prix_minimal_data = $FourchettePrix->where('product_id', $id)->first();
$prix_minimal = $prix_minimal_data['prix_minimal'] ?? '';
if ($this->request->getMethod() === 'post' && $validation->withRequest($this->request)->run()) {
$availabilityValue = (int)$this->request->getPost('availability');
$availability = ($availabilityValue === 1) ? 1 : 0;
@ -321,33 +341,60 @@ class ProductCOntroller extends AdminController
'type' => $this->request->getPost('type'),
];
// ---- GESTION PRIX MINIMAL ----
$prix_minimal_saisi = (float)$this->request->getPost('price_min');
$prix_vente = (float)$this->request->getPost('price_vente');
// Vérification serveur : prix minimal <= prix de vente
if ($prix_minimal_saisi > 0 && $prix_vente < $prix_minimal_saisi) {
session()->setFlashdata('errors', 'Le prix de vente ne peut pas être inférieur au prix minimal.');
return redirect()->to('/products/update/' . $id)->withInput();
}
// Sauvegarde ou mise à jour du prix minimal
if ($prix_minimal_data) {
$FourchettePrix->update($prix_minimal_data['id'], [
'prix_minimal' => $prix_minimal_saisi
]);
} else {
$FourchettePrix->insert([
'product_id' => $id,
'prix_minimal' => $prix_minimal_saisi
]);
}
// Upload image si fournie
if ($this->request->getFile('product_image')->isValid()) {
$uploadImage = $this->uploadImage();
$uploadData = ['image' => $uploadImage];
$Products->update($id, $uploadData);
$Products->update($id, ['image' => $uploadImage]);
}
// Mise à jour du produit
if ($Products->updateProduct($data, $id)) {
session()->setFlashdata('success', 'Successfully updated');
session()->setFlashdata('success', 'Produit mis à jour avec succès.');
return redirect()->to('/products');
} else {
session()->setFlashdata('errors', 'Error occurred!!');
session()->setFlashdata('errors', 'Une erreur est survenue lors de la mise à jour.');
return redirect()->to('/products/update/' . $id);
}
} else {
// Affichage du formulaire
$data = [
'stores' => $Stores->getActiveStore(),
'validation' => $validation,
'page_title' => $this->pageTitle,
'product_data' => $Products->getProductData($id),
'product_data' => $product_data,
'categorie' => $Category->getActiveCategory(),
'marque' => $Brands->getActiveBrands()
'marque' => $Brands->getActiveBrands(),
'prix_minimal' => $prix_minimal
];
return $this->render_template('products/editbackup', $data);
}
}
public function remove()
{
$this->verifyRole('deleteProduct');
@ -418,6 +465,7 @@ class ProductCOntroller extends AdminController
'prix d\'achat' => 'price',
'prix ar' => 'prix_vente',
'catégories' => 'categorie_id',
'prix minimale' => 'prix_minimal',
'magasin' => 'store_id',
'disponibilité' => 'availability',
'état' => 'etats',
@ -565,4 +613,5 @@ class ProductCOntroller extends AdminController
]);
}
}
}

82
app/Controllers/RecouvrementController.php

@ -51,44 +51,65 @@ class RecouvrementController extends AdminController
// Récupère les données brutes
$paymentDataOrders = $orders->getPaymentModes();
$paymentDataAvance = $avance->getPaymentModesAvance();
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse()->mr ?? 0;
$totalRecouvrement = $recouvrement->getTotalRecouvrements();
// Totaux recouvrement
$me = $totalRecouvrement->me ?? 0;
$bm = $totalRecouvrement->bm ?? 0;
$be = $totalRecouvrement->be ?? 0;
$mb = $totalRecouvrement->mb ?? 0;
// Totaux paiements Orders
$total_orders = $paymentDataOrders->total ?? 0;
$mv1_orders = $paymentDataOrders->total_mvola1 ?? 0;
$mv2_orders = $paymentDataOrders->total_mvola2 ?? 0;
$es1_orders = $paymentDataOrders->total_espece1 ?? 0;
$es2_orders = $paymentDataOrders->total_espece2 ?? 0;
$vb1_orders = $paymentDataOrders->total_virement_bancaire1 ?? 0;
$vb2_orders = $paymentDataOrders->total_virement_bancaire2 ?? 0;
// Totaux paiements Avances
$total_avances = $paymentDataAvance->total ?? 0;
$mv_avances = $paymentDataAvance->total_mvola ?? 0;
$es_avances = $paymentDataAvance->total_espece ?? 0;
$vb_avances = $paymentDataAvance->total_virement_bancaire ?? 0;
$ta = $avance->getTotalAvance()->ta ?? 0;
// Combinaison Orders + Avances
$total_sortie_caisse = $sortieCaisse->getTotalSortieCaisse();
// === EXTRACTION DES SORTIES PAR MODE DE PAIEMENT ===
$total_sortie_espece = isset($total_sortie_caisse->total_espece) ? (float) $total_sortie_caisse->total_espece : 0;
$total_sortie_mvola = isset($total_sortie_caisse->total_mvola) ? (float) $total_sortie_caisse->total_mvola : 0;
$total_sortie_virement = isset($total_sortie_caisse->total_virement) ? (float) $total_sortie_caisse->total_virement : 0;
// === TOTAUX RECOUVREMENT ===
$me = isset($totalRecouvrement->me) ? (float) $totalRecouvrement->me : 0;
$bm = isset($totalRecouvrement->bm) ? (float) $totalRecouvrement->bm : 0;
$be = isset($totalRecouvrement->be) ? (float) $totalRecouvrement->be : 0;
$mb = isset($totalRecouvrement->mb) ? (float) $totalRecouvrement->mb : 0;
// === TOTAUX PAIEMENTS ORDERS ===
$total_orders = isset($paymentDataOrders->total) ? (float) $paymentDataOrders->total : 0;
$mv1_orders = isset($paymentDataOrders->total_mvola1) ? (float) $paymentDataOrders->total_mvola1 : 0;
$mv2_orders = isset($paymentDataOrders->total_mvola2) ? (float) $paymentDataOrders->total_mvola2 : 0;
$es1_orders = isset($paymentDataOrders->total_espece1) ? (float) $paymentDataOrders->total_espece1 : 0;
$es2_orders = isset($paymentDataOrders->total_espece2) ? (float) $paymentDataOrders->total_espece2 : 0;
$vb1_orders = isset($paymentDataOrders->total_virement_bancaire1) ? (float) $paymentDataOrders->total_virement_bancaire1 : 0;
$vb2_orders = isset($paymentDataOrders->total_virement_bancaire2) ? (float) $paymentDataOrders->total_virement_bancaire2 : 0;
// === TOTAUX PAIEMENTS AVANCES ===
$total_avances = isset($paymentDataAvance->total) ? (float) $paymentDataAvance->total : 0;
$mv_avances = isset($paymentDataAvance->total_mvola) ? (float) $paymentDataAvance->total_mvola : 0;
$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;
// Ajustements avec recouvrements et sorties caisse
$total_mvola_final = $total_mvola - $me - $mb + $bm;
$total_espece_final = $total_espece + $me + $be - $total_sortie_caisse;
$total_virement_bancaire_final = $total_vb - $be - $bm + $mb;
// === 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;
// === CALCUL DU TOTAL GÉNÉRAL ===
$total_sortie_global = $total_sortie_espece + $total_sortie_mvola + $total_sortie_virement;
$total_final = $total - $total_sortie_global;
return [
'total' => $total,
'total' => $total_final,
'total_mvola' => $total_mvola_final,
'total_espece' => $total_espece_final,
'total_virement_bancaire' => $total_virement_bancaire_final,
@ -96,7 +117,6 @@ class RecouvrementController extends AdminController
'permission' => $this->permission
];
}
public function fetchRecouvrementData()
{
helper(['url', 'form']);

60
app/Controllers/RemiseController.php

@ -38,7 +38,7 @@ class RemiseController extends AdminController
public function fetchRemiseData()
{
helper(['url', 'form']);
$Remise = new Remise(); // Assure-toi du bon namespace
$Remise = new Remise();
$draw = intval($this->request->getVar('draw'));
$data = $Remise->getAllDemandeRemiseToday();
@ -64,7 +64,7 @@ class RemiseController extends AdminController
$buttons .= '<i class="fa fa-times-circle"></i>';
$buttons .= '</button>';
}
// die(var_dump($value));
$result['data'][$key] = [
$value['id_demande'],
$value['product'],
@ -79,12 +79,11 @@ class RemiseController extends AdminController
}
public function updateRemise($id_demande) {
public function updateRemise($id_demande)
{
$this->verifyRole('validateRemise');
// Load validation service
$validation = \Config\Services::validation();
$data['page_title'] = $this->pageTitle;
$validation->setRules([
@ -94,15 +93,15 @@ class RemiseController extends AdminController
$validationData = [
'demande_status' => $this->request->getPost('demande_status')
];
$data = [
'demande_status' => $this->request->getPost('demande_status')
];
$Remise = new Remise();
if ($this->request->getMethod() == 'post') {
$today = date('Y-m-d');
$demande_status = $this->request->getPost('demande_status');
$data = [
'demande_status' => $this->request->getPost('demande_status'),
'demande_status' => $demande_status,
'date_demande' => $today,
];
@ -111,10 +110,47 @@ class RemiseController extends AdminController
$Notification = new NotificationController();
$session = session();
$users = $session->get('user');
$Notification->createNotification("Une demande de remise a été " . $this->request->getPost('demande_status')." Pour le produit: ".$remise_product,"Caissière",(int)$users['store_id'],'remise/');
$ordersModel = new Orders();
$order_id = $Remise->getOrderIdByDemandeId($id_demande);
// Logique de notification selon le statut
if ($demande_status == 'Refusé') {
// Mettre à jour le paid_status de la commande à 0 (Refusé)
if ($order_id) {
$ordersModel->update($order_id, ['paid_status' => 0]);
}
// Si refusé par le Conseil : notification retourne au Commercial
$Notification->createNotification(
"Votre demande de remise a été refusée pour le produit: " . $remise_product . ". Veuillez modifier la commande.",
"COMMERCIALE",
(int)$users['store_id'],
"orders" // Retour à l'ajout de commande chez le commercial
);
$message = 'La demande de remise a été refusée. Une notification a été envoyée au commercial.';
} elseif ($demande_status == 'Accepté' || $demande_status == 'Validé') {
// Si accepté par le Conseil : la commande reste "En Attente" (paid_status = 2)
// La caissière devra la valider manuellement
if ($order_id) {
$ordersModel->update($order_id, ['paid_status' => 2]); // 2 = En Attente
}
// Notification à la Caissière pour qu'elle valide la commande
$Notification->createNotification(
"Une commande avec remise acceptée est prête pour validation. Produit: " . $remise_product,
"Caissière",
(int)$users['store_id'],
"orders" // Va chez la caissière pour validation finale
);
$message = 'La demande de remise a été acceptée. La commande a été envoyée à la caissière pour validation.';
}
return $this->response->setJSON([
'success' => true,
'messages' => 'Vous avez '.$this->request->getPost('demande_status').' la remise'
'messages' => $message
]);
} else {
return $this->response->setJSON([
@ -123,7 +159,5 @@ class RemiseController extends AdminController
]);
}
}
}
}

75
app/Controllers/ReportController.php

@ -39,17 +39,21 @@ class ReportController extends AdminController
// Process the parking data and calculate total amounts
$final_parking_data = [];
foreach ($parking_data as $month => $orders) {
$total_amount_earned = 0; // Initialize the total amount for the month
$total_amount_earned = 0;
if (!empty($orders)) {
foreach ($orders as $order) {
$total_amount_earned += (float) $order['net_amount'];
// Utiliser le montant selon la logique : discount si existe, sinon gross_amount
if (!empty($order['discount']) && $order['discount'] > 0) {
$total_amount_earned += (float) $order['discount'];
} else {
$total_amount_earned += (float) $order['gross_amount'];
}
}
}
$final_parking_data[$month] = $total_amount_earned;
}
// Data for the camembert (pie chart)
$paymentModes = $Orders->getPaymentModes();
$total_mvola1 = $paymentModes->total_mvola1;
@ -256,77 +260,74 @@ class ReportController extends AdminController
$Orders = new Orders();
$session = session();
$users = $session->get('user');
if ($users['group_name'] === "Conseil" || $users['group_name'] === "Direction" ) {
// Pour Direction et Conseil : afficher TOUTES les performances
if ($users['group_name'] === "DAF" || $users['group_name'] === "Direction") {
$orderPaid = $Orders->getPerformanceByOrders();
foreach ($orderPaid as $key => $value) {
$benefice =
// Déterminer le prix de vente réel
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
? $value['discount']
: $value['prix_vente'];
// Calculer le bénéfice
$benefice = $prix_vente_reel - $value['price'];
$result['data'][$key] = [
$value['firstname'] . ' ' . $value['lastname'],
$value['email'],
($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('Y-m-d'),
number_format($value['price'], 0, '.', ' '),
number_format($value['prix_vente'],0,'.',' '),
number_format($prix_vente_reel, 0, '.', ' '),
$this->returnName($value['store_id']),
number_format($value['prix_vente'] - $value['price'],0,'.',' '),
number_format($benefice, 0, '.', ' '),
];
}
// Return data in JSON format
return $this->response->setJSON($result);
}
if ($users['group_name'] === "Conseil" || $users['group_name'] === "Direction") {
// Pour Cheffe d'Agence : performances de son magasin
if ($users['group_name'] === "Cheffe d'Agence") {
$orderPaid = $Orders->getPerformanceByOrders1();
foreach ($orderPaid as $key => $value) {
$benefice =
// Déterminer le prix de vente réel
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
? $value['discount']
: $value['prix_vente'];
$result['data'][$key] = [
$value['firstname'] . ' ' . $value['lastname'],
$value['email'],
($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('Y-m-d'),
number_format($value['price'],0,'.',' '),
number_format($value['prix_vente'],0,'.',' '),
$this->returnName($value['store_id']),
number_format($value['prix_vente'] - $value['price'],0,'.',' '),
number_format($prix_vente_reel, 0, '.', ' '),
];
}
// Return data in JSON format
return $this->response->setJSON($result);
}
// Pour COMMERCIALE : uniquement ses propres ventes
if ($users['group_name'] === "COMMERCIALE") {
$orderPaid = $Orders->getPerformanceByOrders2();
foreach ($orderPaid as $key => $value) {
$benefice =
// Déterminer le prix de vente réel
$prix_vente_reel = (!empty($value['discount']) && $value['discount'] > 0)
? $value['discount']
: $value['prix_vente'];
$result['data'][$key] = [
$value['firstname'] . ' ' . $value['lastname'],
($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('Y-m-d'),
number_format($value['prix_vente'],0,'.',' '),
number_format($prix_vente_reel, 0, '.', ' '),
];
}
// Return data in JSON format
return $this->response->setJSON($result);
}
if ($users['group_name'] === "Cheffe d'Agence") {
$orderPaid = $Orders->getPerformanceByOrders1();
foreach ($orderPaid as $key => $value) {
$benefice =
$result['data'][$key] = [
$value['firstname'] . ' ' . $value['lastname'],
($value['sku'] == "" ? $value['motoname'] : $value['sku']),
(new DateTime($value['datevente']))->format('Y-m-d'),
number_format($value['prix_vente'],0,'.',' '),
];
}
// Return data in JSON format
// Retour par défaut si aucun rôle ne correspond
return $this->response->setJSON($result);
}
}
public function fetchmecperformance()
{

1177
app/Controllers/SortieCaisseController.php

File diff suppressed because it is too large

2
app/Models/Avance.php

@ -11,7 +11,7 @@ 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' // Ajout du champ type et deadline
'type_avance','type_payment', 'deadline','commentaire','product_name' // Ajout du champ type et deadline
];
public function createAvance(array $data) {

10
app/Models/Notification.php

@ -15,12 +15,20 @@ class Notification extends Model
$session = session();
$users = $session->get('user');
$today = date('Y-m-d');
return $this->where('store_id', $users['store_id'])
->groupStart()
->where('forgroup', $users['group_name'])
->orWhere('forgroup', strtolower('TOUS'))
->groupEnd()
->where('is_read', 0)
->groupStart()
->where('is_read', 0) // toutes les notifications non lues
->orGroupStart()
->where('is_read', 1) // notifications lues
->where('DATE(created_at)', $today) // mais seulement celles d’aujourd’hui
->groupEnd()
->groupEnd()
->orderBy('created_at', 'DESC')
->findAll();
}

233
app/Models/Orders.php

@ -5,6 +5,7 @@ namespace App\Models;
use CodeIgniter\Model;
use App\Models\Products;
use App\Models\OrderItems;
use App\Models\FourchettePrix;
class Orders extends Model
{
@ -32,7 +33,9 @@ class Orders extends Model
'tranche_1',
'tranche_2',
'order_payment_mode',
'order_payment_mode_1'
'order_payment_mode_1',
'delivered_at',
'delivered_by'
];
@ -76,11 +79,23 @@ class Orders extends Model
public function getOrdersData(int $id = null)
{
// Récupération des infos de session
$session = session();
$user = $session->get('user');
// Construction de la requête avec jointures et agrégation des noms de produits
// Si on demande UNE commande spécifique (pas de GROUP_CONCAT)
if ($id !== null) {
return $this->db->table('orders')
->select([
'orders.*',
"CONCAT(users.firstname, ' ', users.lastname) AS user_name"
])
->join('users', 'orders.user_id = users.id', 'left')
->where('orders.id', $id)
->get()
->getRowArray();
}
// Pour la LISTE des commandes (avec GROUP_CONCAT)
$builder = $this->db->table('orders')
->select([
'orders.id',
@ -96,28 +111,19 @@ class Orders extends Model
'orders.net_amount',
'orders.paid_status',
'orders.user_id',
"CONCAT(users.firstname, ' ', users.lastname) AS user_name",
"CONCAT(users.firstname, ' ', users.lastname) AS user_name"
])
->join('users', 'orders.user_id = users.id', 'left')
->join('orders_item', 'orders_item.order_id = orders.id', 'left')
->join('products', 'products.id = orders_item.product_id', 'left')
// Sépare chaque nom de produit par un saut de ligne '\n'
->select("GROUP_CONCAT(products.name SEPARATOR '\\n') AS product_names", false)
->select("IFNULL(GROUP_CONCAT(DISTINCT products.name SEPARATOR '\\n'), '') AS product_names", false)
->groupBy('orders.id');
// Si on demande une commande précise
if ($id !== null) {
return $builder
->where('orders.id', $id)
->get()
->getRowArray();
}
// Récupération du groupe de l'utilisateur
$group = $this->getUserGroupNameByUserId($user['id']);
$groupName = $group['group_name'] ?? '';
// Selon le rôle, on affiche toutes les commandes ou seulement celles du magasin
// Selon le rôle
if (in_array($groupName, ['Direction', 'Conseil'], true)) {
return $builder
->orderBy('orders.id', 'DESC')
@ -125,7 +131,6 @@ class Orders extends Model
->getResultArray();
}
// Autres utilisateurs : filtrage par magasin
return $builder
->where('orders.store_id', $user['store_id'])
->orderBy('orders.id', 'DESC')
@ -133,8 +138,6 @@ class Orders extends Model
->getResultArray();
}
public function getOrdersDataByBillNo(string $id = null)
{
$session = session();
@ -356,7 +359,7 @@ class Orders extends Model
*/
public function countTotalPaidOrders()
{
return $this->where('paid_status', 1)->countAllResults();
return $this->whereIn('paid_status', [1, 3])->countAllResults();
}
public function getPaidOrderData()
@ -364,14 +367,17 @@ class Orders extends Model
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin) {
return $this->where('paid_status', 1)->orderBy('id', 'DESC')->findAll();
return $this->whereIn('paid_status', [1, 3]) // ← Ajoutez 3 ici
->orderBy('id', 'DESC')
->findAll();
}
else {
return $this->where('paid_status', 1)
return $this->whereIn('paid_status', [1, 3]) // ← Ajoutez 3 ici
->where('store_id', $users['store_id'])
->orderBy('id', 'DESC')->findAll();
->orderBy('id', 'DESC')
->findAll();
}
}
@ -388,43 +394,84 @@ class Orders extends Model
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if($isAdmin){
return $this->db->table('orders')
->select('
SUM(net_amount) AS total,
SUM(CASE WHEN orders.order_payment_mode = "MVOLA" THEN tranche_1 ELSE 0 END) AS total_mvola1,
SUM(CASE WHEN orders.order_payment_mode = "MVOLA" THEN tranche_2 ELSE 0 END) AS total_mvola2,
SUM(CASE WHEN orders.order_payment_mode = "En espèce" THEN tranche_1 ELSE 0 END) AS total_espece1,
SUM(CASE WHEN orders.order_payment_mode = "En espèce" THEN tranche_2 ELSE 0 END) AS total_espece2,
SUM(CASE WHEN orders.order_payment_mode = "Virement bancaire" THEN tranche_1 ELSE 0 END) AS total_virement_bancaire1,
SUM(CASE WHEN orders.order_payment_mode = "Virement bancaire" THEN tranche_2 ELSE 0 END) AS total_virement_bancaire2
')
->where('orders.paid_status', 1)
->get()
->getRowObject();
}
else{
return $this->db->table('orders')
->select('
SUM(net_amount) AS total,
SUM(CASE WHEN orders.order_payment_mode = "MVOLA" THEN tranche_1 ELSE 0 END) AS total_mvola1,
SUM(CASE WHEN orders.order_payment_mode = "MVOLA" THEN tranche_2 ELSE 0 END) AS total_mvola2,
SUM(CASE WHEN orders.order_payment_mode = "En espèce" THEN tranche_1 ELSE 0 END) AS total_espece1,
SUM(CASE WHEN orders.order_payment_mode = "En espèce" THEN tranche_2 ELSE 0 END) AS total_espece2,
SUM(CASE WHEN orders.order_payment_mode = "Virement bancaire" THEN tranche_1 ELSE 0 END) AS total_virement_bancaire1,
SUM(CASE WHEN orders.order_payment_mode = "Virement bancaire" THEN tranche_2 ELSE 0 END) AS total_virement_bancaire2
')
->where('orders.paid_status', 1)
->where('orders.store_id', $users['store_id'])
->get()
->getRowObject();
}
}
$baseQuery = $this->db->table('orders')
->select('
SUM(CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN orders.discount
ELSE orders.net_amount
END) AS total,
SUM(CASE
WHEN orders.order_payment_mode = "MVOLA" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_1 * orders.discount / orders.net_amount)
ELSE orders.tranche_1
END
ELSE 0
END) AS total_mvola1,
SUM(CASE
WHEN orders.order_payment_mode_1 = "MVOLA" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_2 * orders.discount / orders.net_amount)
ELSE orders.tranche_2
END
ELSE 0
END) AS total_mvola2,
SUM(CASE
WHEN orders.order_payment_mode = "En espèce" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_1 * orders.discount / orders.net_amount)
ELSE orders.tranche_1
END
ELSE 0
END) AS total_espece1,
SUM(CASE
WHEN orders.order_payment_mode_1 = "En espèce" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_2 * orders.discount / orders.net_amount)
ELSE orders.tranche_2
END
ELSE 0
END) AS total_espece2,
SUM(CASE
WHEN orders.order_payment_mode = "Virement bancaire" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_1 * orders.discount / orders.net_amount)
ELSE orders.tranche_1
END
ELSE 0
END) AS total_virement_bancaire1,
SUM(CASE
WHEN orders.order_payment_mode_1 = "Virement bancaire" THEN
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN (orders.tranche_2 * orders.discount / orders.net_amount)
ELSE orders.tranche_2
END
ELSE 0
END) AS total_virement_bancaire2
')
->whereIn('orders.paid_status', [1, 3]); // ← CHANGEZ CETTE LIGNE
if (!$isAdmin) {
$baseQuery->where('orders.store_id', $users['store_id']);
}
return $baseQuery->get()->getRowObject();
}
public function getTotalOrders()
{
@ -497,9 +544,9 @@ class Orders extends Model
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'orders_item.product_id = products.id')
->join('brands', 'brands.id = products.marque')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3]) // ← CHANGÉ ICI
->get()
->getResult(); // Return a single object
->getResult();
return $order;
}
@ -512,34 +559,43 @@ class Orders extends Model
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'orders_item.product_id = products.id')
->join('brands', 'brands.id = products.marque')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3]) // ← CHANGÉ ICI
->where('orders.store_id', $id)
->get()
->getResult(); // Return a single object
->getResult();
} else {
$order = $this->db->table('orders')
->select('orders.id, orders.paid_status, orders.store_id, orders.date_time, brands.name, products.name as Pname, products.sku')
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'orders_item.product_id = products.id')
->join('brands', 'brands.id = products.marque')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3]) // ← CHANGÉ ICI
->get()
->getResult(); // Return a single object
->getResult();
}
return $order;
}
public function getOrderVendue()
{
$order = $this->db->table('products')
->select('products.*, products.name as Pname, products.sku, (orders_item.amount - orders.discount) as totalNet, orders.date_time as DateTime')
->select('
products.*,
products.name as Pname,
products.sku,
CASE
WHEN orders.discount > 0 AND orders.discount IS NOT NULL
THEN orders.discount
ELSE orders.gross_amount
END as totalNet,
orders.date_time as DateTime
', false) // ← MODIFIÉ ICI
->join('orders_item', 'products.id = orders_item.product_id')
->join('orders', 'orders_item.order_id = orders.id')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3]) // ← ET ICI
->get()
->getResultArray(); // Return a single object
->getResultArray();
return $order;
}
@ -552,12 +608,13 @@ class Orders extends Model
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'products.id = orders_item.product_id')
->join('users', 'users.id = orders.user_id')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3])
->get()
->getResultArray();
return $Performance;
}
// for Adminan cheffe d'agence
public function getPerformanceByOrders1()
{
@ -570,7 +627,7 @@ class Orders extends Model
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'products.id = orders_item.product_id')
->join('users', 'users.id = orders.user_id')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3])
->where('orders.store_id', $users["store_id"])
->get()
->getResultArray();
@ -590,7 +647,7 @@ class Orders extends Model
->join('orders_item', 'orders.id = orders_item.order_id')
->join('products', 'products.id = orders_item.product_id')
->join('users', 'users.id = orders.user_id')
->where('orders.paid_status', 1)
->whereIn('orders.paid_status', [1, 3])
->where('orders.user_id', $users["id"])
->get()
->getResultArray();
@ -599,7 +656,6 @@ class Orders extends Model
}
public function update1(int $id, $data1,$data2)
{
if ($id) {
@ -614,9 +670,6 @@ class Orders extends Model
}
public function getUserList()
{
$session = session();
@ -645,27 +698,24 @@ class Orders extends Model
$results = [];
foreach ($users as $user) {
// 1) Récupérer count et sum
$summary = $this->db->table('orders')
->select('COUNT(id) AS total_user_order, SUM(net_amount) AS total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time)', $date)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
// 2) Récupérer la liste des order_id
$ids = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time)', $date)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
// 3) Composer le résultat pour cet utilisateur
$results[] = [
'user_id' => $user->user_id,
'full_name' => $user->full_name,
@ -678,46 +728,40 @@ class Orders extends Model
return $results;
}
public function getUserPerformanceByWeek(string $date = null)
{
$users = $this->getUserList();
$results = [];
// Si aucune date fournie, on prend aujourd'hui
if ($date === null) {
$date = date('Y-m-d');
}
// Trouver le lundi et le dimanche de la semaine de $date
$timestamp = strtotime($date);
$startOfWeek = date('Y-m-d', strtotime('monday this week', $timestamp));
$endOfWeek = date('Y-m-d', strtotime('sunday this week', $timestamp));
foreach ($users as $user) {
// 1) Récupérer count et sum pour la semaine
$summary = $this->db->table('orders')
->select('COUNT(id) AS total_user_order, SUM(net_amount) AS total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfWeek)
->where('DATE(date_time) <=', $endOfWeek)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
// 2) Récupérer la liste des order_id de la semaine
$ids = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfWeek)
->where('DATE(date_time) <=', $endOfWeek)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
// 3) Résultat pour l'utilisateur
$results[] = [
'user_id' => $user->user_id,
'full_name' => $user->full_name,
@ -730,13 +774,21 @@ public function getUserPerformanceByWeek(string $date = null)
return $results;
}
public function checkMinimalPrice($product_id, $discount)
{
$fourchetteModel = new FourchettePrix(); // plus court car on a "use"
$row = $fourchetteModel->where('product_id', $product_id)->first();
if ($row) {
return ($discount >= $row['prix_minimal']);
}
return true;
}
public function getUserPerformanceByMonth(string $month)
{
// Calcul du premier et dernier jour du mois donné
$startOfMonth = date('Y-m-01', strtotime($month . '-01'));
$endOfMonth = date('Y-m-t', strtotime($month . '-01'));
@ -744,29 +796,26 @@ public function getUserPerformanceByMonth(string $month)
$results = [];
foreach ($users as $user) {
// 1) Récupérer les stats du mois
$orderData = $this->db->table('orders')
->select('COUNT(id) as total_user_order, SUM(net_amount) as total_prix_vente')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfMonth)
->where('DATE(date_time) <=', $endOfMonth)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getRow();
// 2) Récupérer les IDs des commandes du mois
$ids = $this->db->table('orders')
->select('id')
->where('user_id', $user->user_id)
->where('DATE(date_time) >=', $startOfMonth)
->where('DATE(date_time) <=', $endOfMonth)
->where('paid_status', 1)
->whereIn('paid_status', [1, 3])
->get()
->getResult();
$orderIds = array_map(fn($o) => $o->id, $ids);
// 3) Composer les résultats
$results[] = [
'user_id' => $user->user_id,
'full_name' => $user->full_name,

23
app/Models/Products.php

@ -153,15 +153,32 @@ class Products extends Model
return $this->delete($id) ? true : false;
}
public function countTotalProducts()
{
$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 product
* count all products including sold and reserved (méthode originale si besoin)
*/
public function countTotalProducts()
public function countAllProductsIncludingSold()
{
return $this->countAll();
}
public function getTotalProductPriceByIds(array $productIds)
{
try {

2
app/Models/Recouvrement.php

@ -73,7 +73,7 @@ class Recouvrement extends Model{
public function getTotalRecouvrements(int $id = null, $start_date = null, $end_date = null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
$isAdmin = in_array($users['group_name'], ['DAF', 'Direction']);
if($isAdmin){
if ($id) {
return $this->db->table('recouvrement')

92
app/Models/Remise.php

@ -1,6 +1,5 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
use App\Models\Orders;
use DateTime;
@ -12,52 +11,41 @@ class Remise extends Model
* @var string
*/
protected $table = 'demande_remise';
protected $primaryKey = 'id_demande'; // Primary key column
protected $primaryKey = 'id_demande';
protected $allowedFields = ['id_order', 'montant_demande', 'date_demande','product','id_store','demande_status','total_price'];
public function getAllDemandeRemiseToday()
{
$session = session();
$users = $session->get('user');
if ($users["group_name"] === "Conseil") {
try {
$session = session();
$users = $session->get('user');
$users = session()->get('user_data');
if($users['group_name'] === "Direction"){
// Si le rôle est CONSEIL → voir toutes les demandes "En attente"
if ($users["group_name"] === "Conseil") {
return $this->where('demande_status', 'En attente')
->orderBy('date_demande', 'DESC')
->findAll();
}
return $this->where('demande_status', 'En attente')
->orderBy('date_demande', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des demandes du jour : ' . $e->getMessage());
return [];
}
// Si le rôle est DIRECTION → voir toutes les demandes aussi (pas filtrées par magasin)
if ($users["group_name"] === "Direction") {
return $this->orderBy('date_demande', 'DESC')
->findAll();
}
try {
$session = session();
$users = $session->get('user');
return $this
->where('id_store',$users['store_id'])
// Autres rôles (Caissière, etc.) → voir uniquement celles de son magasin
return $this->where('id_store', $users['store_id'])
->orderBy('date_demande', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des demandes du jour : ' . $e->getMessage());
return [];
}
}
public function addDemande( array $data) {
public function addDemande(array $data)
{
try {
return $this->insert($data);
} catch (\Exception $e) {
@ -66,48 +54,51 @@ class Remise extends Model
}
}
public function getRemiseData1(int $remise_id){
return $this->select('montant_demande','id_order')
public function getRemiseData1(int $remise_id)
{
return $this->select('montant_demande, id_order')
->where('id_demande', $remise_id)
->first();
}
public function updateRemise( $id,$data) {
/**
* Récupère l'ID de la commande associée à une demande de remise
* @param int $id_demande
* @return int|null
*/
public function getOrderIdByDemandeId(int $id_demande): ?int
{
$result = $this->select('id_order')
->where('id_demande', $id_demande)
->first();
return $result['id_order'] ?? null;
}
public function updateRemise($id, $data)
{
if ($id <= 0) {
log_message('error', 'ID invalide pour la mise à jour du recouvrement : ' . $id);
log_message('error', 'ID invalide pour la mise à jour de la demande : ' . $id);
return false;
}
try {
// Essayer de mettre à jour les données
return $this->update($id, $data);
if ($data['demande_status'] == 'accepte') {
$Order = new Orders();
$remise_data = $this->getRemiseData1($id);
$montant_demande = $remise_data->montant_demande;
$order_id = $remise_data->id_order;
$data1 = [
'net_amount' => $montant_demande,
'gross_amount' => $montant_demande
];
$data2 = [
'rate' => $montant_demande,
'amount' => $montant_demande
];
$Order->update1($order_id, $data1,$data2);
}
// Mettre à jour uniquement les données de la demande de remise
// On ne touche PAS aux montants de la commande ici
$updateResult = $this->update($id, $data);
return $updateResult;
} catch (\Exception $e) {
// En cas d'exception, loguer l'erreur
log_message('error', 'Erreur lors de la mise à jour du recouvrement : ' . $e->getMessage());
log_message('error', 'Erreur lors de la mise à jour de la demande : ' . $e->getMessage());
return false;
}
}
public function getProductByDemandeId(int $id_demande): ?string
{
$row = $this
->select('product')
->where('id_order', $id_demande)
$row = $this->select('product')
->where('id_demande', $id_demande)
->first();
return $row['product'] ?? null;
@ -127,5 +118,4 @@ class Remise extends Model
return "Nouvelle remise créée avec succès.";
}
}
}

17
app/Models/Reports.php

@ -1,7 +1,5 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class Reports extends Model
@ -22,12 +20,12 @@ class Reports extends Model
}
/**
* Getting the year of the orders (paid_status = 1)
* Getting the year of the orders (paid_status = 1 or 3)
* @return array
*/
public function getOrderYear()
{
$result = $this->where('paid_status', 1)->findAll();
$result = $this->whereIn('paid_status', [1, 3])->findAll(); // ← CHANGÉ ICI
$return_data = array_map(function($v) {
return date('Y', strtotime($v['date_time']));
@ -46,8 +44,8 @@ class Reports extends Model
if ($year) {
$months = $this->months();
// Fetch orders with paid_status = 1
$result = $this->where('paid_status', 1)->findAll();
// Fetch orders with paid_status = 1 or 3
$result = $this->whereIn('paid_status', [1, 3])->findAll();
$final_data = [];
@ -58,12 +56,17 @@ class Reports extends Model
foreach ($result as $v) {
$month_year = date('Y-m', strtotime($v['date_time']));
if ($get_mon_year == $month_year) {
// Modifier le montant selon votre logique
if (!empty($v['discount']) && $v['discount'] > 0) {
$v['amount_to_display'] = $v['discount']; // Utiliser le rabais
} else {
$v['amount_to_display'] = $v['gross_amount']; // Utiliser gross_amount
}
$final_data[$get_mon_year][] = $v;
}
}
}
// die(var_dump($final_data));
return $final_data;
}

78
app/Models/SortieCaisse.php

@ -11,6 +11,9 @@ class SortieCaisse extends Model
*/
protected $table = 'sortie_caisse';
protected $primaryKey = 'id_sortie'; // Primary key column
// Dans le fichier App/Models/SortieCaisse.php
// Mettre à jour le tableau $allowedFields
protected $allowedFields = [
'montant_retire',
'date_retrait',
@ -29,7 +32,35 @@ class SortieCaisse extends Model
'user_id',
'store_id',
'mime_type',
'admin_raison'
'admin_raison',
// Champs pour le formulaire IM1
'nom_demandeur',
'fonction_demandeur',
'mode_paiement',
'montant_lettre',
'date_demande',
'reference',
// ✅ NOUVEAU CHAMP
'date_paiement_effectif', // Date à laquelle la caissière a effectué le paiement
// Champs pour le formulaire IM2/IM3
'numero_fiche',
'date_fiche',
'service_demandeur',
'objet_depense',
'nature_depense',
'montant_estime',
'mode_reglement',
'date_paiement_prevue',
'justificatifs',
'visa_demandeur',
'date_visa_demandeur',
'visa_chef_service',
'date_visa_chef_service',
'visa_direction',
'date_visa_direction',
'visa_conseil',
'date_visa_conseil',
'observations'
];
public function getAllSortieCaisse()
{
@ -109,8 +140,6 @@ public function getAllSortieCaisse1()
}
}
public function addSortieCaisse(array $data) {
try {
return $this->insert($data);
@ -121,17 +150,14 @@ public function getAllSortieCaisse1()
}
public function updateSortieCaisse(int $id, array $data) {
// Vérifier si l'ID est valide (par exemple, positif et non nul)
if ($id <= 0) {
log_message('error', 'ID invalide pour la mise à jour de la sortie caisse : ' . $id);
return false;
}
try {
// Essayer de mettre à jour les données
return $this->update($id, $data);
} catch (\Exception $e) {
// En cas d'exception, loguer l'erreur
log_message('error', 'Erreur lors de la mise à jour du recouvrement : ' . $e->getMessage());
return false;
}
@ -142,8 +168,6 @@ public function getAllSortieCaisse1()
$reparation = $this->select('*')
->where('id_sortie', $id)
->first();
// return $this->where('user_id', $id)->findAll();
return $reparation;
}
@ -151,26 +175,48 @@ public function getAllSortieCaisse1()
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']);
if ($isAdmin) {
try {
return $this->select('
SUM(montant_retire) AS mr,
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
SUM(CASE WHEN mode_paiement = "MVOLA" THEN montant_retire ELSE 0 END) AS total_mvola,
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(montant_retire) AS mr
')
->where('statut', "Valider")
->whereIn('statut', ['Valider', 'Payé'])
->get()
->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des sorties caisse : ' . $e->getMessage());
return false;
log_message('error', 'Erreur getTotalSortieCaisse (Admin) : ' . $e->getMessage());
return (object)[
'total_espece' => 0,
'total_mvola' => 0,
'total_virement' => 0,
'mr' => 0
];
}
} else {
return $this->select('SUM(montant_retire) AS mr')
try {
return $this->select('
SUM(CASE WHEN mode_paiement = "En espèce" THEN montant_retire ELSE 0 END) AS total_espece,
SUM(CASE WHEN mode_paiement = "MVOLA" THEN montant_retire ELSE 0 END) AS total_mvola,
SUM(CASE WHEN mode_paiement = "Virement Bancaire" THEN montant_retire ELSE 0 END) AS total_virement,
SUM(montant_retire) AS mr
')
->where('store_id', $users['store_id'])
->where('statut', "Valider")
->whereIn('statut', ['Valider', 'Payé'])
->get()
->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur getTotalSortieCaisse (Store) : ' . $e->getMessage());
return (object)[
'total_espece' => 0,
'total_mvola' => 0,
'total_virement' => 0,
'mr' => 0
];
}
}
}
}

450
app/Views/avances/avance.php

@ -1,4 +1,6 @@
<!-- Content Wrapper. Contains page content -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<div class="content-wrapper">
<section class="content-header">
<h1>
@ -102,50 +104,45 @@
<div class="modal-body">
<div class="row">
<!-- type d'avance -->
<!-- <div class="form-group col-md-6">
<label for="id_product" class="form-label">Type d'avance</label>
<select name="id_product" id="id_product" class="form-control " required>
<option value="" disabled selected>Sélectionnez un type d'avance </option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
</select>
</div> -->
<!-- Type d'avance -->
<div class="form-group col-md-6">
<label for="type_avance" class="form-label">Type d'avance</label>
<select name="type_avance" id="type_avance" class="form-control" required>
<option value="" disabled selected>Sélectionnez un type d'avance</option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
<option value="mere">Avance sur mer</option>
</select>
</div>
<!-- Moyen de paiement -->
<div class="form-group col-md-6">
<label for="type_avance" class="form-label">Moyen de payment</label>
<label for="type_payment" class="form-label">Moyen de paiement</label>
<select class="form-control" id="type_payment" name="type_payment">
<option value="" disabled selected>Sélectionnez un moyen de payement</option>
<option value="" disabled selected>Sélectionnez un moyen de paiement</option>
<option value="MVOLA">MVOLA</option>
<option value="Virement Bancaire">Virement Bancaire</option>
<option value="En espèce">En espèce</option>
</select>
</div>
<!-- Nom client -->
<div class="form-group col-md-6">
<label>Nom du client</label>
<input type="text" name="customer_name_avance" id="customer_name_avance" class="form-control" required>
</div>
<!-- Téléphone client -->
<div class="form-group col-md-6">
<label>Téléphone du client</label>
<input type="text" name="customer_phone_avance" id="customer_phone_avance" class="form-control" required>
</div>
</div>
<div class="row">
<!-- Adresse client -->
<div class="form-group col-md-6">
<label>Adresse du client</label>
<input type="text" name="customer_address_avance" id="customer_address_avance" class="form-control" required>
</div>
<!-- CIN client -->
<div class="form-group col-md-6">
<label>CIN du client</label>
@ -154,10 +151,10 @@
</div>
<div class="row">
<!-- Produit -->
<div class="form-group col-md-6">
<!-- Produit avec sélection (pour "terre") -->
<div class="form-group col-md-6" id="product_select_container">
<label for="id_product" class="form-label">Produit</label>
<select name="id_product" id="id_product" class="form-control" onchange="getProductDataCreate()" required>
<select name="id_product" id="id_product" class="form-control" onchange="getProductDataCreate()">
<option value="">Sélectionnez un produit</option>
<?php foreach($products as $p): ?>
<option value="<?= $p['id'] ?>" <?= $p['product_sold'] ? 'disabled' : '' ?>>
@ -166,10 +163,17 @@
<?php endforeach; ?>
</select>
</div>
<!-- Prix brut -->
<!-- Produit avec texte libre (pour "mère") -->
<div class="form-group col-md-6" id="product_text_container" style="display:none;">
<label>Produit (à compléter)</label>
<input type="text" name="product_name_text" id="product_name_text" class="form-control" placeholder="Entrez le nom du produit">
</div>
<!-- Prix du produit -->
<div class="form-group col-md-6">
<label>Prix du produit</label>
<input type="text" name="gross_amount" id="gross_amount" class="form-control" readonly>
<input type="number" name="gross_amount" id="gross_amount" class="form-control" placeholder="Entrez le prix">
</div>
</div>
@ -177,15 +181,25 @@
<!-- Avance -->
<div class="form-group col-md-6">
<label>Avance</label>
<input type="number" name="avance_amount" id="avance_amount" class="form-control" onkeyup="updateDueCreate()" required>
<input type="number" name="avance_amount" id="avance_amount" class="form-control" placeholder="Entrez l'avance">
</div>
<!-- Reste à payer -->
<div class="form-group col-md-6">
<label>Reste à payer</label>
<input type="text" name="amount_due" id="amount_due" class="form-control" readonly>
</div>
</div>
<!-- Commentaire (affiché uniquement pour "mère") -->
<div class="row">
<div class="form-group col-md-12" id="commentaire_container" style="display:none;">
<label>Commentaire</label>
<textarea name="commentaire" id="commentaire" class="form-control" rows="3"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Enregistrer</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
@ -196,17 +210,19 @@
</div>
<?php endif; ?>
<!-- Modal Modification CORRIGÉ -->
<!-- Modal Modification -->
<?php if (in_array('updateAvance', $user_permission)): ?>
<div class="modal fade" id="updateModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<form id="update_avance_form" method="post">
<input type="hidden" name="id" id="avance_id_edit">
<div class="modal-header">
<h4 class="modal-title">Modifier une avance</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="row">
<!-- Type d'avance -->
@ -215,11 +231,11 @@
<select name="type_avance_edit" id="type_avance_edit" class="form-control" required>
<option value="" disabled>Sélectionnez un type d'avance</option>
<option value="terre">Avance sur terre</option>
<option value="mere">Avance sur mère</option>
<option value="mere">Avance sur mer</option>
</select>
</div>
<!-- CORRECTION: ID cohérent pour le type de paiement -->
<!-- Moyen de paiement -->
<div class="form-group col-md-6">
<label for="type_payment_edit" class="form-label">Moyen de paiement</label>
<select class="form-control" id="type_payment_edit" name="type_payment_edit">
@ -253,11 +269,13 @@
<label>CIN du client</label>
<input type="text" name="customer_cin_avance_edit" id="customer_cin_avance_edit" class="form-control" required>
</div>
</div>
<!-- Produit -->
<div class="form-group col-md-6">
<div class="row">
<!-- Produit avec sélection (pour "terre") -->
<div class="form-group col-md-6" id="product_select_container_edit">
<label class="form_label">Produit</label>
<select name="id_product_edit" id="id_product_edit" class="form-control" onchange="getProductDataUpdate()" required>
<select name="id_product_edit" id="id_product_edit" class="form-control" onchange="getProductDataUpdate()">
<option value="">Sélectionnez un produit</option>
<?php foreach($products as $p): ?>
<option value="<?= $p['id'] ?>" <?= $p['product_sold'] ? 'disabled' : '' ?>>
@ -268,17 +286,24 @@
</select>
</div>
<!-- Produit avec texte libre (pour "mère") -->
<div class="form-group col-md-6" id="product_text_container_edit" style="display:none;">
<label>Produit (à compléter)</label>
<input type="text" name="product_name_text_edit" id="product_name_text_edit" class="form-control" placeholder="Entrez le nom du produit">
</div>
<!-- Prix du produit -->
<div class="form-group col-md-6">
<label>Prix du produit</label>
<input type="text" name="gross_amount_edit" id="gross_amount_edit" class="form-control" readonly>
<input type="number" name="gross_amount_edit" id="gross_amount_edit" class="form-control" placeholder="Entrez le prix">
</div>
</div>
<div class="row">
<!-- Avance -->
<div class="form-group col-md-6">
<label>Avance</label>
<input type="number" name="avance_amount_edit" id="avance_amount_edit" class="form-control" onkeyup="updateDueEdit()" required>
<input type="number" name="avance_amount_edit" id="avance_amount_edit" class="form-control" placeholder="Entrez l'avance">
</div>
<!-- Reste à payer -->
@ -287,6 +312,14 @@
<input type="text" name="amount_due_edit" id="amount_due_edit" class="form-control" readonly>
</div>
</div>
<!-- Commentaire (affiché uniquement pour "mère") -->
<div class="row">
<div class="form-group col-md-12" id="commentaire_container_edit" style="display:none;">
<label>Commentaire</label>
<textarea name="commentaire_edit" id="commentaire_edit" class="form-control" rows="3"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
@ -367,10 +400,26 @@ $(document).ready(function() {
// 🔄 FONCTION DE MISE À JOUR DYNAMIQUE DE LA DATATABLE
function refreshDataTable() {
if (typeof manageTable !== 'undefined' && manageTable) {
manageTable.ajax.reload(null, false); // Recharger seulement les données de la table
manageTable.ajax.reload(null, false);
}
}
// ✅ INITIALISATION : Définir "terre" par défaut au chargement du modal création
$('#createModal').on('show.bs.modal', function() {
$('#create_avance_form')[0].reset();
// Par défaut : mode "terre"
$('#product_select_container').show();
$('#id_product').prop('required', true);
$('#product_text_container').hide();
$('#product_name_text').prop('required', false);
$('#gross_amount').prop('readonly', true).prop('type', 'text');
$('#commentaire_container').hide();
// Réattacher les événements
$('#avance_amount').off('keyup').on('keyup', updateDueCreate);
});
<?php if ($isAdmin): ?>
var adminColumns = [
{ title: "Client" },
@ -386,18 +435,18 @@ $(document).ready(function() {
<?php endif; ?>
];
var manageTable = initAvanceTable('fetchAvanceData', adminColumns);
var manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
$('#avance_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceBecameOrder', adminColumns);
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceBecameOrder', adminColumns);
});
$('#avance_expired').on('click', function () {
manageTable = initAvanceTable('fetchExpiredAvance', adminColumns);
manageTable = initAvanceTable(base_url + 'avances/fetchExpiredAvance', adminColumns);
});
$('#avance_no_order').on('click', function () {
manageTable = initAvanceTable('fetchAvanceData', adminColumns);
manageTable = initAvanceTable(base_url + 'avances/fetchAvanceData', adminColumns);
});
<?php endif; ?>
@ -424,70 +473,159 @@ $(document).ready(function() {
});
<?php endif; ?>
// =====================================================
// 🔥 GESTION DU TYPE D'AVANCE - CRÉATION
// =====================================================
$('#type_avance').on('change', function() {
var typeAvance = $(this).val();
if (typeAvance === 'mere') {
// MASQUER le select de produit
$('#product_select_container').hide();
$('#id_product').prop('required', false).val('');
// AFFICHER le champ texte pour produit
$('#product_text_container').show();
$('#product_name_text').prop('required', true);
// RENDRE le prix modifiable ET changer en type="number"
$('#gross_amount').prop('readonly', false).prop('type', 'number').val('').prop('required', true);
// AFFICHER le commentaire
$('#commentaire_container').show();
// Calcul simple : Prix - Avance
$('#avance_amount, #gross_amount').off('keyup').on('keyup', function() {
var prix = parseFloat($('#gross_amount').val()) || 0;
var avance = parseFloat($('#avance_amount').val()) || 0;
var reste = prix - avance;
$('#amount_due').val(reste >= 0 ? reste.toFixed(0) : 0);
});
} else if (typeAvance === 'terre') {
// AFFICHER le select de produit
$('#product_select_container').show();
$('#id_product').prop('required', true);
// MASQUER le champ texte
$('#product_text_container').hide();
$('#product_name_text').prop('required', false).val('');
// RENDRE le prix readonly ET en type="text"
$('#gross_amount').prop('readonly', true).prop('type', 'text').prop('required', false);
// MASQUER le commentaire
$('#commentaire_container').hide();
$('#commentaire').val('');
// Restaurer le comportement normal avec validation 25%
$('#avance_amount').off('keyup').on('keyup', updateDueCreate);
$('#gross_amount').off('keyup');
}
// Réinitialiser les champs
$('#gross_amount').val('');
$('#avance_amount').val('');
$('#amount_due').val('');
});
// =====================================================
// 🔥 GESTION DU TYPE D'AVANCE - ÉDITION
// =====================================================
$('#type_avance_edit').on('change', function() {
var typeAvance = $(this).val();
if (typeAvance === 'mere') {
$('#product_select_container_edit').hide();
$('#id_product_edit').prop('required', false).val('');
$('#product_text_container_edit').show();
$('#product_name_text_edit').prop('required', true);
$('#gross_amount_edit').prop('readonly', false).prop('type', 'number').prop('required', true);
$('#commentaire_container_edit').show();
$('#avance_amount_edit, #gross_amount_edit').off('keyup').on('keyup', function() {
var prix = parseFloat($('#gross_amount_edit').val()) || 0;
var avance = parseFloat($('#avance_amount_edit').val()) || 0;
var reste = prix - avance;
$('#amount_due_edit').val(reste >= 0 ? reste.toFixed(0) : 0);
});
} else if (typeAvance === 'terre') {
$('#product_select_container_edit').show();
$('#id_product_edit').prop('required', true);
$('#product_text_container_edit').hide();
$('#product_name_text_edit').prop('required', false).val('');
$('#gross_amount_edit').prop('readonly', true).prop('type', 'text').prop('required', false);
$('#commentaire_container_edit').hide();
$('#commentaire_edit').val('');
$('#avance_amount_edit').off('keyup').on('keyup', updateDueEdit);
$('#gross_amount_edit').off('keyup');
}
});
// ✅ CRÉATION avec actualisation automatique
$('#create_avance_form').on('submit', function(e) {
e.preventDefault();
const $form = $(this);
var typeAvance = $('#type_avance').val();
var brut = parseFloat($('#gross_amount').val()) || 0;
var avance = parseFloat($('#avance_amount').val()) || 0;
var minAvance = brut * 0.25;
if (typeAvance === 'terre') {
var minAvance = brut * 0.25;
if (avance < minAvance) {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong>
L'avance doit être au minimum de 25% du prix du produit (${minAvance.toFixed(2)} Ar).
</div>
`);
Swal.fire({
icon: 'error',
title: 'Avance insuffisante',
html: `L'avance doit être au minimum de <b>25%</b> du prix du produit (<b>${minAvance.toFixed(2)} Ar</b>).`,
confirmButtonText: 'OK',
confirmButtonColor: '#d33'
});
return;
}
// Désactiver le bouton de soumission
}
// Validation pour "mère"
if (typeAvance === 'mere') {
if (!$('#product_name_text').val() || brut === 0 || avance === 0) {
Swal.fire({
icon: 'warning',
title: 'Champs manquants',
text: 'Veuillez remplir tous les champs : produit, prix et avance.'
});
return;
}
}
$form.find('button[type="submit"]').prop('disabled', true).text('Enregistrement...');
$.post('/avances/createAvance', $form.serialize(), function(res) {
if (res.success === true) {
$("#messages").html(`
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ${res.messages}
</div>
`);
Swal.fire({
icon: 'success',
title: 'Succès',
text: res.messages,
timer: 1500,
showConfirmButton: false
}).then(() => {
$("#createModal").modal('hide');
$form[0].reset(); // Reset le formulaire
// 🔄 MISE À JOUR DYNAMIQUE après ajout
setTimeout(function() {
$form[0].reset();
location.reload();
}, 500); // petit délai pour voir le message
// Auto-masquer le message après 3 secondes
setTimeout(function() {
$("#messages .alert").fadeOut();
}, 3000);
});
} else {
$("#createModal").modal('hide');
$("#messages").html(`
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ${res.messages}
</div>
`);
// Réactiver le bouton en cas d'erreur
Swal.fire({
icon: 'error',
title: 'Erreur',
text: res.messages
});
$form.find('button[type="submit"]').prop('disabled', false).text('Enregistrer');
}
}, 'json');
});
// 🔥 SUPPRESSION avec actualisation automatique
// 🔥 SUPPRESSION
window.removeFunc = function(id, product_id) {
$('#removeModal').modal('show');
$('#removeForm input[name="avance_id"]').val(id);
@ -496,11 +634,9 @@ $(document).ready(function() {
$('#removeForm').on('submit', function(e) {
e.preventDefault();
var form = $(this);
var submitButton = form.find('button[type="submit"]');
// Désactiver le bouton
submitButton.prop('disabled', true).text('Suppression...');
$.ajax({
@ -521,10 +657,8 @@ $(document).ready(function() {
</div>
`);
// 🔄 MISE À JOUR DYNAMIQUE après suppression
refreshDataTable();
// Auto-masquer le message après 3 secondes
setTimeout(function() {
$("#messages .alert").fadeOut();
location.reload();
@ -542,19 +676,6 @@ $(document).ready(function() {
`);
}
},
error: function(xhr, status, error) {
console.log('Erreur AJAX:', error);
$('#removeModal').modal('hide');
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Une erreur est survenue lors de la suppression.
</div>
`);
},
complete: function() {
submitButton.prop('disabled', false).text('Oui');
}
@ -563,12 +684,6 @@ $(document).ready(function() {
return false;
});
// Réinitialiser le modal à la fermeture
$('#removeModal').on('hidden.bs.modal', function() {
$('#removeForm')[0].reset();
$('#removeForm button[type="submit"]').prop('disabled', false).text('Oui');
});
}); // Fin document.ready
// --- Fonctions utilitaires ---
@ -585,19 +700,17 @@ function getProductDataCreate() {
var avance25 = brutCreate * 0.25;
$('#avance_amount').val(avance25.toFixed(0));
$('#amount_due').val((brutCreate - avance25).toFixed(0));
updateDueCreate();
}, 'json');
}
function updateDueCreate() {
var av = parseFloat($('#avance_amount').val()) || 0;
var brutAmount = parseFloat($('#gross_amount').val()) || 0;
$('#amount_due').val(Math.max(brutAmount - av, 0));
$('#amount_due').val(Math.max(brutAmount - av, 0).toFixed(0));
}
function getProductDataUpdate() {
var id = $('#id_product_edit').val();
console.log('getProductDataUpdate appelée avec ID:', id); // Pour déboguer
if (!id) {
brutEdit = 0;
@ -611,32 +724,22 @@ function getProductDataUpdate() {
data: { product_id: id },
dataType: 'json',
success: function(r) {
console.log('Données produit reçues:', r); // Pour déboguer
brutEdit = parseFloat(r.prix_vente) || 0;
$('#gross_amount_edit').val(brutEdit.toFixed(0));
var avance25 = brutEdit * 0.25;
$('#avance_amount_edit').val(avance25.toFixed(0));
$('#amount_due_edit').val((brutEdit - avance25).toFixed(0));
updateDueEdit();
},
error: function(xhr, status, error) {
console.log('Erreur lors de la récupération des données produit:', error);
}
});
}
function updateDueEdit() {
var av = parseFloat($('#avance_amount_edit').val()) || 0;
$('#amount_due_edit').val(Math.max(brutEdit - av, 0).toFixed(2));
$('#amount_due_edit').val(Math.max(brutEdit - av, 0).toFixed(0));
}
// ✅ MODIFICATION avec mise à jour dynamique - CORRIGÉ
// ✅ MODIFICATION avec mise à jour dynamique - VERSION CORRIGÉE
// ✅ MODIFICATION
function editFunc(id) {
console.log('editFunc appelée avec ID:', id);
// Réinitialiser d'abord le formulaire
$('#update_avance_form')[0].reset();
$.ajax({
@ -644,71 +747,53 @@ function editFunc(id) {
type: 'GET',
dataType: 'json',
success: function(r) {
console.log('Données récupérées:', r);
// Pré-remplir le formulaire de modification avec les BONS IDs
$('#avance_id_edit').val(r.id || id);
$('#customer_name_avance_edit').val(r.customer_name || '');
$('#customer_phone_avance_edit').val(r.customer_phone || '');
$('#customer_address_avance_edit').val(r.customer_address || r.customer_adress || '');
$('#customer_cin_avance_edit').val(r.customer_cin || '');
$('#type_avance_edit').val(r.type_avance || '');
$('#type_payment_edit').val(r.type_payment || '');
$('#gross_amount_edit').val(r.gross_amount || '');
$('#avance_amount_edit').val(r.avance_amount || '');
$('#amount_due_edit').val(r.amount_due || '');
$('#commentaire_edit').val(r.commentaire || '');
// CORRECTION 1: ID correct pour le type de paiement
$('#type_payment_edit').val(r.type_payment || '');
// Récupérer l'ID du produit avec toutes les variantes possibles
var productId = r.product_id || r.id_product || r.productId || r.idProduct;
console.log('Product ID trouvé:', productId);
// Gérer le produit selon le type
if (r.type_avance === 'mere' && r.product_name) {
$('#product_name_text_edit').val(r.product_name);
}
var productId = r.product_id || r.id_product;
brutEdit = parseFloat(r.gross_amount || 0);
// Réinitialiser le bouton
$('#update_avance_form button[type="submit"]').prop('disabled', false).text('Modifier');
// Ouvrir le modal
$('#updateModal').modal('show');
// CORRECTION 2: Sélection du produit après ouverture complète du modal
$('#updateModal').on('shown.bs.modal', function(e) {
if (productId) {
console.log('Tentative de sélection du produit ID:', productId);
// Déclencher le change pour afficher les bons champs
$('#type_avance_edit').trigger('change');
// Méthode robuste de sélection
if (productId && r.type_avance === 'terre') {
setTimeout(function() {
// Vérifier d'abord si l'option existe
var optionExists = $('#id_product_edit option[value="' + productId + '"]').length > 0;
console.log('Option produit existe:', optionExists);
if (optionExists) {
$('#id_product_edit').val(productId);
console.log('Produit sélectionné:', $('#id_product_edit').val());
// Déclencher l'événement change pour mettre à jour les prix si nécessaire
$('#id_product_edit').trigger('change');
} else {
console.log('Attention: Produit ID ' + productId + ' non trouvé dans les options');
}
}, 100); // Petit délai pour s'assurer que le modal est complètement chargé
$('#id_product_edit').val(productId).trigger('change');
}, 100);
}
// Détacher l'événement pour éviter les appels multiples
$(this).off('shown.bs.modal');
});
// CORRECTION 3: Gestion du formulaire de soumission améliorée
$('#update_avance_form').off('submit').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var $submitBtn = $form.find('button[type="submit"]');
var typeAvance = $('#type_avance_edit').val();
var avance = parseFloat($('#avance_amount_edit').val()) || 0;
var minAvance = brutEdit * 0.25;
var brut = parseFloat($('#gross_amount_edit').val()) || 0;
// Validation 25%
// Validation pour "terre"
if (typeAvance === 'terre') {
var minAvance = brutEdit * 0.25;
if (avance < minAvance) {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
@ -721,27 +806,35 @@ function editFunc(id) {
`);
return;
}
}
// Désactiver le bouton pendant l'opération
$submitBtn.prop('disabled', true).text('Modification...');
// Validation pour "mère"
if (typeAvance === 'mere') {
if (!$('#product_name_text_edit').val() || brut === 0 || avance === 0) {
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong>
Veuillez remplir tous les champs requis.
</div>
`);
return;
}
}
// CORRECTION 4: Données du formulaire avec vérifications
var formData = $form.serialize();
console.log('Données envoyées:', formData);
$submitBtn.prop('disabled', true).text('Modification...');
$.ajax({
url: base_url + 'avances/updateAvance/' + id,
type: 'POST',
data: formData,
data: $form.serialize(),
dataType: 'json',
success: function(res) {
console.log('Réponse serveur:', res);
if (res.success === true) {
// Fermer le modal
$('#updateModal').modal('hide');
// Message de succès
$("#messages").html(`
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
@ -751,15 +844,12 @@ function editFunc(id) {
</div>
`);
// Mise à jour de la DataTable
if (typeof manageTable !== 'undefined' && manageTable) {
manageTable.ajax.reload(null, false);
}
// Auto-masquer le message
setTimeout(function() {
location.reload();
$("#messages .alert").fadeOut();
}, 1000);
} else {
@ -773,47 +863,13 @@ function editFunc(id) {
`);
}
},
error: function(xhr, status, error) {
console.log('Erreur AJAX:', error);
console.log('Réponse complète:', xhr.responseText);
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Erreur lors de la modification.
</div>
`);
},
complete: function() {
// Réactiver le bouton dans tous les cas
$submitBtn.prop('disabled', false).text('Modifier');
}
});
});
},
error: function(xhr, status, error) {
console.log('Erreur lors du chargement des données:', error);
console.log('Réponse:', xhr.responseText);
$("#messages").html(`
<div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Erreur lors du chargement des données.
</div>
`);
}
});
// Réinitialiser le modal à la fermeture
$('#updateModal').on('hidden.bs.modal', function() {
$('#update_avance_form')[0].reset();
$('#update_avance_form button[type="submit"]').prop('disabled', false).text('Modifier');
// Détacher les événements pour éviter les conflits
$('#update_avance_form').off('submit');
});
}
</script>

126
app/Views/demande/index.php

@ -52,12 +52,7 @@
<?php endif; ?>
</tr>
</thead>
</table>
<!-- totaliter mvola = somme tranche 1 izay type mvola tranche 1 + somme tranche 2
totaliter mvola dia - recouvrement izay sortie ùmvola plus recouvremnt entrer mvola
-->
</div>
</div>
</div>
@ -65,6 +60,7 @@
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
$(document).ready(function() {
@ -111,96 +107,104 @@
{ data: 4 },
{ data: 5 }
],
});
})
// Fonction pour refuser une demande de remise
function refuseFunc(id) {
Swal.fire({
title: 'Confirmation',
text: 'Êtes-vous sûr de vouloir refuser cette demande de remise ?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Oui, refuser',
cancelButtonText: 'Annuler',
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '<?= base_url('remise/updateRemise') ?>/' + id,
type: 'POST',
data: {
'demande_status': 'rejete'
},
data: { 'demande_status': 'Refusé' },
dataType: 'json',
success: function(response) {
manageTable.ajax.reload(null, false);
if (response.success === true) {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>');
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
id.closest('.form-group')
.removeClass('has-error')
.removeClass('has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success');
id.after(value);
Swal.fire({
icon: 'warning',
title: 'Refusé',
text: response.messages,
showConfirmButton: false,
timer: 2000
});
} else {
$("#messages").html('<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>');
Swal.fire({
icon: 'error',
title: 'Erreur',
text: response.messages
});
}
},
error: function() {
Swal.fire({
icon: 'error',
title: 'Erreur',
text: 'Une erreur est survenue lors du traitement de la demande.'
});
}
});
}
});
}
// Fonction pour valider une demande de remise
function valideFunc(id) {
Swal.fire({
title: 'Confirmation',
text: 'Êtes-vous sûr de vouloir accepter cette demande de remise ?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Oui, accepter',
cancelButtonText: 'Annuler',
confirmButtonColor: '#28a745',
cancelButtonColor: '#3085d6'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: '<?= base_url('remise/updateRemise') ?>/' + id,
type: 'POST',
data: {
'demande_status': 'accepte'
},
data: { 'demande_status': 'Accepté' },
dataType: 'json',
success: function(response) {
manageTable.ajax.reload(null, false);
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>');
} else {
if (response.messages instanceof Object) {
$.each(response.messages, function(index, value) {
var id = $("#" + index);
id.closest('.form-group')
.removeClass('has-error')
.removeClass('has-success')
.addClass(value.length > 0 ? 'has-error' : 'has-success');
id.after(value);
Swal.fire({
icon: 'success',
title: 'Accepté',
text: response.messages,
showConfirmButton: false,
timer: 2000
});
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>');
Swal.fire({
icon: 'warning',
title: 'Attention',
text: response.messages
});
}
},
error: function() {
Swal.fire({
icon: 'error',
title: 'Erreur',
text: 'Une erreur est survenue lors du traitement de la demande.'
});
}
});
}
});
}

179
app/Views/orders/create.php

@ -414,4 +414,183 @@
$("#product_info_table tbody tr#row_" + tr_id).remove();
subAmount();
}
function formatAllNumbers() {
// Sélecteur pour tous les inputs que tu veux formater
const inputs = document.querySelectorAll('input[type="text"], input[disabled]');
inputs.forEach(input => {
if (input.value && !isNaN(input.value.replace(/\s/g, ''))) {
let val = input.value.replace(/\s/g, '');
input.value = Number(val).toLocaleString('fr-FR'); // ajoute les espaces tous les 3 chiffres
}
});
}
// Appel après que la page et le DOM sont chargés
document.addEventListener('DOMContentLoaded', () => {
formatAllNumbers();
});
console.log('%c🚀 MODE EDIT AS NEW ACTIVÉ', 'color: green; font-weight: bold; font-size: 16px;');
// DEBUG: Afficher les données reçues
var orderData = <?= json_encode($existing_order ?? []) ?>;
var itemsData = <?= json_encode($existing_items ?? []) ?>;
console.log('📦 Order Data:', orderData);
console.log('📦 Items Data:', itemsData);
console.log('📊 Nombre de produits:', itemsData.length);
$(document).ready(function() {
// ============================================
// ÉTAPE 1: PRÉ-REMPLIR LES CHAMPS CLIENT
// ============================================
console.log('✅ Étape 1: Pré-remplissage des champs client');
$('#customer_name').val('<?= esc($existing_order['customer_name'] ?? '') ?>');
$('#customer_address').val('<?= esc($existing_order['customer_address'] ?? '') ?>');
$('#customer_phone').val('<?= esc($existing_order['customer_phone'] ?? '') ?>');
$('#customer_cin').val('<?= esc($existing_order['customer_cin'] ?? '') ?>');
var originalDiscount = parseFloat('<?= $existing_order['discount'] ?? 0 ?>') || 0;
if (originalDiscount > 0) {
$('#discount').val(originalDiscount);
console.log('💰 Rabais appliqué:', originalDiscount);
}
// ============================================
// ÉTAPE 2: NETTOYER LE TABLEAU
// ============================================
console.log('✅ Étape 2: Nettoyage du tableau');
$('#product_info_table tbody').empty();
// ============================================
// ÉTAPE 3: CHARGER LES PRODUITS
// ============================================
<?php if (isset($existing_items) && !empty($existing_items)): ?>
console.log('✅ Étape 3: Chargement de ' + itemsData.length + ' produit(s)');
// Fonction de formatage
function formatNumber(num) {
return parseFloat(num).toLocaleString('fr-FR', {
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
}
// Charger chaque produit
itemsData.forEach(function(item, index) {
var rowId = index + 1;
console.log('🔄 Chargement ligne ' + rowId + ':', item);
// CONSTRUIRE LA LIGNE HTML
var html = '<tr id="row_' + rowId + '">';
// COLONNE 1: SELECT PRODUIT
html += '<td>';
html += '<select class="form-control select_group product" ';
html += 'data-row-id="' + rowId + '" ';
html += 'id="product_' + rowId + '" ';
html += 'name="product[]" ';
html += 'style="width:100%;" ';
html += 'onchange="getProductData(' + rowId + ')" required>';
html += '<option value="">Sélectionner...</option>';
// OPTIONS DES PRODUITS
<?php foreach ($products as $p): ?>
<?php if ($p['product_sold'] == 0): ?>
html += '<option value="<?= $p['id'] ?>"';
if (item.product_id === <?= $p['id'] ?>) {
html += ' selected="selected"';
console.log(' ✓ Produit trouvé: <?= esc($p['name']) ?> (ID: <?= $p['id'] ?>)');
}
html += '>';
html += '<?= esc($p['sku']) ?> | <?= esc($p['name']) ?> | ';
html += '<?= esc($p['numero_de_moteur']) ?> | <?= esc($p['puissance']) ?>';
html += '</option>';
<?php endif; ?>
<?php endforeach; ?>
html += '</select></td>';
// COLONNE 2: PRIX UNITAIRE
html += '<td>';
html += '<input type="text" name="rate[]" ';
html += 'id="rate_' + rowId + '" ';
html += 'class="form-control" disabled ';
html += 'value="' + formatNumber(item.rate) + '">';
html += '<input type="hidden" name="rate_value[]" ';
html += 'id="rate_value_' + rowId + '" ';
html += 'value="' + item.rate + '">';
html += '</td>';
// COLONNE 3: MONTANT
html += '<td>';
html += '<input type="text" name="amount[]" ';
html += 'id="amount_' + rowId + '" ';
html += 'class="form-control" disabled ';
html += 'value="' + formatNumber(item.amount) + '">';
html += '<input type="hidden" name="amount_value[]" ';
html += 'id="amount_value_' + rowId + '" ';
html += 'value="' + item.amount + '">';
html += '</td>';
// COLONNE 4: BOUTON SUPPRIMER
html += '<td>';
html += '<button type="button" class="btn btn-default" ';
html += 'onclick="removeRow(\'' + rowId + '\')">';
html += '<i class="fa fa-close"></i></button>';
html += '</td>';
// QTY CACHÉ (toujours 1 pour les motos)
html += '<input type="hidden" name="qty[]" ';
html += 'id="qty_' + rowId + '" ';
html += 'value="' + item.qty + '" ';
html += 'required onkeyup="getTotal(' + rowId + ')">';
html += '</tr>';
// AJOUTER AU TABLEAU
$('#product_info_table tbody').append(html);
console.log(' ✓ Ligne ' + rowId + ' ajoutée au DOM');
});
// ============================================
// ÉTAPE 4: INITIALISER SELECT2 ET RECALCULER
// ============================================
console.log('✅ Étape 4: Finalisation');
setTimeout(function() {
// Initialiser Select2
if ($.fn.select2) {
$('.product').select2();
console.log(' ✓ Select2 initialisé');
}
// Recalculer les montants
if (typeof subAmount === 'function') {
subAmount();
console.log(' ✓ Montants recalculés');
}
// Formater les nombres
if (typeof formatAllNumbers === 'function') {
formatAllNumbers();
console.log(' ✓ Nombres formatés');
}
console.log('%c🎉 CHARGEMENT TERMINÉ AVEC SUCCÈS!', 'color: green; font-weight: bold; font-size: 14px;');
// Vérification finale
var rowCount = $('#product_info_table tbody tr').length;
console.log('📊 Nombre de lignes dans le tableau:', rowCount);
}, 500);
<?php else: ?>
console.warn('⚠️ Aucun produit à charger');
<?php endif; ?>
});
</script>

93
app/Views/orders/createbyid.php

@ -1,3 +1,4 @@
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<div class="content-wrapper">
<section class="content-header">
<h1>
@ -26,14 +27,19 @@
</div>
<?php endif; ?>
<?php if ($errors = session()->getFlashdata('errors')): ?>
<div class="alert alert-error alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">&times;</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">&times;</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">
@ -110,8 +116,8 @@
<!-- <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%"><button type="button" id="add_row"
class="btn btn-default"><i class="fa fa-plus"></i></button></th> -->
</tr>
</thead>
<tbody>
@ -131,6 +137,7 @@
autocomplete="off" value="<?= $pu ?>">
<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="">
</td>
<td>
@ -207,16 +214,10 @@
$(document).ready(function () {
getTotal(1);
$(".select_group").select2();
// $("#description").wysihtml5();
$("#mainOrdersNav").addClass('active');
$("#addOrderNav").addClass('active');
var btnCust = '<button type="button" class="btn btn-secondary" title="Add picture tags" ' +
'onclick="alert(\'Call your custom code here.\')">' +
'<i class="glyphicon glyphicon-tag"></i>' +
'</button>';
// Add new row in the table
$("#add_row").unbind('click').bind('click', function () {
var table = $("#product_info_table");
@ -228,8 +229,6 @@
type: 'post',
dataType: 'json',
success: function (response) {
// console.log(reponse.x);
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 + ')">' +
@ -240,7 +239,7 @@
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"></td>' +
'<td><input type="text" name="rate[]" id="rate_' + row_id + '" class="form-control" disabled><input type="hidden" name="rate_value[]" id="rate_value_' + row_id + '" class="form-control"><input type="hidden" id="min_price_' + row_id + '" name="min_price[]" value=""></td>' +
'<td><input type="text" name="amount[]" id="amount_' + row_id + '" class="form-control" disabled><input type="hidden" name="amount_value[]" id="amount_value_' + row_id + '" class="form-control"></td>' +
'<td><button type="button" class="btn btn-default" onclick="removeRow(\'' + row_id + '\')"><i class="fa fa-close"></i></button></td>' +
'</tr>';
@ -252,13 +251,25 @@
}
$(".product").select2();
}
});
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) {
@ -281,9 +292,8 @@
if (product_id == "") {
$("#rate_" + row_id).val("");
$("#rate_value_" + row_id).val("");
$("#min_price_" + row_id).val("");
$("#qty_" + row_id).val("");
$("#amount_" + row_id).val("");
$("#amount_value_" + row_id).val("");
@ -297,9 +307,9 @@
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
$("#qty_" + row_id).val(1);
$("#qty_value_" + row_id).val(1);
@ -310,8 +320,8 @@
$("#amount_value_" + row_id).val(total);
subAmount();
} // /success
}); // /ajax function to fetch the product data
}
});
}
}
@ -328,7 +338,7 @@
count = count.substring(4);
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
} // /for
}
totalSubAmount = totalSubAmount.toFixed(2);
@ -351,8 +361,7 @@
// total amount
var totalAmount = (Number(totalSubAmount));
totalAmount = totalAmount.toFixed(2);
// $("#net_amount").val(totalAmount);
// $("#totalAmountValue;").val(totalAmount);
var discount = $("#discount").val();
if (discount) {
var grandTotal = Number(totalAmount) - Number(discount);
@ -362,10 +371,41 @@
} else {
$("#net_amount").val(totalAmount);
$("#net_amount_value").val(totalAmount);
}
}
// ✅ Vérification du rabais vs prix minimal
function checkMinimalPrice() {
var discount = Number($("#discount").val()) || 0;
// Si pas de rabais, pas de vérification nécessaire
if (discount === 0) return true;
} // /else discount
var tableProductLength = $("#product_info_table tbody tr").length;
var error = false;
var messages = [];
for (var i = 0; i < tableProductLength; i++) {
var tr = $("#product_info_table tbody tr")[i];
var rowId = $(tr).attr('id').replace('row_', '');
var minPrice = Number($("#min_price_" + rowId).val()) || 0;
// ✅ Le rabais devient le prix de vente
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 + ")");
}
}
} // /sub total amount
if (error) {
alert(messages.join("\n"));
$("#discount").val('');
subAmount();
return false;
}
return true;
}
function removeRow(tr_id) {
$("#product_info_table tbody tr#row_" + tr_id).remove();
@ -378,7 +418,7 @@
const amount = document.getElementById('amount_1')
const gross_amount_value = document.getElementById('gross_amount');
const gross_amount_value2 = document.getElementById('gross_amount_value');
const remise = document.getElementById('remise');
const remise = document.getElementById('discount');
const net_amount = document.getElementById('net_amount');
const net_amount_value = document.getElementById('net_amount_value');
const amount_value = document.getElementById('amount_value_1');
@ -393,5 +433,6 @@
remise.addEventListener('input', function () {
net_amount.value = (gross_amount_value.value - remise.value);
net_amount_value.value = (gross_amount_value.value - remise.value);
subAmount();
})
</script>

219
app/Views/orders/edit.php

@ -172,7 +172,14 @@
<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">
@ -194,24 +201,34 @@
</select>
</div>
</div> -->
<?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">Tranche de paiement</label>
<div class="col-sm-7">
<select class="form-control" id="payment_mode" name="payment_mode">
<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">
<input type="text" class="form-control" id="montant_total_tranches" disabled>
<input type="hidden" id="montant_total_tranches_value">
<small class="text-muted" id="montant_source_label"></small>
</div>
</div>
<div id="payment-tranches">
<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">
<option value="MVOLA">MVOLA</option>
@ -220,12 +237,13 @@
</select>
</div>
<div class="col-sm-4">
<input type="number" class="form-control" id="payment_amount_1" name="tranche_1" placeholder="Montant">
<input type="number" class="form-control" id="payment_amount_1"
name="tranche_1" placeholder="Montant" onkeyup="calculerTranche2()">
</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</label>
<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">
<option value="MVOLA">MVOLA</option>
@ -234,11 +252,12 @@
</select>
</div>
<div class="col-sm-4">
<input type="number" class="form-control" id="payment_amount_2" name="tranche_2" placeholder="Montant">
<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() {
@ -280,20 +299,24 @@
});
</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">
<option value="1">Payé</option>
<option value="2">Non payé</option>
<option value="1">Validé</option>
<option value="2">Refusé</option>
</select>
</div>
</div>
<?php endif; ?>
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
@ -337,42 +360,54 @@
Imprimente.removeAttribute("href");
Imprimente.setAttribute("href", base_url +'orders/printDivBLF/' + idData);
}
})
// function printOrder(id)
// {
// if(id) {
// $.ajax({
// url: base_url + 'orders/printDiv/' + id,
// type: 'post',
// success:function(response) {
// var mywindow = window.open('', 'new div', 'height=400,width=600');
// // mywindow.document.write('<html><head><title></title>');
// // mywindow.document.write('<link rel="stylesheet" href="<?php //echo base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css')
?>" type="text/css" />');
// // mywindow.document.write('</head><body >');
// mywindow.document.write(response);
// // mywindow.document.write('</body></html>');
// mywindow.print();
// mywindow.close();
// return true;
// }
// });
// }
// }
});
$(document).ready(function() {
$(".select_group").select2();
// $("#description").wysihtml5();
$("#mainOrdersNav").addClass('active');
$("#manageOrdersNav").addClass('active');
// ============================================
// 🔹 AJOUT : GESTION DES TRANCHES DE PAIEMENT
// ============================================
var paymentTranche = 1;
var netAmount = parseFloat($('#net_amount_value').val()) || 0;
$('#payment_amount_1').val(netAmount);
function addPaymentTranche(paymentTranche) {
if (parseInt(paymentTranche) === 2) {
$("#paid_status_1").show();
$("#paid_status_2").show();
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('');
}
}
// Sélection du mode de paiement
$("#payment_mode").on("change", function() {
addPaymentTranche($(this).val());
updateMontantTranches(); // ✅ pour mise à jour automatique
});
// 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);
// Add new row in the table
// ============================================
// 🔹 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;
@ -383,9 +418,6 @@
type: 'post',
dataType: 'json',
success: function(response) {
// console.log(reponse.x);
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 + ')">' +
@ -409,70 +441,62 @@
}
$(".product").select2();
}
});
return false;
});
}); // /document.ready
}); // /document
// ============================================
// 🔹 CALCUL DU TOTAL
// ============================================
function getTotal(row = null) {
if (row) {
var total = Number($("#rate_value_" + row).val()) * Number($("#qty_" + row).val());
total = total.toFixed(2);
$("#amount_" + row).val(total);
$("#amount_value_" + row).val(total);
subAmount();
} else {
alert('no row !! please refresh the page');
}
}
// get the product information from the server
// 🔹 OBTENIR LES DONNÉES PRODUIT
function getProductData(row_id) {
var product_id = $("#product_" + row_id).val();
if (product_id == "") {
$("#rate_" + row_id).val("");
$("#rate_value_" + row_id).val("");
$("#qty_" + row_id).val("");
$("#amount_" + row_id).val("");
$("#amount_value_" + row_id).val("");
} else {
$.ajax({
url: base_url + 'orders/getProductValueById',
type: 'post',
data: {
product_id: product_id
},
data: { product_id: product_id },
dataType: 'json',
success: function(response) {
// setting the rate value into the rate input field
$("#rate_" + row_id).val(response.price);
$("#rate_value_" + row_id).val(response.price);
$("#qty_" + row_id).val(1);
$("#qty_value_" + row_id).val(1);
var total = Number(response.price) * 1;
total = total.toFixed(2);
$("#amount_" + row_id).val(total);
$("#amount_value_" + row_id).val(total);
subAmount();
} // /success
}); // /ajax function to fetch the product data
}
});
}
}
// calculate the total amount of the order
// ============================================
// 🔹 CALCUL DU MONTANT TOTAL (AVEC TVA, REMISE, ETC.)
// ============================================
function subAmount() {
var service_charge = <?php echo ($company_data['service_charge_value'] > 0) ? $company_data['service_charge_value'] : 0; ?>;
var vat_charge = <?php echo ($company_data['vat_charge_value'] > 0) ? $company_data['vat_charge_value'] : 0; ?>;
@ -483,33 +507,25 @@
var tr = $("#product_info_table tbody tr")[x];
var count = $(tr).attr('id');
count = count.substring(4);
totalSubAmount = Number(totalSubAmount) + Number($("#amount_" + count).val());
} // /for
}
totalSubAmount = totalSubAmount.toFixed(2);
// sub total
$("#gross_amount").val(totalSubAmount);
$("#gross_amount_value").val(totalSubAmount);
// vat
var vat = (Number($("#gross_amount").val()) / 100) * vat_charge;
vat = vat.toFixed(2);
$("#vat_charge").val(vat);
$("#vat_charge_value").val(vat);
// service
var service = (Number($("#gross_amount").val()) / 100) * service_charge;
service = service.toFixed(2);
$("#service_charge").val(service);
$("#service_charge_value").val(service);
// total amount
var totalAmount = (Number(totalSubAmount));
totalAmount = totalAmount.toFixed(2);
// $("#net_amount").val(totalAmount);
// $("#totalAmountValue").val(totalAmount);
var discount = $("#discount").val();
if (discount) {
@ -520,8 +536,7 @@
} else {
$("#net_amount").val(totalAmount);
$("#net_amount_value").val(totalAmount);
} // /else discount
}
var paid_amount = Number($("#paid_amount").val());
if (paid_amount) {
@ -531,27 +546,71 @@
$("#remaining_value").val(remaning.toFixed(2));
}
} // /sub total amount
// ✅ Mise à jour automatique des tranches à chaque recalcul
updateMontantTranches();
}
// ============================================
// 🔹 AUTRES FONCTIONS
// ============================================
function paidAmount() {
var grandTotal = $("#net_amount_value").val();
if (grandTotal) {
var dueAmount = Number($("#net_amount_value").val()) - Number($("#paid_amount").val());
dueAmount = dueAmount.toFixed(2);
$("#remaining").val(dueAmount);
$("#remaining_value").val(dueAmount);
} // /if
} // /paid amoutn function
}
}
function removeRow(tr_id) {
$("#product_info_table tbody tr#row_" + tr_id).remove();
subAmount();
}
// ============================================
// 🔹 GESTION MONTANT DE TRANCHES (FONCTIONS NOUVELLES)
// ============================================
function getMontantPourTranches() {
var discount = parseFloat($("#discount").val()) || 0;
var grossAmount = parseFloat($("#gross_amount_value").val()) || 0;
return discount > 0 ? discount : grossAmount;
}
function updateMontantTranches() {
var montant = getMontantPourTranches();
var discount = parseFloat($("#discount").val()) || 0;
$("#montant_total_tranches").val(montant.toFixed(2));
$("#montant_total_tranches_value").val(montant);
if (discount > 0) {
$("#montant_source_label").text("(Prix avec remise acceptée)");
} else {
$("#montant_source_label").text("(Montant brut)");
}
if ($("#payment_amount_1").val()) {
calculerTranche2();
}
}
function calculerTranche2() {
var montantTotal = getMontantPourTranches();
var tranche1 = parseFloat($("#payment_amount_1").val()) || 0;
var tranche2 = montantTotal - tranche1;
if (tranche2 < 0) tranche2 = 0;
$("#payment_amount_2").val(tranche2.toFixed(2));
}
$("#discount").on('keyup', function() {
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
console.log(net_amount_value.value, net_amount.value, payment_amount_1.value);
payment_amount_1.value = net_amount.value;
</script>

183
app/Views/orders/index.php

@ -56,9 +56,9 @@
<th>Nom du client</th>
<th>Téléphone du client</th>
<th>Date et Heure</th>
<th>Produits totaux</th>
<th>Remise</th>
<th>Prix de vente</th>
<th>Statut payant</th>
<th>Validation</th>
<?php if (
in_array('updateOrder', $user_permission)
@ -72,16 +72,17 @@
<?php
$session = session();
$users = $session->get('user');
if ($users['group_name'] === 'COMMERCIALE' || $users['group_name'] === 'Caissière' || $users['group_name'] === "Cheffe d'Agence") {
if ($users['group_name'] === 'COMMERCIALE' || $users['group_name'] === 'Caissière' || $users['group_name'] === 'SECURITE' || $users['group_name'] === "Cheffe d'Agence") {
?>
<th>Nom du produit</th>
<th>Commerciale</th>
<th>Date et Heure</th>
<th>Produits totaux</th>
<th>Remise</th>
<th>Prix de vente</th>
<th>Statut payant</th>
<th>Validation</th>
<?php if (
in_array('viewOrder', $user_permission)
|| in_array('updateOrder', $user_permission)
) { ?>
<th>Action</th>
<?php } ?>
@ -144,6 +145,9 @@
</div>
<div class="modal-body">
<!-- Messages de la modal -->
<div id="modal-messages"></div>
<!-- Ligne Date / Heure -->
<div class="row">
<div class="col-md-6 form-group">
@ -181,31 +185,39 @@
<h4>Produits</h4>
<table class="table table-bordered table-striped" id="view_products_table">
<thead>
<tr><th>Produit</th><th>Quantité</th><th>Prix unitaire</th><th>Montant</th></tr>
<tr><th>Marque</th><th>Designation</th><th>Numéro de Série</th><th>Prix Unitaire</th></tr>
</thead>
<tbody></tbody>
</table>
<!-- Totaux -->
<table class="table table-bordered table-striped" id="view_totals">
<tr><th>Montant brut</th><th>Rabais</th><th>Montant net</th><th>Statut</th>
<tr><td id="gross_amount"></td><td id="discount"></td><td id="net_amount"></td><td id="paid_status"></td><tr>
<tr><th>Montant brut</th><th>Rabais</th><th>Statut</th></tr>
<tr><td id="gross_amount"></td><td id="discount"></td><td id="paid_status"></td></tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fermer</button>
<?php
$session = session();
$users = $session->get('user');
if ($users['group_name'] === 'SECURITE'): ?>
<button type="button" class="btn btn-success" id="btn-mark-delivered" style="display:none;">
<i class="fa fa-truck"></i> Marquer comme Livré
</button>
<?php endif; ?>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var manageTable;
var base_url = "<?php echo base_url(); ?>";
var currentOrderId = null; // Variable globale pour stocker l'ID de la commande actuelle
$(document).ready(function() {
@ -213,8 +225,6 @@
$("#manageOrdersNav").addClass('active');
// initialize the datatable
// datatable-fr.js
$.extend(true, $.fn.dataTable.defaults, {
language: {
sProcessing: "Traitement en cours...",
@ -245,8 +255,72 @@
'columnDefs': [{
targets: 6,
className: 'text-right'
} // Column index 3 corresponds to "Prix"
]
}]
});
// Gestionnaire pour le bouton "Marquer comme Livré"
$('#btn-mark-delivered').on('click', function() {
if (!currentOrderId) {
alert('Erreur : ID de commande introuvable.');
return;
}
if (confirm('Êtes-vous sûr de vouloir marquer cette commande comme livrée ?')) {
// Désactiver le bouton pendant le traitement
$('#btn-mark-delivered').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Traitement...');
$.ajax({
url: base_url + 'orders/markAsDelivered',
type: 'POST',
data: { order_id: currentOrderId },
dataType: 'json',
success: function(response) {
if (response.success === true) {
// Afficher un message de succès dans la modal
$("#modal-messages").html(
'<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-ok-sign"></span></strong> ' + response.messages +
'</div>'
);
// Mettre à jour le statut dans la modal
$('#paid_status').html('<span class="label label-info">Validé et Livré</span>');
// Cacher le bouton "Livré"
$('#btn-mark-delivered').hide();
// Recharger la table immédiatement
manageTable.ajax.reload(null, false);
} else {
// Afficher un message d'erreur
$("#modal-messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> ' + response.messages +
'</div>'
);
// Réactiver le bouton
$('#btn-mark-delivered').prop('disabled', false).html('<i class="fa fa-truck"></i> Marquer comme Livré');
}
},
error: function(xhr, status, error) {
console.error('Erreur AJAX:', xhr.responseText);
$("#modal-messages").html(
'<div class="alert alert-danger alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong><span class="glyphicon glyphicon-exclamation-sign"></span></strong> Erreur de communication avec le serveur.<br>' +
'Détails: ' + (xhr.responseText || error) +
'</div>'
);
// Réactiver le bouton
$('#btn-mark-delivered').prop('disabled', false).html('<i class="fa fa-truck"></i> Marquer comme Livré');
}
});
}
});
});
@ -255,38 +329,32 @@
function removeFunc(id) {
if (id) {
$("#removeForm").on('submit', function() {
var form = $(this);
// remove the text-danger
$(".text-danger").remove();
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: {
order_id: id
},
data: { order_id: id },
dataType: 'json',
success: function(response) {
manageTable.ajax.reload(null, false);
if (response.success === true) {
$("#messages").html('<div class="alert alert-success alert-dismissible" role="alert">' +
$("#messages").html(
'<div class="alert alert-success alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-ok-sign"></span> </strong>' + response.messages +
'</div>');
// hide the modal
'</div>'
);
$("#removeModal").modal('hide');
} else {
$("#messages").html('<div class="alert alert-warning alert-dismissible" role="alert">' +
$("#messages").html(
'<div class="alert alert-warning alert-dismissible" role="alert">' +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
'<strong> <span class="glyphicon glyphicon-exclamation-sign"></span> </strong>' + response.messages +
'</div>');
'</div>'
);
}
}
});
@ -299,12 +367,15 @@
$(document).on('click', '.btn-view', function(e) {
e.preventDefault();
var orderId = $(this).data('order-id');
currentOrderId = orderId; // Stocker l'ID de la commande
var url = base_url + 'orders/lookOrder/' + orderId;
// Réinitialiser les messages
$('#modal-messages').empty();
// Requête AJAX pour récupérer les données JSON
$.getJSON(url, function(response) {
var d = response.order_data.order;
var sum_order_data = response.order_data.sum_order_data;
// Remplir les champs de la modal
$('#order_date').text(d.date_time || '');
@ -317,35 +388,61 @@
var $tb = $('#view_products_table tbody');
$tb.empty();
$.each(response.order_data.order_item, function(_, item) {
// Trouver le nom du produit
var prodName = '';
var product = null;
$.each(response.products, function(_, p) {
if (p.id == item.product_id) {
prodName = p.name;
product = p;
return false;
}
});
if (product) {
var brandName = '';
$.each(response.brands, function(_, brand) {
if (brand.id == product.marque) {
brandName = brand.name;
return false;
}
});
// Ajouter une ligne au tableau des produits
$tb.append(
'<tr><td>' + prodName + '</td>' +
'<td>' + sum_order_data + '</td>' +
'<td>' + parseInt(item.rate).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits:0 }) + ' Ar</td>' +
'<td>' + parseInt(item.amount).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' Ar</td></tr>'
'<tr>' +
'<td>' + (brandName || 'Aucune marque') + '</td>' +
'<td>' + (product.name || '') + '</td>' +
'<td>' + (product.sku || '') + '</td>' +
'<td>' + parseInt(item.amount).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' Ar</td>' +
'</tr>'
);
}
});
// Totaux
$('#gross_amount').text(parseInt(d.gross_amount).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' Ar');
$('#discount').text(parseInt(d.discount).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' Ar');
$('#net_amount').text(parseInt(d.net_amount).toLocaleString('fr-MG', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' Ar');
// Statut de paiement
var statut = (d.paid_status == 2) ? "non payé" : "payé";
$('#paid_status').text(statut);
// Statut de paiement avec gestion du statut "Livré"
var statutHtml = '';
if (d.paid_status == 1) {
statutHtml = '<span class="label label-success">Validé</span>';
// Afficher le bouton "Livré" pour la sécurité si statut = Validé
<?php if ($users['group_name'] === 'SECURITE'): ?>
$('#btn-mark-delivered').show();
<?php endif; ?>
} else if (d.paid_status == 2) {
statutHtml = '<span class="label label-warning">En Attente</span>';
$('#btn-mark-delivered').hide();
} else if (d.paid_status == 3) {
statutHtml = '<span class="label label-info">Validé et Livré</span>';
$('#btn-mark-delivered').hide();
} else {
statutHtml = '<span class="label label-danger">Refusé</span>';
$('#btn-mark-delivered').hide();
}
$('#paid_status').html(statutHtml);
// Afficher la modal
$('#viewOrderModal').modal('show');
});
});
</script>

41
app/Views/products/create.php

@ -54,7 +54,7 @@
<h3 class="box-title">Ajouter une Moto</h3>
</div>
<!-- /.box-header -->
<form role="form" action="<?php base_url('create') ?>" method="post" enctype="multipart/form-data">
<form role="form" action="<?= base_url('products/create') ?>" method="post" enctype="multipart/form-data">
<?php if (isset($validation) && $validation->getErrors()) : ?>
<div class="alert alert-danger">
<ul>
@ -356,4 +356,43 @@
})
});
// --- Vérification prix minimal vs prix de vente ---
const priceVenteInput = document.getElementById('price_vente');
const priceMinInput = document.getElementById('price_min');
function verifierPrix() {
const prixVente = parseFloat(priceVenteInput.value) || 0;
const prixMin = parseFloat(priceMinInput.value) || 0;
if (prixMin > prixVente) {
priceMinInput.style.borderColor = 'red';
if (!document.getElementById('price_min_error')) {
const small = document.createElement('small');
small.id = 'price_min_error';
small.style.color = 'red';
small.textContent = 'Le prix minimal ne peut pas être supérieur au prix de vente';
priceMinInput.parentNode.appendChild(small);
}
} else {
priceMinInput.style.borderColor = '';
const errorMsg = document.getElementById('price_min_error');
if (errorMsg) errorMsg.remove();
}
}
priceVenteInput.addEventListener('input', verifierPrix);
priceMinInput.addEventListener('input', verifierPrix);
// Vérification au submit
document.querySelector('form').addEventListener('submit', function(e) {
const prixVente = parseFloat(priceVenteInput.value) || 0;
const prixMin = parseFloat(priceMinInput.value) || 0;
if (prixMin > prixVente) {
e.preventDefault();
alert('❌ Le prix minimal ne peut pas être supérieur au prix de vente !');
priceMinInput.focus();
}
});
</script>

234
app/Views/products/editbackup.php

@ -5,8 +5,6 @@
{
// Ensure the string is properly formatted for JSON decoding
$data = str_replace("'", '"', $data); // Convert single quotes to double quotes
// Decode JSON to an array
return json_decode($data, true);
}
?>
@ -24,10 +22,8 @@
<!-- Main content -->
<section class="content">
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-md-12 col-xs-12">
<div id="messages"></div>
<?php if (session()->getFlashdata('success')): ?>
@ -42,19 +38,20 @@
</div>
<?php endif; ?>
<div class="box">
<div class="box-header">
<h3 class="box-title">Mise à jours Moto</h3>
</div>
<!-- /.box-header -->
<form role="form" action="<?php base_url('users/update') ?>" method="post" enctype="multipart/form-data">
<div class="box-body">
<!-- Image actuelle -->
<div class="form-group">
<label>Image: </label>
<img src="<?php echo base_url() . 'assets/images/product_image/' . $product_data['image'] ?>" width="150" height="150" class="img-circle">
</div>
<!-- Nouvelle Image -->
<div class="form-group">
<label for="product_image">Nouvelle Image</label>
<div class="kv-avatar">
@ -64,195 +61,178 @@
</div>
</div>
<!-- Nom du produit -->
<div class="form-group">
<label for="product_name">Nom de Moto</label>
<input type="text" class="form-control" id="product_name" name="nom_de_produit" placeholder="Nom du moto" autocomplete="off" value="<?php echo $product_data['name']; ?>" />
</div>
<!-- Marque -->
<div class="form-group">
<label for="marque">Marque</label>
<select class="form-control" id="marque" name="marque">
<?php foreach ($marque as $k => $v): ?>
<option value="<?php echo $v['id'] ?>" <?php if ($product_data['marque'] == $v['id']) {
echo "selected='selected'";
} ?>><?php echo $v['name'] ?></option>
<option value="<?php echo $v['id'] ?>" <?php if ($product_data['marque'] == $v['id']) echo "selected"; ?>><?php echo $v['name'] ?></option>
<?php endforeach ?>
</select>
</div>
<!-- Numéro de moteur -->
<div class="form-group">
<label for="moteur">Numéro de moteur</label>
<input type="text" class="form-control" id="moteur" name="numero_de_moteur" placeholder="Numéro de série" autocomplete="off" value="<?php echo $product_data['numero_de_moteur']; ?>" />
</div>
<!-- Châssis -->
<div class="form-group">
<label for="chassis">Châssis</label>
<input type="text" class="form-control" id="chassis" name="chasis" placeholder="Chassis" autocomplete="off" value="<?php echo $product_data['chasis']; ?>" />
</div>
<!-- Prix d'achat -->
<div class="form-group">
<label for="price">Prix d'Achat</label>
<input type="number" min="0" class="form-control" id="price" name="price" placeholder="Prix d'achat" autocomplete="off" value="<?php echo $product_data['price']; ?>" />
</div>
<!-- Prix de vente -->
<div class="form-group">
<label for="price_vente">Prix de Vente</label>
<input type="number" min="0" class="form-control" id="price_vente" name="price_vente" placeholder="Prix de vente" autocomplete="off" value="<?php echo $product_data['prix_vente']; ?>" />
<input type="number" min="0" class="form-control" id="price_vente" name="price_vente" placeholder="Prix de vente" autocomplete="off" value="<?php echo $product_data['prix_vente']; ?>" required />
</div>
<!-- Prix minimal -->
<div class="form-group">
<label for="qty">Quantiter</label>
<input type="number" min="0" class="form-control" id="qty" name="quantiter" placeholder="Quantiter" autocomplete="off" value="<?php echo $product_data['qty']; ?>" />
<label for="price_min">Prix Minimal</label>
<input type="number" min="0" class="form-control" id="price_min" name="price_min" placeholder="Prix de vente minimal autorisé" autocomplete="off" value="<?= old('price_min') ?: $prix_minimal ?>" required />
<small id="price_min_error" style="color:red; display:none;">Le prix minimal ne peut pas être supérieur au prix de vente</small>
</div>
<!-- Date d'arrivage -->
<div class="form-group">
<label for="datea">Date d'arrivage</label>
<input type="date" class="form-control" id="datea" name="datea" autocomplete="off" value="<?php echo $product_data['date_arivage']; ?>" />
</div>
<!-- Puissance -->
<div class="form-group">
<label for="puissance">Puissances</label>
<input type="text" class="form-control" id="puissance" name="puissance" placeholder="Puissances" autocomplete="off" value="<?php echo $product_data['puissance']; ?>" />
</div>
<!-- Clé -->
<div class="form-group">
<label for="cler">Clé</label>
<input type="text" class="form-control" id="cler" name="cler" placeholder="Type de clé" autocomplete="off" value="<?php echo $product_data['cler']; ?>" />
</div>
<!-- Description -->
<div class="form-group">
<label for="description">Description</label>
<textarea type="text" class="form-control" id="description" name="description" placeholder="Description" autocomplete="off">
<?php echo $product_data['description']; ?>
</textarea>
<textarea type="text" class="form-control" id="description" name="description" placeholder="Description" autocomplete="off"><?php echo $product_data['description']; ?></textarea>
</div>
<!-- Numéro de série -->
<div class="form-group">
<label for="sku">Numéro de série</label>
<input type="text" class="form-control" id="sku" name="numero_de_serie" placeholder="Numéro de série" autocomplete="off" value="<?php echo $product_data['sku']; ?>" />
</div>
<!-- Catégories -->
<div class="form-group">
<label for="categorie">Catégories</label>
<?php
// Normalisation de la valeur de product_data['categorie_id']
$rawCats = $product_data['categorie_id'] ?? null;
if (is_array($rawCats)) {
$catIds = array_map('intval', $rawCats);
} elseif (is_string($rawCats)) {
// transforme "1,2,3" en [1,2,3]
$catIds = array_filter(array_map('intval', explode(',', $rawCats)), fn($id) => $id > 0);
} elseif (is_int($rawCats) || ctype_digit((string)$rawCats)) {
// wrap l'int en tableau
$catIds = [(int) $rawCats];
} else {
$catIds = [];
}
?>
<select class="form-control select_group" id="categorie" name="categorie[]" multiple="multiple">
<?php foreach ($categorie as $k => $v): ?>
<option value="<?= $v['id']; ?>"
<?= in_array($v['id'], $catIds, true) ? 'selected="selected"' : '' ?>>
<?= esc($v['name']); ?>
</option>
<option value="<?= $v['id']; ?>" <?= in_array($v['id'], $catIds, true) ? 'selected="selected"' : '' ?>><?= esc($v['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- Magasin -->
<div class="form-group">
<label for="store">Magasin</label>
<select class="form-control select_group" id="store" name="store">
<?php foreach ($stores as $k => $v): ?>
<option value="<?php echo $v['id'] ?>" <?php if ($product_data['store_id'] == $v['id']) {
echo "selected='selected'";
} ?>><?php echo $v['name'] ?></option>
<option value="<?php echo $v['id'] ?>" <?php if ($product_data['store_id'] == $v['id']) echo "selected"; ?>><?php echo $v['name'] ?></option>
<?php endforeach ?>
</select>
</div>
<!-- Disponibilité -->
<div class="form-group">
<label for="store">Disponibilité</label>
<label for="availability">Disponibilité</label>
<select class="form-control" id="availability" name="availability">
<option value="1" <?php if ($product_data['availability'] == 1) {
echo "selected='selected'";
} ?>>Oui</option>
<option value="0" <?php if ($product_data['availability'] == 0) {
echo "selected='selected'";
} ?>>Non</option> <!-- Changé de 2 à 0 -->
<option value="1" <?php if ($product_data['availability'] == 1) echo "selected"; ?>>Oui</option>
<option value="0" <?php if ($product_data['availability'] == 0) echo "selected"; ?>>Non</option>
</select>
</div>
<!-- Etats -->
<div class="form-group">
<label for="store">Etats</label>
<label for="etat">Etats</label>
<select class="form-control" id="etat" name="etats">
<option value="1" <?php if ($product_data['etats'] == 1) {
echo "selected='selected'";
} ?>>Kit</option>
<option value="2" <?php if ($product_data['etats'] != 1) {
echo "selected='selected'";
} ?>>Non Kit</option>
<option value="1" <?php if ($product_data['etats'] == 1) echo "selected"; ?>>Kit</option>
<option value="2" <?php if ($product_data['etats'] != 1) echo "selected"; ?>>Non Kit</option>
</select>
</div>
<div class="form-group" id="boxsuplementairekit"></div>
<!-- Pièce manquante -->
<div class="form-group">
<label for="store">Pièce Manquant</label>
<label for="info">Pièce Manquant</label>
<select class="form-control" id="info" name="info">
<option value="2" <?php if ($product_data['info'] != 1) {
echo "selected='selected'";
} ?>>Non</option>
<option value="1" <?php if ($product_data['info'] == 1) {
echo "selected='selected'";
} ?>>Oui</option>
<option value="2" <?php if ($product_data['info'] != 1) echo "selected"; ?>>Non</option>
<option value="1" <?php if ($product_data['info'] == 1) echo "selected"; ?>>Oui</option>
</select>
</div>
<div class="form-group" id="boxsuplementaire"></div>
<!-- Type -->
<div class="form-group">
<label for="store">type</label>
<label for="type">Type</label>
<select class="form-control" id="type" name="type">
<option value="A" <?php if ($product_data['type'] == "A") echo "selected"; ?>>A</option>
<option value="B" <?php if ($product_data['type'] == "B") echo "selected"; ?>>B</option>
<option value="C" <?php if ($product_data['type'] == "C") echo "selected"; ?>>C</option>
<option value="D" <?php if ($product_data['type'] == "D") echo "selected"; ?>>D</option>
</select>
</div>
</div>
</div>
</div> <!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-primary">Enregistrer</button>
<a href="<?php echo base_url('products/') ?>" class="btn btn-warning">Retour</a>
</div>
</form>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- col-md-12 -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
</div> <!-- /.box -->
</div> <!-- /.col-md-12 -->
</div> <!-- /.row -->
</section> <!-- /.content -->
</div> <!-- /.content-wrapper -->
<script type="text/javascript">
$(document).ready(function() {
// --- Initialisations ---
$(".select_group").select2();
$("#description").wysihtml5();
$("#mainProductNav").addClass('active');
$("#manageProductNav").addClass('active');
// --- Gestion image ---
var btnCust = '<button type="button" class="btn btn-secondary" title="Add picture tags" ' +
'onclick="alert(\'Call your custom code here.\')">' +
'<i class="glyphicon glyphicon-tag"></i>' +
@ -269,60 +249,94 @@
removeTitle: 'Cancel or reset changes',
elErrorContainer: '#kv-avatar-errors-1',
msgErrorClass: 'alert alert-block alert-danger',
// defaultPreviewContent: '<img src="/uploads/default_avatar_male.jpg" alt="Your Avatar">',
layoutTemplates: {
main2: '{preview} ' + btnCust + ' {remove} {browse}'
},
allowedFileExtensions: ["jpg", "png", "gif", "jpeg", 'webp']
layoutTemplates: { main2: '{preview} ' + btnCust + ' {remove} {browse}' },
allowedFileExtensions: ["jpg", "png", "gif", "jpeg", "webp"]
});
// --- Gestion champs supplémentaires ---
const info = document.getElementById('info');
const divSupp = document.getElementById('boxsuplementaire')
window.addEventListener('load', () => {
if (info.value == 1) {
divSupp.innerHTML = `
<label for="infoManque">Information</label>
<textarea type="text" class="form-control" id="infoManque" name="infoManque" placeholder="Description sur des trucs manquants" autocomplete="off"><?php echo $product_data['infoManque']; ?></textarea>
`;
const divSupp = document.getElementById('boxsuplementaire');
const etat = document.getElementById('etat');
const boxsuplementairekit = document.getElementById('boxsuplementairekit');
function afficherSupplements() {
if (info && info.value == 1) {
divSupp.innerHTML = `<label for="infoManque">Information</label>
<textarea type="text" class="form-control" id="infoManque" name="infoManque" placeholder="Description sur des trucs manquants" autocomplete="off"><?php echo $product_data['infoManque']; ?></textarea>`;
} else {
divSupp.innerHTML = '';
}
if (etat.value == 1) {
boxsuplementairekit.innerHTML = `
<label for="infoManquekit">Information sur le kit</label>
<textarea type="text" class="form-control" id="infoManquekit" name="infoManquekit" placeholder="Description sur le kit" autocomplete="off"><?php echo $product_data['infoManquekit']; ?></textarea>
`;
if (etat && etat.value == 1) {
boxsuplementairekit.innerHTML = `<label for="infoManquekit">Information sur le kit</label>
<textarea type="text" class="form-control" id="infoManquekit" name="infoManquekit" placeholder="Description sur le kit" autocomplete="off"><?php echo $product_data['infoManquekit']; ?></textarea>`;
} else {
boxsuplementairekit.innerHTML = '';
}
})
info.addEventListener('change', () => {
if (info.value == 1) {
divSupp.innerHTML = `
<label for="infoManque">Information</label>
<textarea type="text" class="form-control" id="infoManque" name="infoManque" placeholder="Description sur des trucs manquants" autocomplete="off"><?php echo $product_data['infoManque']; ?></textarea>
`;
} else {
divSupp.innerHTML = '';
}
})
const etat = document.getElementById('etat');
const boxsuplementairekit = document.getElementById('boxsuplementairekit')
etat.addEventListener('change', () => {
if (etat.value == 1) {
boxsuplementairekit.innerHTML = `
<label for="infoManquekit">Information sur le kit</label>
<textarea type="text" class="form-control" id="infoManquekit" name="infoManquekit" placeholder="Description sur le kit" autocomplete="off"><?php echo $product_data['infoManquekit']; ?></textarea>
`;
window.addEventListener('load', afficherSupplements);
if (info) info.addEventListener('change', afficherSupplements);
if (etat) etat.addEventListener('change', afficherSupplements);
// --- Vérification prix minimal < prix de vente ---
const priceMinInput = document.getElementById('price_min');
const priceVenteInput = document.getElementById('price_vente');
const priceMinError = document.getElementById('price_min_error');
function verifierPrix() {
let prixMin = parseFloat(priceMinInput.value.replace(/\s/g, '')) || 0;
let prixVente = parseFloat(priceVenteInput.value.replace(/\s/g, '')) || 0;
if (prixMin > prixVente) {
priceMinError.style.display = 'block';
} else {
boxsuplementairekit.innerHTML ='';
priceMinError.style.display = 'none';
}
}
})
if (priceMinInput) priceMinInput.addEventListener('input', verifierPrix);
if (priceVenteInput) priceVenteInput.addEventListener('input', verifierPrix);
// --- Formatage automatique des prix (avec affichage initial) ---
['price', 'price_vente', 'price_min'].forEach(id => {
const input = document.getElementById(id);
if (input) {
// Si c’est un input number, on le convertit en texte pour garder les espaces
if (input.type === "number") input.type = "text";
// 🟢 Formate dès la frappe
input.addEventListener('input', function(e) {
let val = e.target.value.replace(/\D/g, ''); // garde seulement les chiffres
e.target.value = val.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
});
// 🟢 Formate aussi dès le chargement de la page (valeurs de la base)
let initialVal = input.value.replace(/\D/g, '');
if (initialVal) {
input.value = initialVal.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
}
});
// --- Nettoyage des espaces avant soumission du formulaire ---
$("form").on('submit', function(e) {
let prixMin = parseFloat(priceMinInput.value.replace(/\s/g, '')) || 0;
let prixVente = parseFloat(priceVenteInput.value.replace(/\s/g, '')) || 0;
if (prixMin > prixVente) {
e.preventDefault();
alert("❌ Le prix minimal ne peut pas être supérieur au prix de vente !");
priceMinInput.focus();
return false;
}
// 🧹 Supprime les espaces avant envoi
['price', 'price_vente', 'price_min'].forEach(id => {
const input = document.getElementById(id);
if (input) {
input.value = input.value.replace(/\s/g, '');
}
});
});
});
</script>

1
app/Views/securite/index.php

@ -113,7 +113,6 @@ $(function() {
],
order: [[1, 'asc']]
});
});
// ===== Edition / validation =====
function editFunc(id) {

1136
app/Views/sortieCaisse/index.php

File diff suppressed because it is too large

125
app/Views/templates/header_menu.php

@ -1,20 +1,18 @@
<header class="main-header">
<!-- Logo -->
<a href="#" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b><img src="<?= base_url('assets/images/company_logo.jpg') ?>"></b></span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg"><b>MotorBike</b></span>
</a>
<!-- Header Navbar: style can be found in header.less -->
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" style="position: relative; height: 50px; background: #3c8dbc;">
<!-- Sidebar toggle button -->
<a href="#" class="sidebar-toggle" onclick="toggleSidebar()">
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Notifications à droite -->
<!-- Notifications -->
<ul class="navbar-nav" style="position: absolute; right: 15px; top: 50%; transform: translateY(-50%); margin: 0; padding: 0; list-style: none;">
<li class="nav-item dropdown" style="position: relative;">
<i class="fa fa-bell" id="notificationIcon" style="font-size: 20px; cursor: pointer; color:white;" data-toggle="dropdown"></i>
@ -31,71 +29,49 @@
</nav>
</header>
<script>
$(document).ready(function() {
const checkElement = setInterval(() => {
const notificationList = document.getElementById('notificationList');
if (notificationList && notificationList.innerHTML) {
clearInterval(checkElement); // Stop checking
const elementother = document.querySelectorAll('.notification_item');
elementother.forEach((notif) => {
notif.addEventListener('click', () => {
let notifId = notif.dataset.id;
// AJAX request to mark the notification as read
fetch("<?= base_url('notifications/markAsRead') ?>/" + notifId, {
method: "POST",
headers: {
"Content-Type": "application/json"
<!-- Styles -->
<style>
/* Notifications non lues */
.notification_item.unread {
font-weight: bold;
background-color: #f0f8ff; /* bleu clair */
}
})
.then(response => response.json()) // Parse JSON response
.then(data => {
console.log("Notification marked as read:", data);
location.reload(); // Refresh to update notification count
})
.catch(error => console.error("Error:", error));
});
});
/* Icône bleue pour notifications non lues */
.icon-unread {
color: #007bff; /* bleu */
}
}, 100); // Check every 100ms
})
</style>
<script>
function fetchNotifications() {
$.ajax({
url: '/notifications', // Change this URL to your actual PHP script
url: '/notifications', // route NotificationController::getNotification
type: 'GET',
dataType: 'json',
success: function(data) {
let notificationCount = 0;
let notificationHTML = '';
let notificationCount = data.length;
$('#notificationHeader').text(notificationCount + ' Notifications');
if (notificationCount > 0) {
$('#notificationCount').text(notificationCount).show();
} else {
$('#notificationCount').hide();
data.forEach(notif => {
// Compter les notifications non lues
if (notif.is_read == 0) {
notificationCount++;
}
let notificationHTML = `
`;
data.forEach(notif => {
// On enlève d’éventuels slashs au début puis on préfixe d’un slash
const href = '/' + notif.link.replace(/^\/+/, '');
const notifClass = notif.is_read == 0 ? "notification_item unread" : "notification_item";
// Icône uniquement pour non lu
const iconHTML = notif.is_read == 0 ? '<i class="fa fa-exclamation-circle icon-unread mr-9"></i>' : '';
notificationHTML += `
<a href="${href}"
class="dropdown-item notification_item"
class="${notifClass}"
data-id="${notif.id}"
data-message="${notif.message}"
style="font-size: 15px; display: flex; align-items: center; justify-content: space-between;">
<span style="display: flex; align-items: center; gap:10px;">
<i class="fas fa-exclamation-circle mr-9"></i> ${notif.message}
${iconHTML} ${notif.message}
</span>
<span class="float-right text-muted text-sm">${notif.created_at}</span>
</a>
@ -103,49 +79,44 @@ data.forEach(notif => {
`;
});
notificationHTML += `
`;
// Injecter les notifications
$('#notificationList').html(notificationHTML);
$('#notificationHeader').text(data.length + ' Notifications');
// Badge pour non lues
if (notificationCount > 0) {
$('#notificationCount').text(notificationCount).show();
} else {
$('#notificationCount').hide();
}
const elementother = document.querySelectorAll('.notification_item');
elementother.forEach((notif) => {
notif.addEventListener('click', () => {
let notifId = notif.dataset.id;
// AJAX request to mark the notification as read
// Ajouter l'événement clic pour marquer comme lu
const items = document.querySelectorAll('.notification_item');
items.forEach(item => {
item.addEventListener('click', () => {
const notifId = item.dataset.id;
fetch("<?= base_url('notifications/markAsRead') ?>/" + notifId, {
method: "POST",
headers: {
"Content-Type": "application/json"
}
headers: { "Content-Type": "application/json" }
})
.then(response => response.json()) // Parse JSON response
.then(response => response.json())
.then(data => {
console.log("Notification marked as read:", data);
// location.reload(); // Refresh to update notification count
// l'icône disparaîtra au prochain fetch
})
.catch(error => console.error("Error:", error));
});
});
},
error: function(error) {
console.error('Error fetching notifications:', error);
error: function(err) {
console.error('Error fetching notifications:', err);
}
});
}
// Fetch notifications every 10 seconds
// Rafraîchir toutes les 10 secondes
setInterval(fetchNotifications, 10000);
// Initial fetch when the page loads
// Premier chargement
fetchNotifications();
</script>
<!-- Left side column. contains the logo and sidebar -->

Loading…
Cancel
Save