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'])); if ($isAdmin) { return [ $value['customer_name'], $value['customer_phone'], $value['customer_address'], $product->getProductNameById($value['product_id']), 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'], $product->getProductNameById($value['product_id']), 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 "

⚠️ ALERTE DEADLINE AVANCE

Une avance arrive à échéance dans {$daysRemaining} jour(s) !

Détails de l'avance :

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(); $validation = \Config\Services::validation(); $validation->setRules([ 'customer_name_avance' => 'required|min_length[2]', 'customer_phone_avance' => 'required', 'customer_address_avance' => 'required', 'customer_cin_avance' => 'required', 'id_product' => 'required|numeric', 'avance_amount' => 'required|numeric|greater_than[0]', 'type_avance' => 'required|in_list[terre,mere]' ]); 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 automatique de la deadline selon le type d'avance $type_avance = $this->request->getPost('type_avance'); 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; // fallback si jamais } $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'], 'product_id' => (int)$this->request->getPost('id_product'), 'gross_amount' => (float)$this->request->getPost('gross_amount'), 'avance_amount' => (float)$this->request->getPost('avance_amount'), 'amount_due' => (float)$this->request->getPost('amount_due'), 'is_order' => 0, 'active' => 1, ]; if ($avance_id = $Avance->createAvance($data)) { $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' ]); } } 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(); // Validation des données (avec les vrais noms des champs du formulaire) $validation = \Config\Services::validation(); $validation->setRules([ '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', 'id_product_edit' => 'required|numeric', 'avance_amount_edit' => 'required|numeric|greater_than[0]', 'type_avance_edit' => 'required|in_list[terre,mere]' ]); 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'); // Changement ici // 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é $type_avance = $this->request->getPost('type_avance_edit'); // Changement ici $current_deadline = $existingAvance['deadline']; // Si le type a changé, recalculer la 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']; $new_product_id = (int)$this->request->getPost('id_product_edit'); // Changement ici $data = [ 'type_avance' => $type_avance, 'type_payment' => $this->request->getPost('type_payment_edit'), // Changement ici 'customer_name' => $this->request->getPost('customer_name_avance_edit'), // Changement ici 'customer_address' => $this->request->getPost('customer_address_avance_edit'), // Changement ici 'customer_phone' => $this->request->getPost('customer_phone_avance_edit'), // Changement ici 'customer_cin' => $this->request->getPost('customer_cin_avance_edit'), // Changement ici 'deadline' => $current_deadline, 'product_id' => $new_product_id, 'gross_amount' => (float)$this->request->getPost('gross_amount_edit'), // Changement ici 'avance_amount' => (float)$this->request->getPost('avance_amount_edit'), // Changement ici 'amount_due' => (float)$this->request->getPost('amount_due_edit'), // Changement ici ]; // Mettre à jour l'avance if ($Avance->updateAvance($avance_id, $data)) { // Gérer le changement de produit si nécessaire if ($old_product_id !== $new_product_id) { // Libérer l'ancien produit $Products->update($old_product_id, ['product_sold' => 0]); // Marquer le nouveau produit comme vendu $Products->update($new_product_id, ['product_sold' => 1]); } // Créer une notification $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' ]); } } 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' ]); } } }