motorbike/app/Models/Avance.php
andrymodeste a195d24e78 feat: refonte gestion commandes, sécurité, impressions et notifications
## Gestion Moto & Commandes
- Correction des notifications pour la Caissière
- Le bouton "Enregistrer" changé en "Payer"
- La moto commandée reste visible dans la liste jusqu'à livraison par la sécurité

## Espace Sécurité
- Ajout des notifications de livraison
- Transfert vers l'espace commande après livraison

## Espace SuperAdmin
- Rejet de commande : le produit redevient disponible en stock automatiquement
- Correction de la gestion des rôles (permissions inversées)
- Avance complète : s'affiche directement chez la Caissière

## Historique des Actions
- Ajout de l'historique des actions pour SuperAdmin (traçabilité)

## Dashboard
- Filtre par date ajouté (par défaut : aujourd'hui)
- Affichage uniquement des données du site concerné

## Espace Commercial
- Liste des produits disponibles sur la liste déroulante dans l'ajout des commandes
- Le bouton "+" se cache après le premier clic pour les clients particuliers

## Impression Documents
- Refonte facture, bon de livraison, facture d'acompte (QR codes, infos dynamiques)

## Sidebar
- Correction des animations et du logo dynamique

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:28:05 +02:00

874 lines
31 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',
'validated', 'validated_by', 'validated_at'
];
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($startDate = null, $endDate = null) {
$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);
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
if ($startDate) {
$builder->where('DATE(avance_date) >=', $startDate);
}
if ($endDate) {
$builder->where('DATE(avance_date) <=', $endDate);
}
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
// ✅ MODIFICATION : Ne compter QUE les avances VALIDÉES
public function getPaymentModesAvance($startDate = null, $endDate = null)
{
$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('validated', 1)
->where('active', 1)
->where('is_order', 0);
if (!$isAdmin) {
$builder->where('store_id', $users['store_id']);
}
if ($startDate) {
$builder->where('DATE(avance_date) >=', $startDate);
}
if ($endDate) {
$builder->where('DATE(avance_date) <=', $endDate);
}
$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']);
$isCommercial = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissière']);
$builder = $this->where('is_order', 0)
->where('active', 1)
->where('amount_due >', 0);
// ✅ LOGIQUE PAR RÔLE
if ($isCommercial) {
// Commercial voit TOUTES ses avances (validées ET non validées)
$builder->where('user_id', $users['id']);
} elseif ($isCaissier) {
// Caissière voit UNIQUEMENT les avances validées de son store
$builder->where('validated', 1)
->where('store_id', $users['store_id']);
} elseif ($isAdmin) {
// Admin voit tout (pas de filtre supplémentaire)
} else {
// Autres rôles : ne rien afficher
$builder->where('1', '0'); // Condition toujours fausse
}
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']);
$isCommercial = in_array($users['group_name'], ['COMMERCIALE']);
$isCaissier = in_array($users['group_name'], ['Caissière']);
$builder = $this->where('amount_due', 0)
->groupStart()
->where('active', 1)
->orWhere('is_order', 1)
->groupEnd();
if ($isCommercial) {
$builder->where('user_id', $users['id']);
} elseif ($isCaissier) {
$builder->where('validated', 1)
->where('store_id', $users['store_id']);
} elseif (!$isAdmin) {
$builder->where('1', '0');
}
if ($id) {
$builder->where('user_id', $id);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
}
/**
* ✅ NOUVELLE : getPendingValidationAvances
*/
public function getPendingValidationAvances(int $store_id = null)
{
try {
$builder = $this->where('validated', 0)
->where('active', 1)
->where('is_order', 0);
if ($store_id) {
$builder->where('store_id', $store_id);
}
return $builder->orderBy('avance_date', 'DESC')->findAll();
} catch (\Exception $e) {
log_message('error', 'Erreur getPendingValidationAvances: ' . $e->getMessage());
return [];
}
}
/**
* ✅ VALIDATION D'UNE AVANCE
*/
public function validateAvance(int $avance_id, int $caissiere_id): bool
{
try {
$avance = $this->find($avance_id);
if (!$avance) {
log_message('error', "Avance {$avance_id} introuvable");
return false;
}
if ($avance['validated'] == 1) {
log_message('warning', "Avance {$avance_id} déjà validée");
return false;
}
$updateResult = $this->update($avance_id, [
'validated' => 1,
'validated_by' => $caissiere_id,
'validated_at' => date('Y-m-d H:i:s')
]);
if ($updateResult) {
log_message('info', "✅ Avance {$avance_id} validée par caissière {$caissiere_id}");
// Vérifier si conversion nécessaire
$this->autoCheckAndConvert($avance_id);
}
return $updateResult;
} catch (\Exception $e) {
log_message('error', "Erreur validation avance {$avance_id}: " . $e->getMessage());
return false;
}
}
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();
}
}