Compare commits
No commits in common. '17803c1acb38e9fd8d87e1aa840f3a2a5158b227' and '0ce9cb608d1a236dc7238b2e096547fc7c624372' have entirely different histories.
17803c1acb
...
0ce9cb608d
@ -1,121 +1,53 @@ |
|||
<?php |
|||
|
|||
namespace Config; |
|||
|
|||
use CodeIgniter\Config\BaseConfig; |
|||
|
|||
class Email extends BaseConfig |
|||
{ |
|||
public string $fromEmail = ''; |
|||
public string $fromName = ''; |
|||
public string $fromEmail = '[email protected]'; |
|||
public string $fromName = 'motorbike'; |
|||
public string $recipients = ''; |
|||
|
|||
/** |
|||
* The "user agent" |
|||
*/ |
|||
public string $userAgent = 'CodeIgniter'; |
|||
|
|||
/** |
|||
* The mail sending protocol: mail, sendmail, smtp |
|||
*/ |
|||
public string $protocol = 'mail'; |
|||
public string $protocol = 'smtp'; |
|||
|
|||
/** |
|||
* The server path to Sendmail. |
|||
*/ |
|||
public string $mailPath = '/usr/sbin/sendmail'; |
|||
|
|||
/** |
|||
* SMTP Server Hostname |
|||
*/ |
|||
public string $SMTPHost = ''; |
|||
|
|||
/** |
|||
* SMTP Username |
|||
*/ |
|||
public string $SMTPUser = ''; |
|||
|
|||
/** |
|||
* SMTP Password |
|||
*/ |
|||
public string $SMTPPass = ''; |
|||
|
|||
/** |
|||
* SMTP Port |
|||
*/ |
|||
public int $SMTPPort = 25; |
|||
|
|||
/** |
|||
* SMTP Timeout (in seconds) |
|||
*/ |
|||
public int $SMTPTimeout = 5; |
|||
|
|||
/** |
|||
* Enable persistent SMTP connections |
|||
*/ |
|||
public string $SMTPHost = 'smtp.gmail.com'; |
|||
|
|||
public string $SMTPUser = '[email protected]'; |
|||
|
|||
public string $SMTPPass = 'loirqovmfuxnasrm'; // Mot de passe d’application (App Password) Gmail |
|||
|
|||
public int $SMTPPort = 587; |
|||
|
|||
public int $SMTPTimeout = 30; |
|||
|
|||
public bool $SMTPKeepAlive = false; |
|||
|
|||
/** |
|||
* SMTP Encryption. |
|||
* |
|||
* @var string '', 'tls' or 'ssl'. 'tls' will issue a STARTTLS command |
|||
* to the server. 'ssl' means implicit SSL. Connection on port |
|||
* 465 should set this to ''. |
|||
*/ |
|||
public string $SMTPCrypto = 'tls'; |
|||
|
|||
/** |
|||
* Enable word-wrap |
|||
*/ |
|||
public bool $wordWrap = true; |
|||
|
|||
/** |
|||
* Character count to wrap at |
|||
*/ |
|||
public int $wrapChars = 76; |
|||
|
|||
/** |
|||
* Type of mail, either 'text' or 'html' |
|||
*/ |
|||
public string $mailType = 'text'; |
|||
public string $mailType = 'html'; |
|||
|
|||
/** |
|||
* Character set (utf-8, iso-8859-1, etc.) |
|||
*/ |
|||
public string $charset = 'UTF-8'; |
|||
|
|||
/** |
|||
* Whether to validate the email address |
|||
*/ |
|||
public bool $validate = false; |
|||
public bool $validate = true; |
|||
|
|||
/** |
|||
* Email Priority. 1 = highest. 5 = lowest. 3 = normal |
|||
*/ |
|||
public int $priority = 3; |
|||
|
|||
/** |
|||
* Newline character. (Use “\r\n” to comply with RFC 822) |
|||
*/ |
|||
public string $CRLF = "\r\n"; |
|||
|
|||
/** |
|||
* Newline character. (Use “\r\n” to comply with RFC 822) |
|||
*/ |
|||
public string $newline = "\r\n"; |
|||
|
|||
/** |
|||
* Enable BCC Batch Mode. |
|||
*/ |
|||
public bool $BCCBatchMode = false; |
|||
|
|||
/** |
|||
* Number of emails in each BCC batch |
|||
*/ |
|||
public int $BCCBatchSize = 200; |
|||
|
|||
/** |
|||
* Enable notify message from server |
|||
*/ |
|||
public bool $DSN = false; |
|||
} |
|||
|
|||
@ -0,0 +1,14 @@ |
|||
<?php |
|||
namespace App\Controllers; |
|||
|
|||
use App\Controllers\BaseController; |
|||
|
|||
class AlertsController extends BaseController |
|||
{ |
|||
public function check() |
|||
{ |
|||
helper('alerts'); |
|||
checkDeadlineAlerts(); |
|||
return "Vérification des alertes effectuée."; |
|||
} |
|||
} |
|||
@ -6,6 +6,7 @@ use App\Models\Company; |
|||
use App\Models\Orders; |
|||
use App\Models\Products; |
|||
use App\Models\Avance; |
|||
use App\Models\User; // Ajout pour récupérer les emails DAF/Directrice |
|||
|
|||
class AvanceController extends AdminController |
|||
{ |
|||
@ -28,354 +29,634 @@ class AvanceController extends AdminController |
|||
return $this->render_template('avances/avance', $data); |
|||
} |
|||
|
|||
public function fetchAvanceData() |
|||
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 .= '<button type="button" class="btn btn-default" onclick="editFunc(' . $value['avance_id'] . ')" title="Modifier">' |
|||
. '<i class="fa fa-pencil"></i></button> '; |
|||
} |
|||
|
|||
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')" title="Supprimer">' |
|||
. '<i class="fa fa-trash"></i></button> '; |
|||
} |
|||
|
|||
if (in_array('viewAvance', $this->permission) && !$isAdmin) { |
|||
$buttons .= '<a href="#" data-order-id="' . $value['avance_id'] . '" class="btn btn-default btn-view" title="Voir">' |
|||
. '<i class="fa fa-eye"></i></a>'; |
|||
} |
|||
|
|||
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->getAllAvanceData(); |
|||
|
|||
$data = $Avance->$methodName(); |
|||
$session = session(); |
|||
$users = $session->get('user'); |
|||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']); |
|||
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']); |
|||
$isCaissier = in_array($users['group_name'], ['Caissier']); |
|||
$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']; |
|||
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); |
|||
|
|||
// Boutons d’action |
|||
$buttons = ''; |
|||
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">' |
|||
. '<i class="fa fa-pencil"></i></button>'; |
|||
} |
|||
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>'; |
|||
$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; |
|||
} |
|||
if (in_array('viewAvance', $this->permission) && !$isAdmin) { |
|||
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>'; |
|||
} |
|||
|
|||
return $this->response->setJSON($result); |
|||
} |
|||
|
|||
public function fetchAvanceData() |
|||
{ |
|||
return $this->fetchAvanceDataGeneric('getAllAvanceData'); |
|||
} |
|||
|
|||
public function fetchAvanceBecameOrder() |
|||
{ |
|||
return $this->fetchAvanceDataGeneric('getAllAvanceData1'); |
|||
} |
|||
|
|||
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']); |
|||
} |
|||
} |
|||
} |
|||
if ($isAdmin) { |
|||
$row = [ |
|||
$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, |
|||
]; |
|||
// dd($row);die; |
|||
$result['data'][] = $row; |
|||
|
|||
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('[email protected]', '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; |
|||
} |
|||
if ($isCommerciale || $isCaissier) { |
|||
$row = [ |
|||
$value['avance_id'], |
|||
number_format((int)$value['avance_amount'], 0, ',', ' '), |
|||
number_format((int)$value['amount_due'], 0, ',', ' '), |
|||
$date_time, |
|||
$buttons, |
|||
]; |
|||
$result['data'][] = $row; |
|||
} 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']; |
|||
} |
|||
} |
|||
|
|||
return $this->response->setJSON($result); |
|||
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 = [ |
|||
'[email protected]', |
|||
'[email protected]' |
|||
]; |
|||
} |
|||
|
|||
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 " |
|||
<html> |
|||
<head> |
|||
<style> |
|||
.container { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; } |
|||
.header { background-color: #f8f9fa; padding: 20px; text-align: center; border-radius: 5px; } |
|||
.alert { background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; margin: 20px 0; border-radius: 5px; } |
|||
.details { background-color: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 5px; } |
|||
.urgent { color: #dc3545; font-weight: bold; } |
|||
.footer { margin-top: 30px; padding: 15px; background-color: #e9ecef; border-radius: 5px; font-size: 12px; } |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class='container'> |
|||
<div class='header'> |
|||
<h2>⚠️ ALERTE DEADLINE AVANCE</h2> |
|||
</div> |
|||
|
|||
<div class='alert'> |
|||
<p><strong " . $urgencyClass . ">Une avance arrive à échéance dans {$daysRemaining} jour(s) !</strong></p> |
|||
</div> |
|||
|
|||
<div class='details'> |
|||
<h3>Détails de l'avance :</h3> |
|||
<ul> |
|||
<li><strong>ID Avance :</strong> #{$avance['avance_id']}</li> |
|||
<li><strong>Type d'avance :</strong> {$typeAvance}</li> |
|||
<li><strong>Client :</strong> {$avance['customer_name']}</li> |
|||
<li><strong>Téléphone :</strong> {$avance['customer_phone']}</li> |
|||
<li><strong>Adresse :</strong> {$avance['customer_address']}</li> |
|||
<li><strong>CIN :</strong> {$avance['customer_cin']}</li> |
|||
<li><strong>Produit :</strong> {$productName}</li> |
|||
<li><strong>Montant restant dû :</strong> <span class='urgent'>{$amountDueFormatted}</span></li> |
|||
<li><strong>Date avance :</strong> {$avanceDateFormatted}</li> |
|||
<li><strong>Date limite :</strong> <span class='urgent'>{$deadlineFormatted}</span></li> |
|||
</ul> |
|||
</div> |
|||
|
|||
<div class='alert'> |
|||
<p><strong>Action requise :</strong></p> |
|||
<p>Veuillez contacter le client pour régulariser le paiement avant l'échéance ou prendre les mesures appropriées.</p> |
|||
</div> |
|||
|
|||
<div class='footer'> |
|||
<p>Cet email a été généré automatiquement par le système de gestion des avances.</p> |
|||
<p>Date d'envoi : " . date('d/m/Y à H:i') . "</p> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
|||
"; |
|||
} |
|||
|
|||
/** |
|||
* 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'); |
|||
$data['page_title'] = $this->pageTitle; |
|||
$this->verifyRole('createAvance'); |
|||
|
|||
$Avance = new Avance(); |
|||
$Products = new Products(); |
|||
$Notification = New NotificationController(); |
|||
if ($this->request->getMethod() !== 'post') { |
|||
return $this->response->setJSON([ |
|||
'success' => false, |
|||
'messages' => 'Méthode non autorisée' |
|||
]); |
|||
} |
|||
|
|||
if ($this->request->getMethod() === 'post') { |
|||
try { |
|||
$session = session(); |
|||
$users = $session->get('user'); |
|||
$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, |
|||
'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' => date('Y-m-d'), |
|||
'user_id' => $users['id'], |
|||
'avance_date' => $avance_date, |
|||
'deadline' => $deadline, |
|||
'user_id' => $users['id'], |
|||
'store_id' => $users['store_id'], |
|||
'product_id' => $this->request->getPost('id_product'), |
|||
'gross_amount' => (float)$this->request->getPost('gross_amount'), |
|||
'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' => (float)0, |
|||
'active' => 1, |
|||
'is_order' => 0, |
|||
'active' => 1, |
|||
]; |
|||
|
|||
if($avance_id = $Avance->createAvance($data)){ |
|||
$product = new Products(); |
|||
$product->update((int)$this->request->getPost('id_product'), ['product_sold' => 1]); |
|||
$Notification->createNotification('Une avance a été créé', "Conseil",(int)$users['store_id'], 'avances'); |
|||
return $this->response->setJSON([ |
|||
'success' => true, |
|||
'messages' => 'Avance créé avec succès !' |
|||
]); |
|||
} |
|||
else{ |
|||
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 est survenue lors de la création d\une avance !' |
|||
'messages' => 'Une erreur interne est survenue' |
|||
]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function updateAvance(int $id) |
|||
{ |
|||
$this->verifyRole('updateAvance'); |
|||
$data['page_title'] = $this->pageTitle; |
|||
|
|||
$Products = new Products(); |
|||
$Avance = new Avance(); |
|||
$session = session(); |
|||
$users = $session->get('user'); |
|||
if ($this->request->getMethod() === 'post') { |
|||
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(); |
|||
$Orders = new Orders(); |
|||
$Company = new Company(); |
|||
$Notification = new NotificationController(); |
|||
|
|||
$validation = \Config\Services::validation(); |
|||
$validation->setRules([ |
|||
'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()) |
|||
]); |
|||
} |
|||
|
|||
// Récupérer la date de création actuelle de l'avance pour recalculer deadline |
|||
$currentAvance = $Avance->find($id); |
|||
if (!$currentAvance) { |
|||
return $this->response->setJSON([ |
|||
'success' => false, |
|||
'messages' => 'Avance introuvable.' |
|||
]); |
|||
} |
|||
$avance_date = $currentAvance['avance_date']; |
|||
|
|||
// Calcul automatique deadline selon le type d'avance |
|||
$type_avance = $this->request->getPost('type_avance_edit'); |
|||
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; |
|||
} |
|||
|
|||
$data = [ |
|||
'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'), |
|||
'gross_amout' => $this->request->getPost('gros_amount_edit'), |
|||
'avance_amount' => (int)$this->request->getPost('avance_amount_edit'), |
|||
'amount_due' => (int)$this->request->getPost('amount_due_edit'), |
|||
'product_id' => $this->request->getPost('id_product_edit'), |
|||
'type_avance' => $type_avance, |
|||
'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'), |
|||
'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'), |
|||
'product_id' => (int)$this->request->getPost('id_product_edit'), |
|||
'deadline' => $deadline, |
|||
]; |
|||
$bill_no = 'BILPR-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4)); |
|||
$Company = new Company(); |
|||
$company = $Company->getCompanyData(1); |
|||
$company['vat_charge_value'] > 0; |
|||
$service_charge_rate = $company['service_charge_value']; |
|||
$vat_charge_rate = $company['vat_charge_value']; |
|||
$gross_amount = $this->request->getPost('gross_amount_edit'); |
|||
$vat_charge = ($gross_amount / 100) * $vat_charge_rate; |
|||
$amount_due = (int)$this->request->getPost('amount_due_edit'); |
|||
$product_id = (array)$this->request->getPost('id_product_edit'); |
|||
|
|||
$amount_due = $data['amount_due']; |
|||
|
|||
if ($amount_due <= 0) { |
|||
$Orders = new Orders(); |
|||
|
|||
$data = [ |
|||
'bill_no' => $bill_no, |
|||
'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'), |
|||
'gross_amout' => $gross_amount, |
|||
'net_amount' => $gross_amount, |
|||
'date_time' => date('Y-m-d H:i:s'), |
|||
$bill_no = 'BILPR-' . strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 4)); |
|||
$company = $Company->getCompanyData(1); |
|||
|
|||
$service_charge_rate = $company['service_charge_value'] ?? 0; |
|||
$vat_charge_rate = $company['vat_charge_value'] ?? 0; |
|||
$gross_amount = $data['gross_amount']; |
|||
$vat_charge = ($gross_amount / 100) * $vat_charge_rate; |
|||
|
|||
$order_data = [ |
|||
'bill_no' => $bill_no, |
|||
'customer_name' => $data['customer_name'], |
|||
'customer_address' => $data['customer_address'], |
|||
'customer_phone' => $data['customer_phone'], |
|||
'customer_cin' => $data['customer_cin'], |
|||
'gross_amount' => $gross_amount, |
|||
'net_amount' => $gross_amount, |
|||
'date_time' => date('Y-m-d H:i:s'), |
|||
'service_charge_rate' => $service_charge_rate, |
|||
'vat_charge_rate' => $vat_charge_rate, |
|||
'vat_charge' => $vat_charge, |
|||
'discount' => (int) 0, |
|||
'paid_status' => 1, |
|||
'user_id' => $users['id'], |
|||
'store_id' => $users['store_id'], |
|||
'amount_value' => $gross_amount, |
|||
'rate_value' => $gross_amount, |
|||
'vat_charge_rate' => $vat_charge_rate, |
|||
'vat_charge' => $vat_charge, |
|||
'discount' => 0, |
|||
'paid_status' => 1, |
|||
'user_id' => $users['id'], |
|||
'store_id' => $users['store_id'], |
|||
'amount_value' => $gross_amount, |
|||
'rate_value' => $gross_amount, |
|||
]; |
|||
$data1 = ['is_order' => 1]; |
|||
if($Orders->create($data,$product_id)){ |
|||
$Avance->updateAvance($id,$data1); |
|||
$Notification = New NotificationController(); |
|||
$Notification->createNotification('Une commande a été créé', "Conseil",(int)$users['store_id'], 'orders'); |
|||
|
|||
$product_id = [$data['product_id']]; |
|||
|
|||
if ($Orders->create($order_data, $product_id)) { |
|||
$Avance->updateAvance($id, ['is_order' => 1]); |
|||
$Notification->createNotification( |
|||
'Une avance a été convertie en commande', |
|||
"Conseil", |
|||
(int)$users['store_id'], |
|||
'orders' |
|||
); |
|||
|
|||
return $this->response->setJSON([ |
|||
'success' => true, |
|||
'messages' => 'success. Avance convertie en commande avec succès.' |
|||
'messages' => 'Avance convertie en commande avec succès.' |
|||
]); |
|||
} |
|||
else{ |
|||
} else { |
|||
return $this->response->setJSON([ |
|||
'success' => false, |
|||
'messages' => 'Erreur lors de la convertion de l\'avance' |
|||
'messages' => 'Erreur lors de la conversion de l\'avance en commande' |
|||
]); |
|||
} |
|||
} |
|||
else{ |
|||
} else { |
|||
if ($Avance->updateAvance($id, $data)) { |
|||
return $this->response->setJSON([ |
|||
'success' => true, |
|||
'messages' => 'success', 'Avance mise à jour avec succès.' |
|||
|
|||
'messages' => 'Avance mise à jour avec succès.' |
|||
]); |
|||
} else { |
|||
return $this->response->setJSON([ |
|||
'success' => true, |
|||
'messages' => 'Errors', 'Une erreur est survenue lors de la mise à jour.' |
|||
'success' => false, |
|||
'messages' => 'Erreur lors de la mise à jour de l\'avance.' |
|||
]); |
|||
} |
|||
} |
|||
} catch (\Exception $e) { |
|||
log_message('error', "Erreur mise à jour avance: " . $e->getMessage()); |
|||
return $this->response->setJSON([ |
|||
'success' => false, |
|||
'messages' => 'Une erreur interne est survenue' |
|||
]); |
|||
} |
|||
} |
|||
|
|||
public function removeAvance() |
|||
{ |
|||
$this->verifyRole('deleteAvance'); |
|||
$avance_id = $this->request->getPost('avance_id'); |
|||
$product_id = $this->request->getPost('product_id'); |
|||
$response = []; |
|||
|
|||
$Avance = new Avance(); |
|||
if ($Avance->removeAvance($avance_id)) { |
|||
$product = new Products(); |
|||
$product->update($product_id, ['product_sold' => 0]); |
|||
$response['success'] = true; |
|||
$response['messages'] = "Avance supprimée avec succès. Ce produit peut désormais être réservé à nouveau."; |
|||
} else { |
|||
$response['success'] = false; |
|||
$response['messages'] = "une erreur est survenue lors de la suppression d'une avance"; |
|||
} |
|||
return $this->response->setJSON($response); |
|||
} |
|||
|
|||
public function fetchSingleAvance($avance_id) |
|||
{ |
|||
$this->verifyRole('updateAvance'); |
|||
|
|||
try { |
|||
$avanceModel = new Avance(); |
|||
|
|||
$data = $avanceModel->fetchSingleAvance($avance_id); |
|||
|
|||
return $this->response->setJSON($data); |
|||
} |
|||
catch (\Throwable $th) { |
|||
log_message('error', "Erreur lors de la récupération d'une avance: " . $th->getMessage()); |
|||
|
|||
return $this->response |
|||
->setStatusCode(500) |
|||
->setJSON(['error' => 'Une erreur interne est survenue. Lors de la création d\'une avance']); |
|||
} |
|||
} |
|||
try { |
|||
$avance_id = $this->request->getPost('avance_id'); |
|||
$product_id = $this->request->getPost('product_id'); |
|||
|
|||
public function fetchAvanceBecameOrder() |
|||
{ |
|||
helper(['url', 'form']); |
|||
$Avance = new Avance(); |
|||
$product = new Products(); |
|||
$result = ['data' => []]; |
|||
$data = $Avance->getAllAvanceData1(); |
|||
$session = session(); |
|||
$users = $session->get('user'); |
|||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']); |
|||
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']); |
|||
$isCaissier = in_array($users['group_name'], ['Caissier']); |
|||
foreach ($data as $key => $value) { |
|||
$isOwner = $users['id'] === $value['user_id']; |
|||
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); |
|||
|
|||
// Boutons d’action |
|||
$buttons = ''; |
|||
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">' |
|||
. '<i class="fa fa-pencil"></i></button>'; |
|||
} |
|||
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>'; |
|||
if (!$avance_id || !$product_id) { |
|||
return $this->response->setJSON([ |
|||
'success' => false, |
|||
'messages' => 'Données manquantes pour la suppression' |
|||
]); |
|||
} |
|||
if (in_array('viewAvance', $this->permission) && !$isAdmin) { |
|||
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>'; |
|||
} |
|||
if ($isAdmin) { |
|||
$row = [ |
|||
$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, |
|||
]; |
|||
// dd($row);die; |
|||
$result['data'][] = $row; |
|||
} |
|||
if ($isCommerciale || $isCaissier) { |
|||
$row = [ |
|||
$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, |
|||
]; |
|||
$result['data'][] = $row; |
|||
|
|||
$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' |
|||
]); |
|||
} |
|||
|
|||
return $this->response->setJSON($result); |
|||
} |
|||
|
|||
public function fetcheExpiredAvance() |
|||
public function fetchSingleAvance($avance_id) |
|||
{ |
|||
helper(['url', 'form']); |
|||
$Avance = new Avance(); |
|||
$product = new Products(); |
|||
$result = ['data' => []]; |
|||
$data = $Avance->getAllAvanceData2(); |
|||
$session = session(); |
|||
$users = $session->get('user'); |
|||
$isAdmin = in_array($users['group_name'], ['Conseil', 'Direction']); |
|||
$isCommerciale = in_array($users['group_name'], ['COMMERCIALE']); |
|||
$isCaissier = in_array($users['group_name'], ['Caissier']); |
|||
foreach ($data as $key => $value) { |
|||
$isOwner = $users['id'] === $value['user_id']; |
|||
$date_time = date('d-m-Y h:i a', strtotime($value['avance_date'])); |
|||
|
|||
// Boutons d’action |
|||
$buttons = ''; |
|||
if (in_array('updateAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-default" onclick="editFunc('. $value['avance_id'] .')">' |
|||
. '<i class="fa fa-pencil"></i></button>'; |
|||
} |
|||
if (in_array('deleteAvance', $this->permission) && ($isAdmin || $isOwner)) { |
|||
$buttons .= '<button type="button" class="btn btn-danger" onclick="removeFunc(' . $value['avance_id'] . ',' . $value['product_id'] . ')"><i class="fa fa-trash"></i></button>'; |
|||
} |
|||
if (in_array('viewAvance', $this->permission) && !$isAdmin) { |
|||
$buttons .= ' <a href="#" data-order-id="'.$value['id'].'" class="btn btn-default btn-view" title="Voir"><i class="fa fa-eye"></i></a>'; |
|||
} |
|||
if ($isAdmin) { |
|||
$row = [ |
|||
$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, |
|||
]; |
|||
// dd($row);die; |
|||
$result['data'][] = $row; |
|||
$this->verifyRole('updateAvance'); |
|||
|
|||
try { |
|||
if (!$avance_id || !is_numeric($avance_id)) { |
|||
return $this->response->setStatusCode(400)->setJSON([ |
|||
'error' => 'ID d\'avance invalide' |
|||
]); |
|||
} |
|||
if ($isCommerciale || $isCaissier) { |
|||
$row = [ |
|||
$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, |
|||
]; |
|||
$result['data'][] = $row; |
|||
|
|||
$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($result); |
|||
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' |
|||
]); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,58 +1,22 @@ |
|||
<?php |
|||
|
|||
namespace App\Controllers; |
|||
|
|||
use CodeIgniter\Controller; |
|||
use CodeIgniter\HTTP\CLIRequest; |
|||
use CodeIgniter\HTTP\IncomingRequest; |
|||
use CodeIgniter\HTTP\RequestInterface; |
|||
use CodeIgniter\HTTP\ResponseInterface; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/** |
|||
* Class BaseController |
|||
* |
|||
* BaseController provides a convenient place for loading components |
|||
* and performing functions that are needed by all your controllers. |
|||
* Extend this class in any new controllers: |
|||
* class Home extends BaseController |
|||
* |
|||
* For security be sure to declare any new methods as protected or private. |
|||
*/ |
|||
abstract class BaseController extends Controller |
|||
class BaseController extends Controller |
|||
{ |
|||
/** |
|||
* Instance of the main Request object. |
|||
* |
|||
* @var CLIRequest|IncomingRequest |
|||
*/ |
|||
protected $request; |
|||
|
|||
/** |
|||
* An array of helpers to be loaded automatically upon |
|||
* class instantiation. These helpers will be available |
|||
* to all other controllers that extend BaseController. |
|||
* |
|||
* @var list<string> |
|||
*/ |
|||
protected $helpers = []; |
|||
|
|||
/** |
|||
* Be sure to declare properties for any property fetch you initialized. |
|||
* The creation of dynamic property is deprecated in PHP 8.2. |
|||
*/ |
|||
// protected $session; |
|||
|
|||
/** |
|||
* @return void |
|||
*/ |
|||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) |
|||
public function initController(\CodeIgniter\HTTP\RequestInterface $request, |
|||
\CodeIgniter\HTTP\ResponseInterface $response, |
|||
\Psr\Log\LoggerInterface $logger) |
|||
{ |
|||
// Do Not Edit This Line |
|||
parent::initController($request, $response, $logger); |
|||
|
|||
// Preload any models, libraries, etc, here. |
|||
helper('alerts'); |
|||
|
|||
// E.g.: $this->session = \Config\Services::session(); |
|||
if (function_exists('checkDeadlineAlerts')) { |
|||
checkDeadlineAlerts(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
<?php |
|||
|
|||
namespace App\Controllers; |
|||
|
|||
use CodeIgniter\Controller; |
|||
|
|||
class TestDeadline extends Controller |
|||
{ |
|||
public function index() |
|||
{ |
|||
// Charger le helper qui contient ta fonction |
|||
helper('alerts'); // si ton fichier s'appelle alerts_helper.php |
|||
|
|||
// 🔹 Supprimer le cache 24h pour forcer l'exécution |
|||
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt'; |
|||
if (file_exists($cacheFile)) { |
|||
unlink($cacheFile); |
|||
} |
|||
|
|||
// Lancer la vérification |
|||
checkDeadlineAlerts(); |
|||
|
|||
echo "✅ Test de l'envoi d'alertes terminé."; |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="fr"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<link rel="stylesheet" href="' . base_url('assets/bower_components/bootstrap/dist/css/bootstrap.min.css') . '"> |
|||
<style> |
|||
body { font-size: 14px; font-family: Arial, sans-serif; } |
|||
.invoice-container { |
|||
max-width: 350px; /* Réduire la largeur du cadre */ |
|||
margin: 20px auto; |
|||
padding: 20px; |
|||
border: 2px solid #007bff; /* Bordure plus visible */ |
|||
border-radius: 10px; |
|||
background: #f0f8ff; /* Couleur de fond plus douce */ |
|||
} |
|||
.invoice-header { |
|||
background: #007bff; |
|||
color: white; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
padding: 10px; |
|||
border-radius: 10px 10px 0 0; |
|||
} |
|||
.invoice-footer { |
|||
background: #343a40; |
|||
color: white; |
|||
text-align: center; |
|||
font-size: 14px; |
|||
padding: 10px; |
|||
border-radius: 0 0 10px 10px; |
|||
margin-top: 12px; |
|||
} |
|||
table { width: 100%; border-collapse: collapse; } |
|||
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } |
|||
th { background: #e9ecef; } |
|||
p, strong { color: #333; } |
|||
</style> |
|||
</head> |
|||
<body onload="window.print();"> |
|||
<div class="invoice-container"> |
|||
<div class="invoice-header"> |
|||
' . esc($company_info['company_name']) . ' |
|||
</div> |
|||
<p><strong>Facture ID:</strong> ' . esc($order_data['bill_no']) . '</p> |
|||
<p><strong>Nom:</strong> ' . esc($order_data['customer_name']) . '</p> |
|||
<p><strong>Adresse:</strong> ' . esc($order_data['customer_address']) . '</p> |
|||
<p><strong>Téléphone:</strong> ' . esc($order_data['customer_phone']) . '</p> |
|||
|
|||
<div style="display: flex;align-items: center;justify-content: space-around;margin-bottom: 3%;"> |
|||
<div> |
|||
<p>Signature du client</p> |
|||
</div> |
|||
<div> |
|||
<p>Signature du commercial</p> |
|||
</div> |
|||
</div> |
|||
<table class="table table-bordered"> |
|||
<thead> |
|||
<tr> |
|||
<th>Produit</th> |
|||
<th>Qté</th> |
|||
<th>Prix</th> |
|||
<th>Total</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody>'; |
|||
|
|||
foreach ($orders_items as $item) { |
|||
$product_data = $Products->getProductData($item['product_id']); |
|||
$html .= '<tr> |
|||
<td>' . esc($product_data['name']) . '</td> |
|||
<td>' . esc($item['qty']) . '</td> |
|||
<td>' . number_format((float)$item['rate'], 2, '.', ' ') . '</td> |
|||
<td>' . number_format((float)$item['amount'], 2, '.', ' ') . '</td> |
|||
</tr>'; |
|||
} |
|||
|
|||
$html .= ' </tbody> |
|||
</table> |
|||
<table class="table"> |
|||
<tr> |
|||
<th>Total:</th> |
|||
<td>' . number_format((float)$order_data['gross_amount'], 2, '.', ' ') . '</td> |
|||
</tr>'; |
|||
|
|||
if (!empty($order_data['service_charge']) && (float)$order_data['service_charge'] > 0) { |
|||
$html .= '<tr> |
|||
<th>Frais de service:</th> |
|||
<td>' . number_format((float)$order_data['service_charge'], 2, '.', ' ') . '</td> |
|||
</tr>'; |
|||
} |
|||
|
|||
if (!empty($order_data['vat_charge']) && (float)$order_data['vat_charge'] > 0) { |
|||
$html .= '<tr> |
|||
<th>TVA:</th> |
|||
<td>' . number_format((float)$order_data['vat_charge'], 2, '.', ' ') . '</td> |
|||
</tr>'; |
|||
} |
|||
|
|||
$html .= '<tr> |
|||
<th>Réduction:</th> |
|||
<td>' . number_format((float)$order_data['discount'], 2, '.', ' ') . '</td> |
|||
</tr> |
|||
<tr> |
|||
<th>Total à payer:</th> |
|||
<td><strong>' . number_format((float)$order_data['net_amount'], 2, '.', ' ') . '</strong></td> |
|||
</tr> |
|||
<tr> |
|||
<th>Statut:</th> |
|||
<td>' . $paid_status . '</td> |
|||
</tr>'; |
|||
|
|||
// Vérification et ajout des informations de paiement |
|||
if (!empty($order_data['order_payment_mode'])) { |
|||
$html .= '<tr> |
|||
<th>Mode de paiement:</th> |
|||
<td><strong>' . esc($order_data['order_payment_mode']) . '</strong></td> |
|||
</tr>'; |
|||
} |
|||
|
|||
if (!empty($order_data['tranche_1'])) { |
|||
$html .= '<tr> |
|||
<th>Tranche 1:</th> |
|||
<td><strong>' . number_format((float)$order_data['tranche_1'], 2, '.', ' ') . '</strong></td> |
|||
</tr>'; |
|||
} |
|||
|
|||
if (!empty($order_data['tranche_2'])) { |
|||
$html .= '<tr> |
|||
<th>Tranche 2:</th> |
|||
<td><strong>' . number_format((float)$order_data['tranche_2'], 2, '.', ' ') . '</strong></td> |
|||
</tr>'; |
|||
} |
|||
|
|||
$html .= '</table> |
|||
|
|||
<div class="invoice-footer"> |
|||
Merci pour votre achat !<br> |
|||
<strong>' . esc($company_info['company_name']) . '</strong> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,176 @@ |
|||
<?php |
|||
|
|||
use App\Models\Users; |
|||
use App\Models\Avance; |
|||
use App\Models\AlertMail; |
|||
|
|||
function checkDeadlineAlerts() |
|||
{ |
|||
log_message('info', "=== DÉBUT checkDeadlineAlerts ==="); |
|||
|
|||
$cacheFile = WRITEPATH . 'cache/check_deadline_last_run.txt'; |
|||
|
|||
// On enlève la vérification de 24h pour s'assurer que le script tourne quotidiennement |
|||
file_put_contents($cacheFile, time()); |
|||
|
|||
$avanceModel = new Avance(); |
|||
$alertMailModel = new AlertMail(); |
|||
$usersModel = new Users(); |
|||
|
|||
$today = date('Y-m-d'); |
|||
log_message('info', "Date du jour: {$today}"); |
|||
|
|||
// Modification pour vérifier les avances dans 0-3 jours |
|||
$avances = $avanceModel |
|||
->where('DATE(deadline) >=', $today) // Inclut le jour même |
|||
->where('DATE(deadline) <=', date('Y-m-d', strtotime('+3 days'))) |
|||
->where('active', 1) |
|||
->findAll(); |
|||
|
|||
log_message('info', "Nombre d'avances trouvées (0-3 jours): " . count($avances)); |
|||
|
|||
$users = $usersModel->select('users.email, users.firstname, users.lastname') |
|||
->join('user_group', 'user_group.user_id = users.id') |
|||
->join('groups', 'groups.id = user_group.group_id') |
|||
->where('groups.group_name', 'DAF') |
|||
->findAll(); |
|||
|
|||
log_message('info', "Utilisateurs DAF trouvés: " . json_encode($users)); |
|||
|
|||
$emails = array_column($users, 'email'); |
|||
log_message('info', "Emails extraits: " . json_encode($emails)); |
|||
|
|||
if (empty($emails)) { |
|||
log_message('error', "Aucun email DAF trouvé"); |
|||
|
|||
$db = \Config\Database::connect(); |
|||
$allGroups = $db->query("SELECT DISTINCT group_name FROM groups")->getResult(); |
|||
log_message('info', "Groupes disponibles: " . json_encode($allGroups)); |
|||
|
|||
return; |
|||
} |
|||
|
|||
foreach ($avances as $avance) { |
|||
$deadline = date('Y-m-d', strtotime($avance['deadline'])); |
|||
$daysLeft = (int) ceil((strtotime($deadline) - strtotime($today)) / 86400); |
|||
|
|||
log_message('info', "Avance ID: {$avance['avance_id']}, Deadline: {$deadline}, Jours restants: {$daysLeft}"); |
|||
|
|||
// Modification des types d'alerte pour 0, 1, 2, 3 jours |
|||
$alertType = match($daysLeft) { |
|||
3 => 'deadline_3_days', |
|||
2 => 'deadline_2_days', |
|||
1 => 'deadline_1_day', |
|||
0 => 'deadline_today', |
|||
default => null, |
|||
}; |
|||
|
|||
if ($alertType === null) { |
|||
log_message('info', "Pas d'alerte nécessaire pour {$daysLeft} jours restants"); |
|||
continue; |
|||
} |
|||
|
|||
log_message('info', "Type d'alerte: {$alertType}"); |
|||
|
|||
// Vérification si l'alerte a déjà été envoyée |
|||
$alreadySent = $alertMailModel |
|||
->where('avance_id', $avance['avance_id']) |
|||
->where('alert_type', $alertType) |
|||
->first(); |
|||
|
|||
if ($alreadySent) { |
|||
log_message('info', "Alerte déjà envoyée pour avance_id={$avance['avance_id']} type={$alertType}"); |
|||
continue; |
|||
} |
|||
|
|||
// Message modifié pour inclure le cas du jour même |
|||
$urgencyText = $daysLeft === 0 ? "ÉCHÉANCE AUJOURD'HUI" : "{$daysLeft} jour(s) restant(s)"; |
|||
$message = " |
|||
<h3>⚠️ URGENT : Avance approchant de la deadline</h3> |
|||
<p><strong>ID Avance :</strong> {$avance['avance_id']}</p> |
|||
<p><strong>Client :</strong> {$avance['customer_name']}</p> |
|||
<p><strong>Montant avance :</strong> " . number_format($avance['avance_amount'], 0, ',', ' ') . " Ar</p> |
|||
<p><strong>Montant dû :</strong> " . number_format($avance['amount_due'], 0, ',', ' ') . " Ar</p> |
|||
<p><strong>Deadline :</strong> {$deadline}</p> |
|||
<p><strong>Statut :</strong> <span style='color: red; font-weight: bold;'>{$urgencyText}</span></p> |
|||
<p><strong>Téléphone client :</strong> {$avance['customer_phone']}</p> |
|||
<p><strong>Adresse client :</strong> {$avance['customer_address']}</p> |
|||
<hr> |
|||
<p><em>Cette avance " . ($daysLeft === 0 ? "arrive à échéance aujourd'hui" : "arrivera à échéance dans {$daysLeft} jour(s)") . ". Action requise immédiatement.</em></p> |
|||
"; |
|||
|
|||
$emailsSent = 0; |
|||
foreach ($emails as $to) { |
|||
log_message('info', "Tentative d'envoi email à: {$to}"); |
|||
|
|||
$subject = $daysLeft === 0 |
|||
? "⚠️ AVANCE URGENTE - ÉCHÉANCE AUJOURD'HUI" |
|||
: "⚠️ AVANCE URGENTE - {$daysLeft} jour(s) restant(s)"; |
|||
|
|||
if (sendEmailInBackground($to, $subject, $message)) { |
|||
$emailsSent++; |
|||
log_message('info', "Email envoyé avec succès à: {$to}"); |
|||
} else { |
|||
log_message('error', "Échec envoi email à: {$to}"); |
|||
} |
|||
} |
|||
|
|||
if ($emailsSent > 0) { |
|||
log_message('info', "Insertion alerte pour avance_id={$avance['avance_id']} avec type {$alertType}"); |
|||
$alertMailModel->insert([ |
|||
'avance_id' => $avance['avance_id'], |
|||
'alert_type' => $alertType, |
|||
'sent_date' => date('Y-m-d H:i:s'), |
|||
'status' => 'sent', |
|||
'created_at' => date('Y-m-d H:i:s'), |
|||
]); |
|||
} else { |
|||
log_message('error', "Aucun email envoyé pour avance_id={$avance['avance_id']} avec type {$alertType}"); |
|||
} |
|||
} |
|||
|
|||
log_message('info', "=== FIN checkDeadlineAlerts ==="); |
|||
} |
|||
|
|||
function sendEmailInBackground($to, $subject, $message) |
|||
{ |
|||
try { |
|||
log_message('info', "Préparation envoi email à: {$to}"); |
|||
|
|||
$email = \Config\Services::email(); |
|||
|
|||
$config = [ |
|||
'protocol' => 'smtp', |
|||
'SMTPHost' => 'smtp.gmail.com', |
|||
'SMTPUser' => '[email protected]', |
|||
'SMTPPass' => 'loirqovmfuxnasrm', |
|||
'SMTPPort' => 587, |
|||
'SMTPCrypto' => 'tls', |
|||
'mailType' => 'html', |
|||
'charset' => 'utf-8', |
|||
'newline' => "\r\n" |
|||
]; |
|||
|
|||
$email->initialize($config); |
|||
|
|||
$email->setFrom('[email protected]', 'Système Motorbike - Alertes Avances'); |
|||
$email->setTo($to); |
|||
$email->setSubject($subject); |
|||
$email->setMessage($message); |
|||
|
|||
log_message('info', "Configuration email terminée, tentative d'envoi..."); |
|||
|
|||
if (!$email->send()) { |
|||
$debugInfo = $email->printDebugger(['headers']); |
|||
log_message('error', "Erreur email à {$to}: " . print_r($debugInfo, true)); |
|||
return false; |
|||
} |
|||
|
|||
log_message('info', "Email envoyé avec succès à: {$to}"); |
|||
return true; |
|||
|
|||
} catch (\Exception $e) { |
|||
log_message('error', "Exception email à {$to}: " . $e->getMessage()); |
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use CodeIgniter\Model; |
|||
|
|||
class AlertMail extends Model |
|||
{ |
|||
protected $table = 'email_alerts'; |
|||
protected $primaryKey = 'id'; |
|||
|
|||
protected $allowedFields = [ |
|||
'avance_id', |
|||
'alert_type', |
|||
'sent_date', |
|||
'status', |
|||
'created_at', |
|||
]; |
|||
|
|||
// Pas de fonction checkDeadlineAlerts ici ! |
|||
} |
|||
@ -1 +0,0 @@ |
|||
icon |
|||
@ -0,0 +1 @@ |
|||
icon |
|||
@ -1 +0,0 @@ |
|||
icon |
|||
@ -0,0 +1 @@ |
|||
icon |
|||
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 16 B After Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 0 B |
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 0 B |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 848 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 657 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 196 KiB |
@ -0,0 +1 @@ |
|||
1755601005 |
|||