motorbike/app/Models/Avance.php
2025-10-03 02:13:54 +03:00

778 lines
28 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

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

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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'));
}
}
// ✅ Mettre à jour l'avance
$updateResult = $this->update($id, $data);
if (!$updateResult) {
return false;
}
// ✅ AJOUT CRITIQUE : Vérifier automatiquement si conversion nécessaire
$this->autoCheckAndConvert($id);
return true;
} catch (\Exception $e) {
log_message('error', 'Erreur lors de la mise à jour de l\'avance : ' . $e->getMessage());
return false;
}
}
private function autoCheckAndConvert(int $avance_id)
{
try {
// Recharger l'avance fraîchement mise à jour
$avance = $this->find($avance_id);
if (!$avance) {
log_message('warning', "⚠️ Avance {$avance_id} introuvable pour vérification auto");
return false;
}
// ✅ Conditions de conversion automatique
$shouldConvert = (
$avance['type_avance'] === 'terre' && // C'est une avance sur terre
(float)$avance['amount_due'] <= 0.01 && // Montant dû = 0 (avec tolérance)
$avance['is_order'] == 0 && // Pas encore convertie
$avance['active'] == 1 // Encore active
);
if ($shouldConvert) {
log_message('info', "🔄 [AUTO-CHECK] Avance {$avance_id} complète détectée ! Conversion automatique...");
// ✅ Appeler la conversion
$order_id = $this->convertToOrder($avance_id);
if ($order_id) {
log_message('info', "✅ [AUTO-CHECK] Conversion réussie → Commande {$order_id}");
return $order_id;
} else {
log_message('error', "❌ [AUTO-CHECK] Échec conversion avance {$avance_id}");
return false;
}
} else {
// Log pour débogage
$reason = '';
if ($avance['type_avance'] !== 'terre') $reason .= 'type=' . $avance['type_avance'] . ' ';
if ((float)$avance['amount_due'] > 0.01) $reason .= 'reste=' . $avance['amount_due'] . ' ';
if ($avance['is_order'] == 1) $reason .= 'déjà_convertie ';
if ($avance['active'] == 0) $reason .= 'inactive ';
if (!empty($reason)) {
log_message('info', " [AUTO-CHECK] Avance {$avance_id} non éligible pour conversion : {$reason}");
}
}
return false;
} catch (\Exception $e) {
log_message('error', "❌ [AUTO-CHECK] Erreur vérification avance {$avance_id}: " . $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)
{
$db = \Config\Database::connect();
try {
$db->transStart();
$avance = $this->find($avance_id);
if (!$avance) {
log_message('error', "❌ Avance {$avance_id} introuvable");
return false;
}
// ✅ VÉRIFICATION 1 : Type d'avance
if ($avance['type_avance'] !== 'terre') {
log_message('info', "⚠️ Avance {$avance_id} est MER, pas de conversion");
return false;
}
// ✅ VÉRIFICATION 2 : Paiement complet
if ($avance['amount_due'] > 0) {
log_message('warning', "⚠️ Avance {$avance_id} non complète (reste: {$avance['amount_due']})");
return false;
}
// ✅ VÉRIFICATION 3 : Déjà convertie ?
if ($avance['is_order'] == 1) {
log_message('info', "⚠️ Avance {$avance_id} déjà convertie");
// ✅ Récupérer l'ID de la commande existante
$existingOrder = $db->table('orders')
->select('id')
->where('customer_name', $avance['customer_name'])
->where('customer_phone', $avance['customer_phone'])
->where('source', 'Avance convertie')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
return $existingOrder ? $existingOrder['id'] : false;
}
// ✅ VÉRIFICATION 4 : Produit existe ?
$Products = new \App\Models\Products();
$product = $Products->find($avance['product_id']);
if (!$product) {
log_message('error', "❌ Produit {$avance['product_id']} introuvable");
return false;
}
// ✅ Récupérer l'utilisateur actuel
$session = session();
$user = $session->get('user');
// ✅ Générer le numéro de commande
$bill_no = $this->generateBillNo($avance['store_id']);
// ✅ Préparer les données de commande
$orderData = [
'bill_no' => $bill_no,
'document_type' => 'facture',
'customer_name' => $avance['customer_name'],
'customer_address' => $avance['customer_address'],
'customer_phone' => $avance['customer_phone'],
'customer_cin' => $avance['customer_cin'],
'customer_type' => 'Particulier',
'source' => 'Avance convertie', // ✅ MARQUEUR IMPORTANT
'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 de validation
'user_id' => $user['id'] ?? $avance['user_id'],
'store_id' => $avance['store_id'],
'tranche_1' => $avance['avance_amount'],
'tranche_2' => 0,
'order_payment_mode' => $avance['type_payment'] ?? 'En espèce',
'service_charge_rate' => 0,
'vat_charge_rate' => 0,
'vat_charge' => 0,
];
// ✅ Créer la commande
$db->table('orders')->insert($orderData);
$order_id = $db->insertID();
if (!$order_id) {
throw new \Exception("Échec création commande pour avance {$avance_id}");
}
// ✅ CORRECTION CRITIQUE : Créer l'item de commande SANS 'qty'
$orderItemData = [
'order_id' => $order_id,
'product_id' => $avance['product_id'],
'rate' => $avance['gross_amount'],
'amount' => $avance['gross_amount'],
'puissance' => $product['puissance'] ?? '',
];
$db->table('orders_item')->insert($orderItemData);
// ✅ MARQUER L'AVANCE COMME CONVERTIE
$this->update($avance_id, [
'is_order' => 1,
'active' => 0 // ✅ Désactiver pour ne plus apparaître dans les listes
]);
// ✅ Le produit RESTE product_sold = 1 (déjà marqué lors de la création de l'avance)
$db->transComplete();
if ($db->transStatus() === false) {
log_message('error', "❌ Transaction échouée pour avance {$avance_id}");
return false;
}
log_message('info', "✅ Avance {$avance_id} convertie en commande {$order_id}");
// ✅ Envoyer notification
$this->sendConversionNotification($avance, $order_id, $bill_no);
return $order_id;
} catch (\Exception $e) {
$db->transRollback();
log_message('error', "❌ Erreur conversion avance {$avance_id}: " . $e->getMessage());
return false;
}
}
private function sendConversionNotification($avance, $order_id, $bill_no)
{
try {
$Notification = new \App\Controllers\NotificationController();
$Stores = new \App\Models\Stores();
$allStores = $Stores->getActiveStore();
$message = "🔄 Avance TERRE convertie en commande<br>" .
"N° Avance : #{$avance['avance_id']}<br>" .
"Client : {$avance['customer_name']}<br>" .
"Commande : {$bill_no}<br>" .
"Montant : " . number_format($avance['gross_amount'], 0, ',', ' ') . " Ar";
// ✅ Notifier tous les stores (Direction, DAF, SuperAdmin)
if (is_array($allStores) && count($allStores) > 0) {
foreach ($allStores as $store) {
foreach (['Direction', 'DAF', 'SuperAdmin'] as $role) {
$Notification->createNotification(
$message,
$role,
(int)$store['id'],
'orders'
);
}
}
}
// ✅ Caissière du store concerné
$Notification->createNotification(
$message,
"Caissière",
(int)$avance['store_id'],
'orders'
);
} catch (\Exception $e) {
log_message('error', '❌ Erreur notification: ' . $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 generateBillNo(int $store_id): string
{
$storePrefixes = [
1 => 'ANTS',
2 => 'BESA',
3 => 'BYPA',
4 => 'TOAM',
];
$prefix = $storePrefixes[$store_id] ?? 'STORE';
$db = \Config\Database::connect();
$lastBill = $db->table('orders')
->select('bill_no')
->like('bill_no', $prefix . '-', 'after')
->orderBy('id', 'DESC')
->limit(1)
->get()
->getRowArray();
if ($lastBill && !empty($lastBill['bill_no'])) {
preg_match('/-(\d+)$/', $lastBill['bill_no'], $matches);
$newNumber = isset($matches[1]) ? (int)$matches[1] + 1 : 1;
} else {
$newNumber = 1;
}
return $prefix . '-' . str_pad($newNumber, 3, '0', STR_PAD_LEFT);
}
/**
* ✅ Récupérer toutes les avances complètes non converties
*/
public function getCompletedNotConverted()
{
return $this->where('type_avance', 'terre')
->where('amount_due <=', 0)
->where('is_order', 0)
->where('active', 1)
->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();
}
}