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) { $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
" . "N° Avance : #{$avance['avance_id']}
" . "Client : {$avance['customer_name']}
" . "Commande : {$bill_no}
" . "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(); } }