verifyRole('viewAvance');
$data['page_title'] = $this->pageTitle;
$Products = new Products();
$session = session();
$users = $session->get('user');
$store_id = $users['store_id'];
$data['products'] = $Products->getProductDataStore($store_id);
return $this->render_template('avances/avance', $data);
}
private function isAdmin($user)
{
return in_array($user['group_name'], ['Conseil', 'Direction']);
}
private function isCommerciale($user)
{
return in_array($user['group_name'], ['COMMERCIALE']);
}
private function isCaissier($user)
{
return in_array($user['group_name'], ['Caissier']);
}
private function buildActionButtons($value, $isAdmin, $isOwner)
{
$buttons = '';
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= ' ';
}
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) {
$buttons .= ' ';
}
if (in_array('viewAvance', $this->permission) && !$isAdmin) {
$buttons .= ''
. '';
}
return $buttons;
}
private function buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCaissier, $buttons)
{
$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'],
$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, ',', ' '),
$date_time,
$buttons,
];
} elseif ($isCommerciale || $isCaissier) {
return [
$value['avance_id'],
$productName, // ✅ Utiliser la variable
number_format((int)$value['avance_amount'], 0, ',', ' '),
number_format((int)$value['amount_due'], 0, ',', ' '),
$date_time,
$buttons,
];
}
return [];
}
private function fetchAvanceDataGeneric($methodName = 'getAllAvanceData')
{
helper(['url', 'form']);
$Avance = new Avance();
$product = new Products();
$result = ['data' => []];
$data = $Avance->$methodName();
$session = session();
$users = $session->get('user');
$isAdmin = $this->isAdmin($users);
$isCommerciale = $this->isCommerciale($users);
$isCaissier = $this->isCaissier($users);
foreach ($data as $key => $value) {
$isOwner = $users['id'] === $value['user_id'];
$buttons = $this->buildActionButtons($value, $isAdmin, $isOwner);
$row = $this->buildDataRow($value, $product, $isAdmin, $isCommerciale, $isCaissier, $buttons);
if (!empty($row)) {
$result['data'][] = $row;
}
}
return $this->response->setJSON($result);
}
public function fetchAvanceData()
{
// Avances incomplètes (reste à payer > 0)
return $this->fetchAvanceDataGeneric('getIncompleteAvances');
}
public function fetchAvanceBecameOrder()
{
// Avances complètes (reste à payer = 0)
return $this->fetchAvanceDataGeneric('getCompletedAvances');
}
public function fetcheExpiredAvance()
{
return $this->fetchAvanceDataGeneric('getAllAvanceData2');
}
/**
* Méthode pour vérifier et envoyer des emails d'alerte 3 jours avant deadline
* À exécuter via CRON job quotidiennement
*/
public function checkDeadlineAlerts()
{
try {
$Avance = new Avance();
$Products = new Products();
// Récupérer toutes les avances actives non converties en commandes
$avances = $Avance->getAvancesNearDeadline(3); // 3 jours avant deadline
if (!empty($avances)) {
foreach ($avances as $avance) {
// Vérifier si l'email n'a pas déjà été envoyé pour cette avance
if (!$this->hasEmailBeenSent($avance['avance_id'])) {
$this->sendDeadlineAlert($avance, $Products);
$this->markEmailAsSent($avance['avance_id']);
}
}
}
return $this->response->setJSON([
'success' => true,
'messages' => 'Vérification des alertes terminée',
'alerts_sent' => count($avances)
]);
} catch (\Exception $e) {
log_message('error', "Erreur vérification deadline: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la vérification des deadlines'
]);
}
}
/**
* Envoyer un email d'alerte au DAF et à la Directrice
*/
private function sendDeadlineAlert($avance, $Products)
{
try {
$email = \Config\Services::email();
// Configuration email (à adapter selon votre config)
$email->setFrom('noreply@yourcompany.com', 'Système de Gestion des Avances');
// Récupérer les emails du DAF et de la Directrice
$recipients = $this->getDAFAndDirectriceEmails($avance['store_id']);
$email->setTo($recipients);
$email->setSubject('⚠️ ALERTE: Avance arrive à échéance dans 3 jours');
// Récupérer le nom du produit
$productName = $Products->getProductNameById($avance['product_id']);
// Calcul des jours restants
$deadline = new \DateTime($avance['deadline']);
$today = new \DateTime();
$daysRemaining = $today->diff($deadline)->days;
// Corps de l'email
$message = $this->buildEmailMessage($avance, $productName, $daysRemaining);
$email->setMessage($message);
// Envoyer l'email
if ($email->send()) {
log_message('info', "Email d'alerte envoyé pour l'avance ID: " . $avance['avance_id']);
return true;
} else {
log_message('error', "Échec envoi email pour avance ID: " . $avance['avance_id'] . " - " . $email->printDebugger());
return false;
}
} catch (\Exception $e) {
log_message('error', "Erreur envoi email alerte: " . $e->getMessage());
return false;
}
}
/**
* Récupérer les emails du DAF et de la Directrice
*/
private function getDAFAndDirectriceEmails($store_id)
{
$User = new User();
$emails = [];
// Récupérer les utilisateurs avec les rôles DAF et Direction pour le store donné
$dafUsers = $User->getUsersByRole('DAF', $store_id);
$directionUsers = $User->getUsersByRole('Direction', $store_id);
// Extraire les emails
foreach ($dafUsers as $user) {
if (!empty($user['email'])) {
$emails[] = $user['email'];
}
}
foreach ($directionUsers as $user) {
if (!empty($user['email'])) {
$emails[] = $user['email'];
}
}
// Si aucun email trouvé, utiliser des emails par défaut (à configurer)
if (empty($emails)) {
$emails = [
'daf@yourcompany.com',
'direction@yourcompany.com'
];
}
return array_unique($emails); // Éviter les doublons
}
/**
* Construire le message de l'email
*/
private function buildEmailMessage($avance, $productName, $daysRemaining)
{
$typeAvance = strtoupper($avance['type_avance']);
$deadlineFormatted = date('d/m/Y', strtotime($avance['deadline']));
$avanceDateFormatted = date('d/m/Y à H:i', strtotime($avance['avance_date']));
$amountDueFormatted = number_format($avance['amount_due'], 0, ',', ' ') . ' FCFA';
$urgencyClass = $daysRemaining <= 1 ? 'style="color: red; font-weight: bold;"' : '';
return "
Une avance arrive à échéance dans {$daysRemaining} jour(s) !
Détails de l'avance :
- ID Avance : #{$avance['avance_id']}
- Type d'avance : {$typeAvance}
- Client : {$avance['customer_name']}
- Téléphone : {$avance['customer_phone']}
- Adresse : {$avance['customer_address']}
- CIN : {$avance['customer_cin']}
- Produit : {$productName}
- Montant restant dû : {$amountDueFormatted}
- Date avance : {$avanceDateFormatted}
- Date limite : {$deadlineFormatted}
Action requise :
Veuillez contacter le client pour régulariser le paiement avant l'échéance ou prendre les mesures appropriées.
";
}
/**
* Vérifier si un email a déjà été envoyé pour cette avance
*/
private function hasEmailBeenSent($avance_id)
{
$db = \Config\Database::connect();
$query = $db->query("SELECT id FROM email_alerts WHERE avance_id = ? AND alert_type = 'deadline_3days'", [$avance_id]);
return $query->getNumRows() > 0;
}
/**
* Marquer l'email comme envoyé
*/
private function markEmailAsSent($avance_id)
{
$db = \Config\Database::connect();
$data = [
'avance_id' => $avance_id,
'alert_type' => 'deadline_3days',
'sent_date' => date('Y-m-d H:i:s'),
'status' => 'sent'
];
$db->table('email_alerts')->insert($data);
}
public function createAvance()
{
$this->verifyRole('createAvance');
if ($this->request->getMethod() !== 'post') {
return $this->response->setJSON([
'success' => false,
'messages' => 'Méthode non autorisée'
]);
}
try {
$session = session();
$users = $session->get('user');
$Avance = new Avance();
$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();
$baseRules = [
'customer_name_avance' => 'required|min_length[2]',
'customer_phone_avance' => 'required',
'customer_address_avance' => 'required',
'customer_cin_avance' => 'required',
'gross_amount' => 'required|numeric|greater_than[0]',
'avance_amount' => 'required|numeric|greater_than[0]',
'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([
'success' => false,
'messages' => 'Données invalides: ' . implode(', ', $validation->getErrors())
]);
}
$avance_date = date('Y-m-d H:i:s');
// 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;
}
// Préparer les données communes
$data = [
'type_avance' => $type_avance,
'type_payment' => $this->request->getPost('type_payment'),
'customer_name' => $this->request->getPost('customer_name_avance'),
'customer_address' => $this->request->getPost('customer_address_avance'),
'customer_phone' => $this->request->getPost('customer_phone_avance'),
'customer_cin' => $this->request->getPost('customer_cin_avance'),
'avance_date' => $avance_date,
'deadline' => $deadline,
'user_id' => $users['id'],
'store_id' => $users['store_id'],
'gross_amount' => (float)$this->request->getPost('gross_amount'),
'avance_amount' => (float)$this->request->getPost('avance_amount'),
'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',
"Conseil",
(int)$users['store_id'],
'avances'
);
return $this->response->setJSON([
'success' => true,
'messages' => 'Avance créée avec succès !',
'avance_id' => $avance_id
]);
} else {
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la création de l\'avance'
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur création avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue: ' . $e->getMessage()
]);
}
}
public function updateAvance()
{
$this->verifyRole('updateAvance');
if ($this->request->getMethod() !== 'post') {
return $this->response->setJSON([
'success' => false,
'messages' => 'Méthode non autorisée'
]);
}
try {
$session = session();
$users = $session->get('user');
$Avance = new Avance();
$Products = new Products();
$Notification = new NotificationController();
// ✅ 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();
$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',
'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_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([
'success' => false,
'messages' => 'Données invalides: ' . implode(', ', $validation->getErrors())
]);
}
$avance_id = $this->request->getPost('id');
// Vérifier si l'avance existe
$existingAvance = $Avance->fetchSingleAvance($avance_id);
if (!$existingAvance) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Avance non trouvée'
]);
}
// Vérifier les permissions (admin ou propriétaire)
$isAdmin = $this->isAdmin($users);
$isOwner = $users['id'] === $existingAvance['user_id'];
if (!$isAdmin && !$isOwner) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Vous n\'avez pas les droits pour modifier cette avance'
]);
}
// Recalculer la deadline si le type d'avance a changé
$current_deadline = $existingAvance['deadline'];
if ($type_avance !== $existingAvance['type_avance']) {
if ($type_avance === 'terre') {
$current_deadline = date('Y-m-d', strtotime($existingAvance['avance_date'] . ' +15 days'));
} elseif ($type_avance === 'mere') {
$current_deadline = date('Y-m-d', strtotime($existingAvance['avance_date'] . ' +2 months'));
}
}
$old_product_id = $existingAvance['product_id'];
// Préparer les données communes
$data = [
'type_avance' => $type_avance,
'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,
'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 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]);
}
}
$Notification->createNotification(
'Une avance a été modifiée',
"Conseil",
(int)$users['store_id'],
'avances'
);
return $this->response->setJSON([
'success' => true,
'messages' => 'Avance modifiée avec succès !'
]);
} else {
return $this->response->setJSON([
'success' => false,
'messages' => 'Erreur lors de la modification de l\'avance'
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur modification avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue: ' . $e->getMessage()
]);
}
}
public function removeAvance()
{
$this->verifyRole('deleteAvance');
try {
$avance_id = $this->request->getPost('avance_id');
$product_id = $this->request->getPost('product_id');
if (!$avance_id || !$product_id) {
return $this->response->setJSON([
'success' => false,
'messages' => 'Données manquantes pour la suppression'
]);
}
$Avance = new Avance();
$Products = new Products();
if ($Avance->removeAvance($avance_id)) {
$Products->update($product_id, ['product_sold' => 0]);
return $this->response->setJSON([
'success' => true,
'messages' => "Avance supprimée avec succès. Le produit peut être réservé à nouveau."
]);
} else {
return $this->response->setJSON([
'success' => false,
'messages' => "Erreur lors de la suppression de l'avance"
]);
}
} catch (\Exception $e) {
log_message('error', "Erreur suppression avance: " . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'messages' => 'Une erreur interne est survenue'
]);
}
}
public function fetchSingleAvance($avance_id)
{
$this->verifyRole('updateAvance');
try {
if (!$avance_id || !is_numeric($avance_id)) {
return $this->response->setStatusCode(400)->setJSON([
'error' => 'ID d\'avance invalide'
]);
}
$avanceModel = new Avance();
$data = $avanceModel->fetchSingleAvance($avance_id);
if (!$data) {
return $this->response->setStatusCode(404)->setJSON([
'error' => 'Avance non trouvée'
]);
}
return $this->response->setJSON($data);
} catch (\Exception $e) {
log_message('error', "Erreur récupération avance: " . $e->getMessage());
return $this->response->setStatusCode(500)->setJSON([
'error' => 'Erreur interne lors de la récupération de l\'avance'
]);
}
}
}