275 lines
10 KiB
PHP
275 lines
10 KiB
PHP
<?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>';
|
|
}
|
|
}
|