You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

721 lines
26 KiB

<?php
namespace App\Models;
use CodeIgniter\Model;
class Avance extends Model {
protected $table = 'avances';
protected $primaryKey = 'avance_id';
protected $allowedFields = [
'avance_amount', 'avance_date','user_id',
'customer_name', 'customer_address', 'customer_phone', 'customer_cin',
'gross_amount','amount_due','product_id','is_order','active','store_id',
'type_avance','type_payment', 'deadline','commentaire','product_name'
];
public function createAvance(array $data) {
try {
if (empty($data['avance_date'])) {
$data['avance_date'] = date('Y-m-d');
}
if (!empty($data['type'])) {
if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
} elseif (strtolower($data['type']) === 'avance sur mer') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +2 months'));
}
}
return $this->insert($data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de l\'ajout de l\'avance : ' . $e->getMessage());
return false;
}
}
public function updateAvance(int $id, array $data) {
if ($id <= 0) {
log_message('error', 'ID invalide pour la mise à jour du recouvrement : ' . $id);
return false;
}
try {
if (!empty($data['type']) && !empty($data['avance_date'])) {
if (strtolower($data['type']) === 'avance sur terre') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +15 days'));
} elseif (strtolower($data['type']) === 'avance sur mer') {
$data['deadline'] = date('Y-m-d', strtotime($data['avance_date'] . ' +2 months'));
}
}
return $this->update($id, $data);
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la mise à jour de l\'avance : ' . $e->getMessage());
return false;
}
}
public function getAllAvanceData(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function fetchSingleAvance(int $avance_id){
return $this->select('avances.*, products.name as product_name_db, products.prix_vente as product_price')
->join('products', 'products.id = avances.product_id', 'left')
->where('avances.avance_id', $avance_id)
->first();
}
public function removeAvance(int $avance_id){
return $this->delete($avance_id);
}
// ✅ CORRECTION : getTotalAvance pour la caissière
public function getTotalAvance() {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
try {
$builder = $this->select('SUM(avance_amount) AS ta')
->where('is_order', 0)
->where('active', 1); // ✅ Ajout du filtre active
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']); // ✅ Filtre par store pour caissière
}
return $builder->get()->getRowObject();
} catch (\Exception $e) {
log_message('error', 'Erreur lors du total du montant des avances : ' . $e->getMessage());
return (object) ['ta' => 0]; // ✅ Retourner un objet avec ta = 0 en cas d'erreur
}
}
// ✅ CORRECTION PRINCIPALE : getPaymentModesAvance pour la caissière
public function getPaymentModesAvance()
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
try {
$builder = $this->db->table('avances')
->select('
SUM(avance_amount) AS total,
SUM(CASE WHEN LOWER(type_payment) = "mvola" THEN avance_amount ELSE 0 END) AS total_mvola,
SUM(CASE WHEN LOWER(type_payment) = "en espèce" THEN avance_amount ELSE 0 END) AS total_espece,
SUM(CASE WHEN LOWER(type_payment) = "virement bancaire" THEN avance_amount ELSE 0 END) AS total_virement_bancaire
')
->where('active', 1)
->where('is_order', 0); // ✅ Exclure les avances devenues orders
// ✅ CORRECTION : Ajouter le filtre store_id pour la caissière
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
$result = $builder->get()->getRowObject();
// ✅ Gérer le cas où il n'y a pas de résultats
if (!$result) {
return (object) [
'total' => 0,
'total_mvola' => 0,
'total_espece' => 0,
'total_virement_bancaire' => 0
];
}
return $result;
} catch (\Exception $e) {
log_message('error', 'Erreur getPaymentModesAvance: ' . $e->getMessage());
return (object) [
'total' => 0,
'total_mvola' => 0,
'total_espece' => 0,
'total_virement_bancaire' => 0
];
}
}
public function getAllAvanceData1(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction','DAF']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',1)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',1)
->where('active',1)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',1)
->where('active',1)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',1) // ✅ Correction: devrait être 1, pas 0
->where('active',1) // ✅ Ajout du filtre active
->where('store_id',$users['store_id']) // ✅ Ajout du filtre store
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function getAllAvanceData2(int $id=null) {
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
if($isAdmin) {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',0)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',0)
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
} else {
if($id){
try {
return $this->where('user_id',$id)
->where('is_order',0)
->where('active',0)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
try {
return $this
->where('is_order',0)
->where('active',0)
->where('store_id',$users['store_id'])
->orderBy('avance_date', 'DESC')
->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la récupération des recouvrements : ' . $e->getMessage());
return false;
}
}
}
public function checkExpiredAvance() {
$now = date('Y-m-d');
$avances = $this->where('active', '1')
->where('deadline <', $now)
->findAll();
if (!empty($avances)) {
$productModel = new Products();
foreach ($avances as $avance) {
$this->update($avance['avance_id'], ['active' => '0']);
if (!empty($avance['product_id'])) { // ✅ Vérifier que product_id existe
$productModel->update($avance['product_id'], ['product_sold' => 0]);
}
}
}
}
public function getAvancesNearDeadline($days = 3)
{
$alertDate = date('Y-m-d', strtotime("+{$days} days"));
return $this->select('avances.*, users.store_id')
->join('users', 'users.id = avances.user_id')
->where('avances.is_order', 0)
->where('avances.active', 1)
->where('avances.amount_due >', 0)
->where('DATE(avances.deadline)', $alertDate)
->findAll();
}
public function getIncompleteAvances(int $id = null)
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$builder = $this->where('is_order', 0)
->where('active', 1)
->where('amount_due >', 0);
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
if ($id) {
$builder->where('user_id', $id);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
}
public function getCompletedAvances(int $id = null)
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$builder = $this->where('is_order', 0)
->where('active', 1)
->where('amount_due', 0);
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
if ($id) {
$builder->where('user_id', $id);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
}
public function markAsPrinted($avance_id)
{
try {
return $this->update($avance_id, [
'is_printed' => 1
]);
} catch (\Exception $e) {
log_message('error', 'Erreur markAsPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Marquer une avance comme non imprimée (quand elle est modifiée)
*/
public function markAsNotPrinted($avance_id)
{
try {
return $this->update($avance_id, [
'is_printed' => 0,
'last_modified_at' => date('Y-m-d H:i:s')
]);
} catch (\Exception $e) {
log_message('error', 'Erreur markAsNotPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Vérifier si une avance a déjà été imprimée
*/
public function isPrinted($avance_id)
{
try {
$avance = $this->find($avance_id);
return $avance ? (bool)$avance['is_printed'] : false;
} catch (\Exception $e) {
log_message('error', 'Erreur isPrinted: ' . $e->getMessage());
return false;
}
}
/**
* Récupérer un produit avec le nom de sa marque
* @param int $product_id
* @return array|null
*/
public function getProductWithBrand($product_id)
{
try {
return $this->select('products.*, brands.name as brand_name')
->join('brands', 'brands.id = products.marque', 'left')
->where('products.id', $product_id)
->first();
} catch (\Exception $e) {
log_message('error', 'Erreur getProductWithBrand: ' . $e->getMessage());
return null;
}
}
// À ajouter dans App\Models\Avance.php
/**
* ✅ Convertir une avance complète en commande
* Appelé automatiquement quand amount_due atteint 0
*
* @param int $avance_id
* @return int|false ID de la commande créée ou false
*/
public function convertToOrder(int $avance_id)
{
try {
$avance = $this->find($avance_id);
if (!$avance) {
log_message('error', "Avance introuvable : {$avance_id}");
return false;
}
// ✅ Vérifier que c'est bien une avance sur TERRE
if ($avance['type_avance'] !== 'terre') {
log_message('info', "Avance {$avance_id} de type '{$avance['type_avance']}' - Conversion ignorée (seules les avances TERRE sont converties)");
return false;
}
// ✅ Vérifier que l'avance est bien complète
if ((float)$avance['amount_due'] > 0) {
log_message('warning', "Avance TERRE {$avance_id} non complète (amount_due={$avance['amount_due']})");
return false;
}
// ✅ Vérifier qu'elle n'a pas déjà été convertie
if ((int)$avance['is_order'] === 1) {
log_message('info', "Avance TERRE {$avance_id} déjà convertie en commande");
return false;
}
// ✅ Vérifier que le produit existe (obligatoire pour avance TERRE)
if (empty($avance['product_id'])) {
log_message('error', "Avance TERRE {$avance_id} sans product_id - Impossible de convertir");
return false;
}
$db = \Config\Database::connect();
$db->transStart();
// ✅ 1. Créer la commande
$orderModel = new \App\Models\Orders();
$bill_no = 'BILAV-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4));
$orderData = [
'bill_no' => $bill_no,
'customer_name' => $avance['customer_name'],
'customer_address' => $avance['customer_address'],
'customer_phone' => $avance['customer_phone'],
'customer_cin' => $avance['customer_cin'],
'date_time' => date('Y-m-d H:i:s'),
'gross_amount' => $avance['gross_amount'],
'net_amount' => $avance['gross_amount'],
'discount' => 0,
'paid_status' => 2, // En attente validation caissière
'user_id' => $avance['user_id'],
'store_id' => $avance['store_id'],
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
'tranche_1' => $avance['avance_amount'],
'tranche_2' => null,
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
'order_payment_mode_1' => null,
];
$order_id = $orderModel->insert($orderData);
if (!$order_id) {
throw new \Exception("Échec création commande pour avance TERRE {$avance_id}");
}
log_message('info', "✅ Commande {$bill_no} créée depuis avance TERRE {$avance_id}");
// ✅ 2. Créer les items de commande
$orderItemModel = new \App\Models\OrderItems();
$productModel = new \App\Models\Products();
$product = $productModel->find($avance['product_id']);
if ($product) {
$orderItemModel->insert([
'order_id' => $order_id,
'product_id' => $avance['product_id'],
'rate' => $avance['gross_amount'],
'qty' => 1,
'amount' => $avance['gross_amount'],
]);
log_message('info', "Item ajouté : produit {$avance['product_id']} (TERRE)");
} else {
log_message('warning', "Produit {$avance['product_id']} introuvable pour avance TERRE {$avance_id}");
}
// ✅ 3. Marquer l'avance comme convertie
$this->update($avance_id, [
'is_order' => 1,
'active' => 0,
]);
$db->transComplete();
if ($db->transStatus() === false) {
log_message('error', "Transaction échouée pour avance TERRE {$avance_id}");
return false;
}
// ✅ 4. NOUVEAU : Envoyer notifications à TOUS les stores
$this->sendConversionNotifications($avance, $order_id, $bill_no);
// ✅ 5. Notification à la caissière du store concerné
$notificationController = new \App\Controllers\NotificationController();
$notificationController->createNotification(
"Nouvelle commande issue d'une avance TERRE complète - {$bill_no}",
"Caissière",
(int)$avance['store_id'],
"orders"
);
log_message('info', "✅ Avance TERRE {$avance_id} convertie en commande {$order_id} ({$bill_no})");
return $order_id;
} catch (\Exception $e) {
log_message('error', "Erreur conversion avance TERRE→commande : " . $e->getMessage());
return false;
}
}
private function sendConversionNotifications($avance, $order_id, $bill_no)
{
try {
$Notification = new \App\Controllers\NotificationController();
$db = \Config\Database::connect();
// Récupérer tous les stores
$storesQuery = $db->table('stores')->select('id')->get();
$allStores = $storesQuery->getResultArray();
// Récupérer les infos de l'utilisateur
$userQuery = $db->table('users')
->select('firstname, lastname')
->where('id', $avance['user_id'])
->get();
$user = $userQuery->getRowArray();
$userName = $user ? "{$user['firstname']} {$user['lastname']}" : 'Utilisateur inconnu';
// Préparer le message
$customerName = $avance['customer_name'];
$avanceNumber = str_pad($avance['avance_id'], 5, '0', STR_PAD_LEFT);
$avanceAmount = number_format((float)$avance['gross_amount'], 0, ',', ' ');
$typeAvance = strtoupper($avance['type_avance']);
$notificationMessage = "🎉 Avance {$typeAvance}{$avanceNumber} convertie en COMMANDE {$bill_no} - Client: {$customerName} - Montant: {$avanceAmount} Ar - Par: {$userName}";
// ✅ Envoyer notification à DAF, Direction et SuperAdmin de TOUS les stores
foreach ($allStores as $store) {
$storeId = (int)$store['id'];
// Notification pour DAF
$Notification->createNotification(
$notificationMessage,
"DAF",
$storeId,
'orders'
);
// Notification pour Direction
$Notification->createNotification(
$notificationMessage,
"Direction",
$storeId,
'orders'
);
// Notification pour SuperAdmin
$Notification->createNotification(
$notificationMessage,
"SuperAdmin",
$storeId,
'orders'
);
}
log_message('info', "✅ Notifications conversion envoyées pour avance {$avance['avance_id']} → commande {$order_id} à tous les stores");
} catch (\Exception $e) {
log_message('error', "Erreur envoi notifications conversion: " . $e->getMessage());
}
}
/**
* ✅ Hook appelé automatiquement lors du paiement d'une avance
* Intégrer ceci dans votre fonction de paiement existante
*/
public function afterPayment(int $avance_id)
{
$avance = $this->find($avance_id);
if (!$avance) {
return false;
}
// ✅ Si l'avance est maintenant complète ET que c'est une avance TERRE
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'terre') {
log_message('info', "💰 Avance TERRE {$avance_id} complète ! Conversion automatique en commande...");
return $this->convertToOrder($avance_id);
}
// ✅ Si c'est une avance MER complète, on ne fait rien (elle reste dans la liste)
if ((float)$avance['amount_due'] <= 0 && $avance['type_avance'] === 'mere') {
log_message('info', "💰 Avance MER {$avance_id} complète ! Elle reste dans la liste des avances.");
}
return true;
}
/**
* ✅ Générer un numéro de facture unique
*/
private function generateBillNumber($store_id)
{
$db = \Config\Database::connect();
// Récupérer le dernier numéro pour ce store
$query = $db->query(
"SELECT bill_no FROM orders WHERE store_id = ? ORDER BY id DESC LIMIT 1",
[$store_id]
);
$result = $query->getRow();
if ($result && preg_match('/(\d+)$/', $result->bill_no, $matches)) {
$lastNumber = intval($matches[1]);
$newNumber = $lastNumber + 1;
} else {
$newNumber = 1;
}
// Format: BILL-STORE{store_id}-{number}
return 'BILL-STORE' . $store_id . '-' . str_pad($newNumber, 5, '0', STR_PAD_LEFT);
}
/**
* ✅ Récupérer toutes les avances complètes non converties
*/
public function getCompletedNotConverted()
{
return $this->where('amount_due', 0)
->where('is_order', 0)
->where('active', 1)
->where('type_avance', 'terre') // ✅ Uniquement TERRE à convertir
->findAll();
}
/**
* ✅ NOUVELLE MÉTHODE : Récupérer les avances MER complètes (pour statistiques)
*/
public function getCompletedMerAvances()
{
$session = session();
$users = $session->get('user');
$isAdmin = in_array($users['group_name'], ['SuperAdmin', 'Direction', 'DAF']);
$builder = $this->where('amount_due', 0)
->where('active', 1)
->where('type_avance', 'mere');
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
}
}