MERGE_UAT_BUG_FIXES
This commit is contained in:
commit
e548add25e
@ -4011,204 +4011,10 @@ class MasterController extends AdminController
|
||||
*/
|
||||
public function cronCdLowBalanceAlert()
|
||||
{
|
||||
log_message('error', 'cronCdLowBalanceAlert: started');
|
||||
$response = \App\Helpers\CdLowBalanceAlertHelper::runCron();
|
||||
$statusCode = ($response['status'] ?? false) ? 200 : 500;
|
||||
|
||||
try {
|
||||
$cdMasters = $this->CDMasterModel->getCdMastersWithLowAlertThreshold();
|
||||
log_message('error', 'cronCdLowBalanceAlert: CD masters with alert threshold count = ' . count($cdMasters));
|
||||
|
||||
$lowBalanceAccounts = [];
|
||||
|
||||
foreach ($cdMasters as $cdMaster) {
|
||||
$cdAcPk = (int) ($cdMaster['id'] ?? 0);
|
||||
$threshold = (float) ($cdMaster['cd_balance_low_alert_amount'] ?? 0);
|
||||
|
||||
if ($cdAcPk <= 0 || $threshold <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lastDeposit = $this->clientDepositModel
|
||||
->select('balance')
|
||||
->where('cd_ac_pk', $cdAcPk)
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
|
||||
if (! empty($lastDeposit)) {
|
||||
$currentBalance = (float) ($lastDeposit['balance'] ?? 0);
|
||||
} else {
|
||||
$currentBalance = (float) ($cdMaster['opening_bal'] ?? 0);
|
||||
}
|
||||
|
||||
if ($currentBalance >= $threshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowBalanceAccounts[] = array_merge($cdMaster, [
|
||||
'current_balance' => $currentBalance,
|
||||
'alert_amount' => $threshold,
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($lowBalanceAccounts)) {
|
||||
$response = [
|
||||
'status' => true,
|
||||
'message' => 'No CD accounts below the configured low-balance alert amount.',
|
||||
'low_balance_count' => 0,
|
||||
'account_manager_mails_sent' => 0,
|
||||
'data' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
return $this->respond($response, 200);
|
||||
}
|
||||
|
||||
$accountManagerMailsSent = 0;
|
||||
$mailErrors = [];
|
||||
$data = [];
|
||||
|
||||
foreach ($lowBalanceAccounts as $account) {
|
||||
$result = $this->sendCdLowBalanceAccountManagerAlert($account);
|
||||
$accountManagerMailsSent += (int) ($result['sent'] ?? 0);
|
||||
if (! empty($result['errors'])) {
|
||||
$mailErrors = array_merge($mailErrors, $result['errors']);
|
||||
}
|
||||
if (! empty($result['data'])) {
|
||||
$data = array_merge($data, $result['data']);
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => true,
|
||||
'message' => 'CD low balance alert cron completed.',
|
||||
'low_balance_count' => count($lowBalanceAccounts),
|
||||
'account_manager_mails_sent' => $accountManagerMailsSent,
|
||||
'data' => $data,
|
||||
'errors' => $mailErrors,
|
||||
];
|
||||
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
return $this->respond($response, 200);
|
||||
} catch (\Throwable $e) {
|
||||
$response = [
|
||||
'status' => false,
|
||||
'message' => 'CD low balance alert cron failed.',
|
||||
'low_balance_count' => 0,
|
||||
'account_manager_mails_sent' => 0,
|
||||
'data' => [],
|
||||
'errors' => [$e->getMessage()],
|
||||
];
|
||||
log_message('error', 'cronCdLowBalanceAlert Exception: ' . $e->getMessage());
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
return $this->respond($response, 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendCdLowBalanceAccountManagerAlert(array $account): array
|
||||
{
|
||||
$clientId = (int) ($account['client_id'] ?? 0);
|
||||
$cdAcPk = (int) ($account['id'] ?? 0);
|
||||
$cdAcNo = (string) ($account['cd_ac_no'] ?? '');
|
||||
|
||||
if ($clientId <= 0) {
|
||||
log_message('error', 'cronCdLowBalanceAlert ACM mail: skipped, invalid client_id for cd_ac_pk=' . $cdAcPk);
|
||||
return ['sent' => 0, 'errors' => [], 'data' => []];
|
||||
}
|
||||
|
||||
$recipients = $this->getAccountManagerRecipients($clientId);
|
||||
if (empty($recipients)) {
|
||||
$error = "Client {$clientId}: No account manager found.";
|
||||
log_message('error', 'cronCdLowBalanceAlert ACM mail: ' . $error);
|
||||
return ['sent' => 0, 'errors' => [$error], 'data' => []];
|
||||
}
|
||||
|
||||
$sent = 0;
|
||||
$errors = [];
|
||||
$data = [];
|
||||
$subject = 'CD Low Balance Alert - ' . ($account['client_name'] ?? 'Client');
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$acmEmail = (string) ($recipient['email'] ?? '');
|
||||
$acmName = (string) ($recipient['name'] ?? 'Account Manager');
|
||||
$message = $this->buildCdLowBalanceAccountManagerMailContent($account, $acmName);
|
||||
|
||||
$mailResponse = MailHelper::send_email([
|
||||
'mail' => $acmEmail,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'common' => [
|
||||
'mail_type' => 'account_manager_cd_low_balance_alert',
|
||||
'client_id' => $clientId,
|
||||
],
|
||||
]);
|
||||
|
||||
$decoded = is_string($mailResponse) ? json_decode($mailResponse, true) : $mailResponse;
|
||||
$mailStatus = is_array($decoded) && ($decoded['status'] ?? '') === 'success' ? 'success' : 'failed';
|
||||
|
||||
$data[] = [
|
||||
'client_id' => $clientId,
|
||||
'cd_ac_pk' => $cdAcPk,
|
||||
'cd_ac_no' => $cdAcNo,
|
||||
'acm_name' => $acmName,
|
||||
'acm_email' => $acmEmail,
|
||||
'mail_status' => $mailStatus,
|
||||
];
|
||||
|
||||
if ($mailStatus === 'success') {
|
||||
$sent++;
|
||||
} else {
|
||||
$errors[] = "Client {$clientId}: Failed to send alert to {$acmEmail}.";
|
||||
}
|
||||
}
|
||||
|
||||
return ['sent' => $sent, 'errors' => $errors, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{name: string, email: string}>
|
||||
*/
|
||||
private function getAccountManagerRecipients(int $clientId): array
|
||||
{
|
||||
$rows = $this->insurerRMModel
|
||||
->select('user_profiles.first_name, user_profiles.email')
|
||||
->join('user_profiles', 'user_profiles.id = client_rm.user_id')
|
||||
->where('client_rm.client_id', $clientId)
|
||||
->where('client_rm.level', 3)
|
||||
->where('client_rm.is_active', 1)
|
||||
->where('user_profiles.is_active', 1)
|
||||
->findAll();
|
||||
|
||||
$recipients = [];
|
||||
$seenEmails = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$email = strtolower(trim((string) ($row['email'] ?? '')));
|
||||
if ($email === '' || isset($seenEmails[$email])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seenEmails[$email] = true;
|
||||
$recipients[] = [
|
||||
'name' => trim((string) ($row['first_name'] ?? 'Account Manager')) ?: 'Account Manager',
|
||||
'email' => (string) $row['email'],
|
||||
];
|
||||
}
|
||||
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
private function buildCdLowBalanceAccountManagerMailContent(array $account, string $recipientName): string
|
||||
{
|
||||
return '<p>Dear ' . esc($recipientName) . ',</p>'
|
||||
. '<p>The CD account balance for <strong>' . esc((string) ($account['client_name'] ?? '')) . '</strong> has fallen below the configured alert threshold.</p>'
|
||||
. '<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;">'
|
||||
. '<tr><td><strong>Insurer</strong></td><td>' . esc((string) ($account['insurer_name'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Insurer Branch</strong></td><td>' . esc((string) ($account['insurer_branch_name'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>CD Account No</strong></td><td>' . esc((string) ($account['cd_ac_no'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Current Balance</strong></td><td>' . esc(number_format((float) ($account['current_balance'] ?? 0), 2, '.', '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Alert Amount</strong></td><td>' . esc(number_format((float) ($account['alert_amount'] ?? 0), 2, '.', '')) . '</td></tr>'
|
||||
. '</table>'
|
||||
. '<p>Please review the CD account and take the required action.</p>';
|
||||
return $this->respond($response, $statusCode);
|
||||
}
|
||||
|
||||
|
||||
|
||||
274
app/Helpers/CdLowBalanceAlertHelper.php
Normal file
274
app/Helpers/CdLowBalanceAlertHelper.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\CDMasterModel;
|
||||
use App\Models\ClientDepositModel;
|
||||
use App\Models\ClientRMModel;
|
||||
|
||||
class CdLowBalanceAlertHelper
|
||||
{
|
||||
/**
|
||||
* Check one CD account after a balance reduction and notify account managers if below threshold.
|
||||
*/
|
||||
public static function triggerAfterBalanceReduction(?int $cdAcPk, float $currentBalance): void
|
||||
{
|
||||
if ($cdAcPk === null || $cdAcPk <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$account = self::buildLowBalanceAccount($cdAcPk, $currentBalance);
|
||||
if ($account === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = self::sendAccountManagerAlert($account);
|
||||
log_message('error', 'CdLowBalanceAlertHelper triggerAfterBalanceReduction cd_ac_pk=' . $cdAcPk . ': ' . json_encode($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron entry: scan all CD masters with alert threshold configured.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function runCron(): array
|
||||
{
|
||||
log_message('error', 'cronCdLowBalanceAlert: started');
|
||||
|
||||
try {
|
||||
$cdMasterModel = new CDMasterModel();
|
||||
$cdMasters = $cdMasterModel->getCdMastersWithLowAlertThreshold();
|
||||
log_message('error', 'cronCdLowBalanceAlert: CD masters with alert threshold count = ' . count($cdMasters));
|
||||
|
||||
$depositModel = new ClientDepositModel();
|
||||
$lowBalanceAccounts = [];
|
||||
|
||||
foreach ($cdMasters as $cdMaster) {
|
||||
$cdAcPk = (int) ($cdMaster['id'] ?? 0);
|
||||
$threshold = (float) ($cdMaster['cd_balance_low_alert_amount'] ?? 0);
|
||||
|
||||
if ($cdAcPk <= 0 || $threshold <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentBalance = self::resolveCurrentBalance($depositModel, $cdAcPk, (float) ($cdMaster['opening_bal'] ?? 0));
|
||||
if ($currentBalance >= $threshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowBalanceAccounts[] = array_merge($cdMaster, [
|
||||
'current_balance' => $currentBalance,
|
||||
'alert_amount' => $threshold,
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($lowBalanceAccounts)) {
|
||||
$response = [
|
||||
'status' => true,
|
||||
'message' => 'No CD accounts below the configured low-balance alert amount.',
|
||||
'low_balance_count' => 0,
|
||||
'account_manager_mails_sent' => 0,
|
||||
'data' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$accountManagerMailsSent = 0;
|
||||
$mailErrors = [];
|
||||
$data = [];
|
||||
|
||||
foreach ($lowBalanceAccounts as $account) {
|
||||
$result = self::sendAccountManagerAlert($account);
|
||||
$accountManagerMailsSent += (int) ($result['sent'] ?? 0);
|
||||
if (! empty($result['errors'])) {
|
||||
$mailErrors = array_merge($mailErrors, $result['errors']);
|
||||
}
|
||||
if (! empty($result['data'])) {
|
||||
$data = array_merge($data, $result['data']);
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => true,
|
||||
'message' => 'CD low balance alert cron completed.',
|
||||
'low_balance_count' => count($lowBalanceAccounts),
|
||||
'account_manager_mails_sent' => $accountManagerMailsSent,
|
||||
'data' => $data,
|
||||
'errors' => $mailErrors,
|
||||
];
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
|
||||
return $response;
|
||||
} catch (\Throwable $e) {
|
||||
$response = [
|
||||
'status' => false,
|
||||
'message' => 'CD low balance alert cron failed.',
|
||||
'low_balance_count' => 0,
|
||||
'account_manager_mails_sent' => 0,
|
||||
'data' => [],
|
||||
'errors' => [$e->getMessage()],
|
||||
];
|
||||
log_message('error', 'cronCdLowBalanceAlert Exception: ' . $e->getMessage());
|
||||
log_message('error', 'cronCdLowBalanceAlert: ' . json_encode($response));
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private static function buildLowBalanceAccount(int $cdAcPk, float $currentBalance): ?array
|
||||
{
|
||||
$cdMasterModel = new CDMasterModel();
|
||||
$cdMaster = $cdMasterModel->getCdMasterWithLowAlertThresholdById($cdAcPk);
|
||||
if ($cdMaster === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$threshold = (float) ($cdMaster['cd_balance_low_alert_amount'] ?? 0);
|
||||
if ($threshold <= 0 || $currentBalance >= $threshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_merge($cdMaster, [
|
||||
'current_balance' => $currentBalance,
|
||||
'alert_amount' => $threshold,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function resolveCurrentBalance(ClientDepositModel $depositModel, int $cdAcPk, float $openingBalance): float
|
||||
{
|
||||
$lastDeposit = $depositModel
|
||||
->select('balance')
|
||||
->where('cd_ac_pk', $cdAcPk)
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
|
||||
if (! empty($lastDeposit)) {
|
||||
return (float) ($lastDeposit['balance'] ?? 0);
|
||||
}
|
||||
|
||||
return $openingBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $account
|
||||
* @return array{sent: int, errors: array<int, string>, data: array<int, array<string, mixed>>}
|
||||
*/
|
||||
private static function sendAccountManagerAlert(array $account): array
|
||||
{
|
||||
$clientId = (int) ($account['client_id'] ?? 0);
|
||||
$cdAcPk = (int) ($account['id'] ?? 0);
|
||||
$cdAcNo = (string) ($account['cd_ac_no'] ?? '');
|
||||
|
||||
if ($clientId <= 0) {
|
||||
log_message('error', 'cronCdLowBalanceAlert ACM mail: skipped, invalid client_id for cd_ac_pk=' . $cdAcPk);
|
||||
|
||||
return ['sent' => 0, 'errors' => [], 'data' => []];
|
||||
}
|
||||
|
||||
$recipients = self::getAccountManagerRecipients($clientId);
|
||||
if (empty($recipients)) {
|
||||
$error = "Client {$clientId}: No account manager found.";
|
||||
log_message('error', 'cronCdLowBalanceAlert ACM mail: ' . $error);
|
||||
|
||||
return ['sent' => 0, 'errors' => [$error], 'data' => []];
|
||||
}
|
||||
|
||||
$sent = 0;
|
||||
$errors = [];
|
||||
$data = [];
|
||||
$subject = 'CD Low Balance Alert - ' . ($account['client_name'] ?? 'Client');
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$acmEmail = (string) ($recipient['email'] ?? '');
|
||||
$acmName = (string) ($recipient['name'] ?? 'Account Manager');
|
||||
$message = self::buildAccountManagerMailContent($account, $acmName);
|
||||
|
||||
$mailResponse = MailHelper::send_email([
|
||||
'mail' => $acmEmail,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'common' => [
|
||||
'mail_type' => 'account_manager_cd_low_balance_alert',
|
||||
'client_id' => $clientId,
|
||||
],
|
||||
]);
|
||||
|
||||
$decoded = is_string($mailResponse) ? json_decode($mailResponse, true) : $mailResponse;
|
||||
$mailStatus = is_array($decoded) && ($decoded['status'] ?? '') === 'success' ? 'success' : 'failed';
|
||||
|
||||
$data[] = [
|
||||
'client_id' => $clientId,
|
||||
'cd_ac_pk' => $cdAcPk,
|
||||
'cd_ac_no' => $cdAcNo,
|
||||
'acm_name' => $acmName,
|
||||
'acm_email' => $acmEmail,
|
||||
'mail_status' => $mailStatus,
|
||||
];
|
||||
|
||||
if ($mailStatus === 'success') {
|
||||
$sent++;
|
||||
} else {
|
||||
$errors[] = "Client {$clientId}: Failed to send alert to {$acmEmail}.";
|
||||
}
|
||||
}
|
||||
|
||||
return ['sent' => $sent, 'errors' => $errors, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{name: string, email: string}>
|
||||
*/
|
||||
private static function getAccountManagerRecipients(int $clientId): array
|
||||
{
|
||||
$rows = (new ClientRMModel())
|
||||
->select('user_profiles.first_name, user_profiles.email')
|
||||
->join('user_profiles', 'user_profiles.id = client_rm.user_id')
|
||||
->where('client_rm.client_id', $clientId)
|
||||
->where('client_rm.level', 3)
|
||||
->where('client_rm.is_active', 1)
|
||||
->where('user_profiles.is_active', 1)
|
||||
->findAll();
|
||||
|
||||
$recipients = [];
|
||||
$seenEmails = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$email = strtolower(trim((string) ($row['email'] ?? '')));
|
||||
if ($email === '' || isset($seenEmails[$email])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seenEmails[$email] = true;
|
||||
$recipients[] = [
|
||||
'name' => trim((string) ($row['first_name'] ?? 'Account Manager')) ?: 'Account Manager',
|
||||
'email' => (string) $row['email'],
|
||||
];
|
||||
}
|
||||
|
||||
return $recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $account
|
||||
*/
|
||||
private static function buildAccountManagerMailContent(array $account, string $recipientName): string
|
||||
{
|
||||
return '<p>Dear ' . esc($recipientName) . ',</p>'
|
||||
. '<p>The CD account balance for <strong>' . esc((string) ($account['client_name'] ?? '')) . '</strong> has fallen below the configured alert threshold.</p>'
|
||||
. '<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;">'
|
||||
. '<tr><td><strong>Insurer</strong></td><td>' . esc((string) ($account['insurer_name'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Insurer Branch</strong></td><td>' . esc((string) ($account['insurer_branch_name'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>CD Account No</strong></td><td>' . esc((string) ($account['cd_ac_no'] ?? '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Current Balance</strong></td><td>' . esc(number_format((float) ($account['current_balance'] ?? 0), 2, '.', '')) . '</td></tr>'
|
||||
. '<tr><td><strong>Alert Amount</strong></td><td>' . esc(number_format((float) ($account['alert_amount'] ?? 0), 2, '.', '')) . '</td></tr>'
|
||||
. '</table>'
|
||||
. '<p>Please review the CD account and take the required action.</p>';
|
||||
}
|
||||
}
|
||||
@ -76,6 +76,11 @@ class DepositHelper
|
||||
|
||||
// Return the response
|
||||
if ($insertId) {
|
||||
if (($data['transaction_type'] ?? 'Credit') !== 'Credit') {
|
||||
$cdAcPk = self::resolveCdAcPk($data);
|
||||
CdLowBalanceAlertHelper::triggerAfterBalanceReduction($cdAcPk, $newBalance);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Transaction saved successfully',
|
||||
@ -126,6 +131,24 @@ class DepositHelper
|
||||
|
||||
return $lastBalance;
|
||||
}
|
||||
|
||||
private static function resolveCdAcPk(array $data): ?int
|
||||
{
|
||||
if (! empty($data['cd_ac_pk'])) {
|
||||
return (int) $data['cd_ac_pk'];
|
||||
}
|
||||
|
||||
if (empty($data['cd_ac_no'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cdMaster = (new CDMasterModel())
|
||||
->where('cd_ac_no', $data['cd_ac_no'])
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
|
||||
return $cdMaster ? (int) $cdMaster['id'] : null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@ -118,6 +118,34 @@ class CDMasterModel extends Model
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getCdMastersWithLowAlertThreshold(): array
|
||||
{
|
||||
return $this->buildCdMastersWithLowAlertThresholdQuery()
|
||||
->orderBy('cd_master.id', 'desc')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function getCdMasterWithLowAlertThresholdById(int $cdAcPk): ?array
|
||||
{
|
||||
if ($cdAcPk <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $this->buildCdMastersWithLowAlertThresholdQuery()
|
||||
->where('cd_master.id', $cdAcPk)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \CodeIgniter\Database\BaseBuilder
|
||||
*/
|
||||
private function buildCdMastersWithLowAlertThresholdQuery()
|
||||
{
|
||||
return $this->db->table('cd_master')
|
||||
->select('cd_master.*, clients.client_name, clients.short_name, insurers.name AS insurer_name, insurer_branch.branch_name AS insurer_branch_name')
|
||||
@ -128,9 +156,6 @@ class CDMasterModel extends Model
|
||||
->where('clients.is_active', 1)
|
||||
->where('insurers.is_active', 1)
|
||||
->where('cd_master.cd_balance_low_alert_amount IS NOT NULL', null, false)
|
||||
->where('cd_master.cd_balance_low_alert_amount >', 0)
|
||||
->orderBy('cd_master.id', 'desc')
|
||||
->get()
|
||||
->getResultArray();
|
||||
->where('cd_master.cd_balance_low_alert_amount >', 0);
|
||||
}
|
||||
}
|
||||
|
||||
196
tests/smoke_cd_low_balance_alert.php
Normal file
196
tests/smoke_cd_low_balance_alert.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Smoke test: CD low balance alert on debit (DepositHelper + CdLowBalanceAlertHelper)
|
||||
*
|
||||
* Run:
|
||||
* php tests/smoke_cd_low_balance_alert.php [cd_ac_pk] # dry-run: show state only
|
||||
* php tests/smoke_cd_low_balance_alert.php [cd_ac_pk] --debit 1 # live: post 1 INR test debit + alert check
|
||||
* php tests/smoke_cd_low_balance_alert.php --cron # run cron scan only
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
ob_start();
|
||||
|
||||
define('FCPATH', __DIR__ . '/../public/');
|
||||
chdir(FCPATH);
|
||||
|
||||
require FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
|
||||
require_once SYSTEMPATH . 'Config/DotEnv.php';
|
||||
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
|
||||
|
||||
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
|
||||
|
||||
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
|
||||
if (is_file($boot)) {
|
||||
require_once $boot;
|
||||
}
|
||||
|
||||
use App\Helpers\CdLowBalanceAlertHelper;
|
||||
use App\Helpers\DepositHelper;
|
||||
use App\Models\CDMasterModel;
|
||||
use App\Models\ClientDepositModel;
|
||||
use App\Models\ClientRMModel;
|
||||
|
||||
$pass = 0;
|
||||
$fail = 0;
|
||||
$results = [];
|
||||
|
||||
function ok(string $label, bool $cond, string $detail = ''): void
|
||||
{
|
||||
global $pass, $fail, $results;
|
||||
if ($cond) {
|
||||
$pass++;
|
||||
$results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
} else {
|
||||
$fail++;
|
||||
$results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
}
|
||||
}
|
||||
|
||||
function argValue(string $flag): ?string
|
||||
{
|
||||
global $argv;
|
||||
$idx = array_search($flag, $argv, true);
|
||||
if ($idx === false || ! isset($argv[$idx + 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $argv[$idx + 1];
|
||||
}
|
||||
|
||||
function resolveCurrentBalance(ClientDepositModel $depositModel, int $cdAcPk, float $openingBalance): float
|
||||
{
|
||||
$lastDeposit = $depositModel
|
||||
->select('balance')
|
||||
->where('cd_ac_pk', $cdAcPk)
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
|
||||
if (! empty($lastDeposit)) {
|
||||
return (float) ($lastDeposit['balance'] ?? 0);
|
||||
}
|
||||
|
||||
return $openingBalance;
|
||||
}
|
||||
|
||||
$db = db_connect('default');
|
||||
$cdMasterModel = new CDMasterModel();
|
||||
$depositModel = new ClientDepositModel();
|
||||
$runCron = in_array('--cron', $argv, true);
|
||||
$debitAmount = argValue('--debit');
|
||||
$cdAcPkArg = null;
|
||||
|
||||
foreach (array_slice($argv, 1) as $arg) {
|
||||
if (str_starts_with($arg, '--')) {
|
||||
continue;
|
||||
}
|
||||
if (ctype_digit($arg)) {
|
||||
$cdAcPkArg = (int) $arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL . '=== smoke_cd_low_balance_alert ===' . PHP_EOL;
|
||||
|
||||
if ($runCron) {
|
||||
$response = CdLowBalanceAlertHelper::runCron();
|
||||
ok('cron status', ($response['status'] ?? false) === true, (string) ($response['message'] ?? ''));
|
||||
ok('cron response has data key', array_key_exists('data', $response));
|
||||
echo 'Cron response: ' . json_encode($response, JSON_PRETTY_PRINT) . PHP_EOL;
|
||||
} else {
|
||||
$configured = $cdMasterModel->getCdMastersWithLowAlertThreshold();
|
||||
ok('at least one CD master has alert threshold', count($configured) > 0, 'count=' . count($configured));
|
||||
|
||||
if ($cdAcPkArg === null && ! empty($configured)) {
|
||||
$cdAcPkArg = (int) ($configured[0]['id'] ?? 0);
|
||||
}
|
||||
|
||||
ok('cd_ac_pk resolved', $cdAcPkArg !== null && $cdAcPkArg > 0, 'cd_ac_pk=' . ($cdAcPkArg ?? 'none'));
|
||||
|
||||
if ($cdAcPkArg > 0) {
|
||||
$cdMaster = $cdMasterModel->getCdMasterWithLowAlertThresholdById($cdAcPkArg);
|
||||
ok('CD master found with alert threshold', $cdMaster !== null, 'cd_ac_pk=' . $cdAcPkArg);
|
||||
|
||||
if ($cdMaster !== null) {
|
||||
$threshold = (float) ($cdMaster['cd_balance_low_alert_amount'] ?? 0);
|
||||
$currentBalance = resolveCurrentBalance($depositModel, $cdAcPkArg, (float) ($cdMaster['opening_bal'] ?? 0));
|
||||
$isLow = $currentBalance < $threshold;
|
||||
|
||||
echo PHP_EOL . 'Account snapshot:' . PHP_EOL;
|
||||
echo ' client_id : ' . ($cdMaster['client_id'] ?? '') . ' (' . ($cdMaster['client_name'] ?? '') . ')' . PHP_EOL;
|
||||
echo ' cd_ac_pk : ' . $cdAcPkArg . PHP_EOL;
|
||||
echo ' cd_ac_no : ' . ($cdMaster['cd_ac_no'] ?? '') . PHP_EOL;
|
||||
echo ' balance : ' . number_format($currentBalance, 2) . PHP_EOL;
|
||||
echo ' alert amt : ' . number_format($threshold, 2) . PHP_EOL;
|
||||
echo ' below alert : ' . ($isLow ? 'yes' : 'no') . PHP_EOL;
|
||||
|
||||
$acmRows = (new ClientRMModel())
|
||||
->select('user_profiles.first_name, user_profiles.email')
|
||||
->join('user_profiles', 'user_profiles.id = client_rm.user_id')
|
||||
->where('client_rm.client_id', (int) $cdMaster['client_id'])
|
||||
->where('client_rm.level', 3)
|
||||
->where('client_rm.is_active', 1)
|
||||
->where('user_profiles.is_active', 1)
|
||||
->findAll();
|
||||
ok('account manager exists for client', count($acmRows) > 0, 'count=' . count($acmRows));
|
||||
|
||||
foreach ($acmRows as $row) {
|
||||
echo ' ACM : ' . ($row['first_name'] ?? '') . ' <' . ($row['email'] ?? '') . '>' . PHP_EOL;
|
||||
}
|
||||
|
||||
if ($debitAmount !== null) {
|
||||
$amount = (float) $debitAmount;
|
||||
ok('debit amount > 0', $amount > 0, 'amount=' . $amount);
|
||||
|
||||
$beforeBalance = $currentBalance;
|
||||
$afterBalance = $beforeBalance - $amount;
|
||||
|
||||
echo PHP_EOL . 'Posting test debit of ' . number_format($amount, 2) . ' ...' . PHP_EOL;
|
||||
|
||||
$saveResult = DepositHelper::saveDeposit([
|
||||
'amount' => $amount,
|
||||
'sub_type_id' => 4,
|
||||
'client_id' => (int) $cdMaster['client_id'],
|
||||
'client_policy_id' => 0,
|
||||
'endorsement_no' => 'SMOKE-TEST-' . date('YmdHis'),
|
||||
'cd_ac_no' => (string) ($cdMaster['cd_ac_no'] ?? ''),
|
||||
'insurer_id' => (int) ($cdMaster['insurer_id'] ?? 0),
|
||||
'unit' => 'SMOKE',
|
||||
'description' => 'Smoke test debit for CD low balance alert',
|
||||
'transaction_type' => 'Debit',
|
||||
'updated_by' => 1,
|
||||
'event_name' => 'smoke_test',
|
||||
'is_active' => 1,
|
||||
'cd_ac_pk' => $cdAcPkArg,
|
||||
], 1);
|
||||
|
||||
ok('saveDeposit succeeded', ($saveResult['success'] ?? false) === true, json_encode($saveResult));
|
||||
|
||||
$newBalance = resolveCurrentBalance($depositModel, $cdAcPkArg, (float) ($cdMaster['opening_bal'] ?? 0));
|
||||
ok('balance reduced after debit', $newBalance < $beforeBalance, "before={$beforeBalance}, after={$newBalance}");
|
||||
|
||||
if ($afterBalance < $threshold) {
|
||||
ok('balance now below alert threshold', $newBalance < $threshold);
|
||||
echo 'Check writable/logs/log-' . date('Y-m-d') . '.log for CdLowBalanceAlertHelper triggerAfterBalanceReduction' . PHP_EOL;
|
||||
echo 'ACM mail should be sent if mail is configured.' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Balance still above alert threshold after debit; no mail expected.' . PHP_EOL;
|
||||
echo 'Tip: use a larger --debit amount or lower cd_balance_low_alert_amount for this account.' . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
echo PHP_EOL . 'Dry run only (no debit posted).' . PHP_EOL;
|
||||
echo 'To test live debit + alert: php tests/smoke_cd_low_balance_alert.php ' . $cdAcPkArg . ' --debit 1' . PHP_EOL;
|
||||
echo 'To test cron scan: php tests/smoke_cd_low_balance_alert.php --cron' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL . implode(PHP_EOL, $results) . PHP_EOL;
|
||||
echo PHP_EOL . "Summary: {$pass} passed, {$fail} failed" . PHP_EOL;
|
||||
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
Loading…
Reference in New Issue
Block a user