*/ 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|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 $account * @return array{sent: int, errors: array, data: array>} */ 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 */ 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 $account */ private static function buildAccountManagerMailContent(array $account, string $recipientName): string { return '

Dear ' . esc($recipientName) . ',

' . '

The CD account balance for ' . esc((string) ($account['client_name'] ?? '')) . ' has fallen below the configured alert threshold.

' . '' . '' . '' . '' . '' . '' . '
Insurer' . esc((string) ($account['insurer_name'] ?? '')) . '
Insurer Branch' . esc((string) ($account['insurer_branch_name'] ?? '')) . '
CD Account No' . esc((string) ($account['cd_ac_no'] ?? '')) . '
Current Balance' . esc(number_format((float) ($account['current_balance'] ?? 0), 2, '.', '')) . '
Alert Amount' . esc(number_format((float) ($account['alert_amount'] ?? 0), 2, '.', '')) . '
' . '

Please review the CD account and take the required action.

'; } }