MERGE_TEST_BUG_FIXES
This commit is contained in:
commit
d028087d23
17
.env.sample
17
.env.sample
@ -180,6 +180,23 @@ bds.installmentReminder.daily = true
|
||||
# Comma-separated weekdays (mon,tue,wed,thu,fri,sat,sun) — used only when daily = false
|
||||
bds.installmentReminder.days = mon,tue,wed,thu,fri
|
||||
|
||||
# Policy Transaction Renewal Reminder — used by sendRenewalReminderMail cron
|
||||
pt.renewalReminder.enabled = true
|
||||
pt.renewalReminder.daily = true
|
||||
pt.renewalReminder.days = mon,tue,wed,thu,fri
|
||||
# Client types: 1=Group, 2=Individual
|
||||
pt.renewalReminder.clientTypes = 1,2
|
||||
# renewal_date | policy_end_date
|
||||
pt.renewalReminder.dateField = renewal_date
|
||||
pt.renewalReminder.daysBefore = 30
|
||||
pt.renewalReminder.includeClientEmail = true
|
||||
# When true, also send for policies whose dateField is already past (overdue)
|
||||
pt.renewalReminder.includeOverdue = false
|
||||
pt.renewalReminder.toEmails =
|
||||
pt.renewalReminder.ccEmails =
|
||||
pt.renewalReminder.bccEmails =
|
||||
pt.renewalReminder.fromMail =
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# MEDI ASSIST WELLNESS SSO Configuration
|
||||
|
||||
@ -70,7 +70,7 @@ class Filters extends BaseConfig
|
||||
'HttpRequestLog' => ['except' => 'cli/*'],
|
||||
'Cors',
|
||||
'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','employeeRest/*','processjob','getCommission','downloadEmployeeEcardZip', 'downloadClaimFile/*', 'api/v1/*', 'cronCdLowBalanceAlert', 'sendCroneRemainderMail']],
|
||||
'SecurityInputFilter' => ['except' => ['/client/notification/create','/ticket/crud_mail_template/*','test_mail','leads/sendMail', 'ticket/reply'] ],
|
||||
'SecurityInputFilter' => ['except' => ['/client/notification/create','/ticket/crud_mail_template/*','test_mail','leads/sendMail', 'ticket/reply', 'policy_tranction/retail-policy/template', 'policy_tranction/retail-policy/send-test-mail'] ],
|
||||
'GlobalPostFileUploadGuard'
|
||||
// 'csrf',
|
||||
// 'invalidchars',
|
||||
|
||||
321
app/Config/PtRenewalReminderConfig.php
Normal file
321
app/Config/PtRenewalReminderConfig.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace Config;
|
||||
|
||||
use CodeIgniter\Config\BaseConfig;
|
||||
use Config\Database;
|
||||
|
||||
class PtRenewalReminderConfig extends BaseConfig
|
||||
{
|
||||
public bool $enabled = false;
|
||||
|
||||
public bool $daily = false;
|
||||
|
||||
/** @var string[] Lowercase three-letter day abbreviations, e.g. mon, tue */
|
||||
public array $days = [];
|
||||
|
||||
/** @var int[] Client type ids: 1=Group, 2=Individual */
|
||||
public array $clientTypes = [];
|
||||
|
||||
/** Whitelisted: renewal_date | policy_end_date */
|
||||
public string $dateField = 'renewal_date';
|
||||
|
||||
public int $daysBefore = 30;
|
||||
|
||||
public bool $includeClientEmail = false;
|
||||
|
||||
/** When true, also include policies whose dateField is already past */
|
||||
public bool $includeOverdue = false;
|
||||
|
||||
/** @var string[] */
|
||||
public array $toEmails = [];
|
||||
|
||||
/** @var string[] */
|
||||
public array $ccEmails = [];
|
||||
|
||||
/** @var string[] */
|
||||
public array $bccEmails = [];
|
||||
|
||||
public string $fromMail = '';
|
||||
|
||||
/** Loaded from notifications.subject when present */
|
||||
public string $mailSubject = '';
|
||||
|
||||
/** Loaded from notifications.mail_content when present */
|
||||
public string $mailContent = '';
|
||||
|
||||
public ?int $notificationId = null;
|
||||
|
||||
private const ALLOWED_DATE_FIELDS = ['renewal_date', 'policy_end_date'];
|
||||
|
||||
public const TEMPLATE_NAME = 'retail_reminder_mail';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Env defaults (seed / fallback)
|
||||
$defaults = [
|
||||
'enabled' => env('pt.renewalReminder.enabled', 'true'),
|
||||
'daily' => env('pt.renewalReminder.daily', 'true'),
|
||||
'days' => env('pt.renewalReminder.days', 'mon,tue,wed,thu,fri'),
|
||||
'clientTypes' => env('pt.renewalReminder.clientTypes', '2'),
|
||||
'dateField' => env('pt.renewalReminder.dateField', 'renewal_date'),
|
||||
'daysBefore' => env('pt.renewalReminder.daysBefore', 30),
|
||||
'includeClientEmail' => env('pt.renewalReminder.includeClientEmail', 'true'),
|
||||
'includeOverdue' => env('pt.renewalReminder.includeOverdue', 'false'),
|
||||
'toEmails' => env('pt.renewalReminder.toEmails', ''),
|
||||
'ccEmails' => env('pt.renewalReminder.ccEmails', ''),
|
||||
'bccEmails' => env('pt.renewalReminder.bccEmails', ''),
|
||||
'fromMail' => env('pt.renewalReminder.fromMail', ''),
|
||||
];
|
||||
|
||||
$dbConfig = $this->loadConfigFromNotification();
|
||||
$merged = array_merge($defaults, $dbConfig);
|
||||
|
||||
$this->applyMergedConfig($merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function loadConfigFromNotification(): array
|
||||
{
|
||||
try {
|
||||
$db = Database::connect();
|
||||
$row = $db->table('notifications')
|
||||
->where('template_name', self::TEMPLATE_NAME)
|
||||
->groupStart()
|
||||
->where('client_id', null)
|
||||
->orWhere('client_id', 0)
|
||||
->orWhere('client_id', '')
|
||||
->groupEnd()
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (empty($row)) {
|
||||
log_message('error', '[PtRenewalReminderConfig] No notifications row for retail_reminder_mail; using env defaults');
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->notificationId = isset($row['id']) ? (int) $row['id'] : null;
|
||||
$this->mailSubject = trim((string) ($row['subject'] ?? ''));
|
||||
$this->mailContent = (string) ($row['mail_content'] ?? '');
|
||||
|
||||
$json = trim((string) ($row['config_json'] ?? ''));
|
||||
if ($json === '') {
|
||||
log_message('error', '[PtRenewalReminderConfig] config_json empty for notification_id=' . ($this->notificationId ?? 'n/a'));
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($json, true);
|
||||
if (! is_array($decoded)) {
|
||||
log_message('error', '[PtRenewalReminderConfig] Invalid config_json for notification_id=' . ($this->notificationId ?? 'n/a'));
|
||||
return [];
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
} catch (\Throwable $e) {
|
||||
log_message(
|
||||
'error',
|
||||
'[PtRenewalReminderConfig] Failed loading notification config: ' . $e->getMessage()
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $merged
|
||||
*/
|
||||
private function applyMergedConfig(array $merged): void
|
||||
{
|
||||
$this->enabled = $this->toBool($merged['enabled'] ?? false);
|
||||
$this->daily = $this->toBool($merged['daily'] ?? false);
|
||||
|
||||
$daysRaw = $merged['days'] ?? 'mon,tue,wed,thu,fri';
|
||||
if (is_array($daysRaw)) {
|
||||
$daysRaw = implode(',', $daysRaw);
|
||||
}
|
||||
$this->days = array_values(array_filter(array_map(
|
||||
static fn (string $day): string => strtolower(substr(trim($day), 0, 3)),
|
||||
explode(',', (string) $daysRaw)
|
||||
)));
|
||||
|
||||
$clientTypesRaw = $merged['clientTypes'] ?? '2';
|
||||
if (is_array($clientTypesRaw)) {
|
||||
$this->clientTypes = array_values(array_unique(array_filter(array_map(
|
||||
static fn ($type): int => (int) $type,
|
||||
$clientTypesRaw
|
||||
), static fn (int $type): bool => $type > 0)));
|
||||
} else {
|
||||
$this->clientTypes = array_values(array_unique(array_filter(array_map(
|
||||
static fn (string $type): int => (int) trim($type),
|
||||
explode(',', (string) $clientTypesRaw)
|
||||
), static fn (int $type): bool => $type > 0)));
|
||||
}
|
||||
|
||||
$dateField = strtolower(trim((string) ($merged['dateField'] ?? 'renewal_date')));
|
||||
if (! in_array($dateField, self::ALLOWED_DATE_FIELDS, true)) {
|
||||
log_message(
|
||||
'error',
|
||||
'[PtRenewalReminderConfig] Invalid dateField "' . $dateField . '", falling back to renewal_date'
|
||||
);
|
||||
$dateField = 'renewal_date';
|
||||
}
|
||||
$this->dateField = $dateField;
|
||||
|
||||
$this->daysBefore = max(0, (int) ($merged['daysBefore'] ?? 30));
|
||||
|
||||
$this->includeClientEmail = $this->toBool($merged['includeClientEmail'] ?? true);
|
||||
$this->includeOverdue = $this->toBool($merged['includeOverdue'] ?? false);
|
||||
|
||||
$this->toEmails = $this->parseEmailList($merged['toEmails'] ?? '');
|
||||
$this->ccEmails = $this->parseEmailList($merged['ccEmails'] ?? '');
|
||||
$this->bccEmails = $this->parseEmailList($merged['bccEmails'] ?? '');
|
||||
$this->fromMail = trim((string) ($merged['fromMail'] ?? ''));
|
||||
}
|
||||
|
||||
public function shouldRunToday(): bool
|
||||
{
|
||||
if (! $this->enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->daily) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$today = strtolower(date('D'));
|
||||
|
||||
return in_array($today, $this->days, true);
|
||||
}
|
||||
|
||||
public function getDateField(): string
|
||||
{
|
||||
return $this->dateField;
|
||||
}
|
||||
|
||||
public function getDaysBefore(): int
|
||||
{
|
||||
return $this->daysBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getClientTypes(): array
|
||||
{
|
||||
return $this->clientTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getToEmails(): array
|
||||
{
|
||||
return $this->toEmails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getCcEmails(): array
|
||||
{
|
||||
return $this->ccEmails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getBccEmails(): array
|
||||
{
|
||||
return $this->bccEmails;
|
||||
}
|
||||
|
||||
public function getFromMail(): string
|
||||
{
|
||||
return $this->fromMail;
|
||||
}
|
||||
|
||||
public function includeClientEmail(): bool
|
||||
{
|
||||
return $this->includeClientEmail;
|
||||
}
|
||||
|
||||
public function includeOverdue(): bool
|
||||
{
|
||||
return $this->includeOverdue;
|
||||
}
|
||||
|
||||
public function getMailSubject(): string
|
||||
{
|
||||
return $this->mailSubject;
|
||||
}
|
||||
|
||||
public function getMailContent(): string
|
||||
{
|
||||
return $this->mailContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten config for UI / API response.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'enabled' => $this->enabled,
|
||||
'daily' => $this->daily,
|
||||
'days' => implode(',', $this->days),
|
||||
'clientTypes' => $this->clientTypes,
|
||||
'dateField' => $this->dateField,
|
||||
'daysBefore' => $this->daysBefore,
|
||||
'includeClientEmail' => $this->includeClientEmail,
|
||||
'includeOverdue' => $this->includeOverdue,
|
||||
'toEmails' => implode(',', $this->toEmails),
|
||||
'ccEmails' => implode(',', $this->ccEmails),
|
||||
'bccEmails' => implode(',', $this->bccEmails),
|
||||
'fromMail' => $this->fromMail,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function parseEmailList(mixed $value): array
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$emails = array_map('trim', $value);
|
||||
} else {
|
||||
$emails = array_map('trim', explode(',', (string) $value));
|
||||
}
|
||||
|
||||
$unique = [];
|
||||
foreach ($emails as $email) {
|
||||
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
$key = strtolower($email);
|
||||
if (! isset($unique[$key])) {
|
||||
$unique[$key] = $email;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($unique);
|
||||
}
|
||||
|
||||
private function toBool(mixed $value): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
|
||||
}
|
||||
}
|
||||
@ -541,6 +541,8 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
|
||||
$routes->get("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
$routes->cli("cli/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
$routes->get("policy_tranction/sendRenewalReminderMail","PolicyTransactionController::sendRenewalReminderMail");
|
||||
$routes->cli("cli/sendRenewalReminderMail","PolicyTransactionController::sendRenewalReminderMail");
|
||||
$routes->cli("cli/cronDailyBDSReport", "PolicyTransactionController::cronDailyBDSReport");
|
||||
$routes->cli('cli/croneDailyActivityReport', 'DashboardController::croneDailyActivityReport');
|
||||
|
||||
@ -561,6 +563,17 @@ $routes->group("policy_tranction", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->post("getMoreInfo","PolicyTransactionController::getMoreInfo");
|
||||
$routes->post("saveInstallment","PolicyTransactionController::saveInstallment");
|
||||
|
||||
$routes->group("retail-policy", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('list', 'PolicyTransactionController::retailPolicyList');
|
||||
$routes->post('list-data', 'PolicyTransactionController::retailPolicyListData');
|
||||
$routes->post('mark-renewed', 'PolicyTransactionController::markRetailPolicyRenewed');
|
||||
$routes->get('config', 'PolicyTransactionController::getRetailReminderConfig');
|
||||
$routes->post('config', 'PolicyTransactionController::saveRetailReminderConfig');
|
||||
$routes->get('template', 'PolicyTransactionController::getRetailReminderTemplate');
|
||||
$routes->post('template', 'PolicyTransactionController::saveRetailReminderTemplate');
|
||||
$routes->post('send-test-mail', 'PolicyTransactionController::sendRetailReminderTestMail');
|
||||
});
|
||||
|
||||
$routes->group("inception", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->match(['get', 'post'], 'list', 'PolicyTransactionController::viewInception');
|
||||
$routes->post('list/datatable', 'PolicyTransactionController::inceptionListDataTable');
|
||||
|
||||
@ -7732,14 +7732,16 @@ class EmployeeRestController extends AdminController
|
||||
|
||||
$benefits = [];
|
||||
if (!empty($termsArray['enrollment_display_key']) && is_array($termsArray['enrollment_display_key'])) {
|
||||
$benefits = $termsArray['enrollment_display_key'];
|
||||
$benefits = $this->normalizePolicyTermsKeys($termsArray['enrollment_display_key']);
|
||||
} else {
|
||||
$benefits = $this->policyTermsFiter($termsRaw, $ticketMeta['policy_group']);
|
||||
$benefits = $this->normalizePolicyTermsKeys(
|
||||
$this->policyTermsFiter($termsRaw, $ticketMeta['policy_group']) ?: []
|
||||
);
|
||||
}
|
||||
|
||||
$policyTerms = [];
|
||||
if (!empty($termsArray['enrollment_display_key']) && is_array($termsArray['enrollment_display_key'])) {
|
||||
$policyTerms = $termsArray['enrollment_display_key'];
|
||||
$policyTerms = $this->normalizePolicyTermsKeys($termsArray['enrollment_display_key']);
|
||||
}
|
||||
|
||||
$familyFloaters = $this->extractFamilyFloaters($termsArray);
|
||||
@ -8032,6 +8034,36 @@ class EmployeeRestController extends AdminController
|
||||
return number_format((float) $amount, 0, '.', ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize enrollment_display_key terms:
|
||||
* - "Room Rent Limit" => room_rent_limit
|
||||
* - whitespace-only values => ""
|
||||
*/
|
||||
private function normalizePolicyTermsKeys(array $terms): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($terms as $key => $value) {
|
||||
$snakeKey = strtolower(trim((string) $key));
|
||||
$snakeKey = preg_replace('/[^a-z0-9]+/', '_', $snakeKey);
|
||||
$snakeKey = trim($snakeKey, '_');
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
} elseif (is_array($value)) {
|
||||
$value = $this->normalizePolicyTermsKeys($value);
|
||||
}
|
||||
|
||||
if ($snakeKey === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized[$snakeKey] = $value;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private function buildPremiumDetails(array $coveredMembers, int $policyTypeId): array
|
||||
{
|
||||
$siValue = 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -906,7 +906,7 @@ class VoloApiController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* E-card PDF — returns a data URL the app can open, or null on failure.
|
||||
* E-card PDF — saves base64 PDF to public/tmp and returns a downloadable URL, or null on failure.
|
||||
*
|
||||
* @param string|null $voloEmployeeId Volo/TPA employee or member id stored in employee_polices.tpa_id
|
||||
*/
|
||||
@ -919,13 +919,15 @@ class VoloApiController extends BaseController
|
||||
}
|
||||
|
||||
$entityId = $this->resolveEntityId((string) $policy_no);
|
||||
log_message('error', 'VOLO - Ecard | entity id: ' . $entityId . ' | policy_no: ' . $policy_no . ' | voloEmployeeId: ' . $voloEmployeeId);
|
||||
if ($entityId === null) {
|
||||
log_message('error', 'VOLO - Ecard | entity id not resolved | policy: ' . ($policy_no ?? ''));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$res = $this->getEcardPdfRequest((string) $voloEmployeeId, $entityId);
|
||||
$res = $this->getEcardPdfRequest((string) $emp_code, $entityId);
|
||||
log_message('error', 'VOLO - Ecard | res: ' . json_encode($res));
|
||||
if (empty($res['status']) || !is_array($res['data'])) {
|
||||
log_message('error', 'VOLO - Ecard FAILED | ' . json_encode($res));
|
||||
|
||||
@ -939,6 +941,38 @@ class VoloApiController extends BaseController
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'data:application/pdf;base64,' . $body;
|
||||
// Strip data-URI prefix if the API already included it.
|
||||
if (stripos($body, 'base64,') !== false) {
|
||||
$body = substr($body, strpos($body, 'base64,') + 7);
|
||||
}
|
||||
|
||||
$pdfBinary = base64_decode($body, true);
|
||||
if ($pdfBinary === false || $pdfBinary === '') {
|
||||
log_message('error', 'VOLO - Ecard FAILED | base64 decode failed | emp_code: ' . ($emp_code ?? ''));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = FCPATH . 'tmp/';
|
||||
if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
|
||||
log_message('error', 'VOLO - Ecard FAILED | cannot create dir: ' . $dir);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$safeEmp = preg_replace('/[^A-Za-z0-9_-]/', '_', (string) $emp_code) ?: 'emp';
|
||||
$fileName = 'volo_ecard_' . $safeEmp . '_' . time() . '_' . bin2hex(random_bytes(4)) . '.pdf';
|
||||
$filePath = $dir . $fileName;
|
||||
|
||||
if (file_put_contents($filePath, $pdfBinary) === false) {
|
||||
log_message('error', 'VOLO - Ecard FAILED | cannot write file: ' . $filePath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$downloadUrl = base_url('public/tmp/' . $fileName);
|
||||
log_message('error', 'VOLO - Ecard SUCCESS | url: ' . $downloadUrl);
|
||||
|
||||
return $downloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,5 +8,5 @@ class NotificationModel extends Model
|
||||
{
|
||||
protected $table = 'notifications';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = ["id","client_id","template_name","subject","mail_content","enabled","created_by","updated_by","is_active",'mail_content_json','common_mail'];
|
||||
protected $allowedFields = ["id","client_id","template_name","subject","mail_content","enabled","created_by","updated_by","is_active",'mail_content_json','common_mail','config_json'];
|
||||
}
|
||||
@ -62,6 +62,7 @@
|
||||
'etat_band',
|
||||
'edate',
|
||||
'renewal_date',
|
||||
'renewal_status',
|
||||
'rollover_date',
|
||||
'policy_holder_name',
|
||||
'same_as_proposer',
|
||||
@ -2126,6 +2127,311 @@
|
||||
// print($this->db->getLastQuery()); die;
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Candidates for policy_transaction renewal reminder cron.
|
||||
*
|
||||
* @param string $dateField Whitelisted column: renewal_date | policy_end_date
|
||||
* @param int $daysBefore Days ahead of today to match
|
||||
* @param int[] $clientTypes Client type ids (1=Group, 2=Individual)
|
||||
* @param bool $includeOverdue When true, also include rows where dateField < today
|
||||
* @return array
|
||||
*/
|
||||
public function getRenewalReminderCandidates(
|
||||
string $dateField,
|
||||
int $daysBefore,
|
||||
array $clientTypes,
|
||||
bool $includeOverdue = false
|
||||
): array {
|
||||
$allowedDateFields = ['renewal_date', 'policy_end_date'];
|
||||
if (! in_array($dateField, $allowedDateFields, true)) {
|
||||
$dateField = 'renewal_date';
|
||||
}
|
||||
|
||||
$clientTypes = array_values(array_unique(array_filter(array_map('intval', $clientTypes), static function ($type) {
|
||||
return $type > 0;
|
||||
})));
|
||||
|
||||
if (empty($clientTypes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$daysBefore = max(0, (int) $daysBefore);
|
||||
$targetDate = date('Y-m-d', strtotime('+' . $daysBefore . ' days'));
|
||||
$today = date('Y-m-d');
|
||||
$column = 'pt.' . $dateField;
|
||||
|
||||
$query = $this->db->table('policy_transaction pt')
|
||||
->select("
|
||||
pt.id,
|
||||
pt.policy_no,
|
||||
pt.client_id,
|
||||
pt.client_branch_id,
|
||||
pt.policy_start_date,
|
||||
pt.policy_end_date,
|
||||
pt.renewal_date,
|
||||
c.client_name,
|
||||
c.short_name AS client_short_name,
|
||||
c.client_type,
|
||||
c.email AS client_email,
|
||||
c.common_mails,
|
||||
policy_type.policy_type,
|
||||
i.name AS insurer_name,
|
||||
CASE WHEN {$column} < '{$today}' THEN 1 ELSE 0 END AS is_overdue
|
||||
")
|
||||
->join('clients c', 'pt.client_id = c.id', 'left')
|
||||
->join('pt_co_share_details pt_co', 'pt.id = pt_co.pt_id and pt_co.co_share_type = 1', 'left')
|
||||
->join('insurers i', 'pt_co.insurer_id = i.id', 'left')
|
||||
->join('policy_type', 'pt.policy_type_id = policy_type.id', 'left')
|
||||
->where('pt.is_active', 1)
|
||||
->where('pt.action_type', 'inception')
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)");
|
||||
|
||||
if ($includeOverdue) {
|
||||
$query->groupStart()
|
||||
->where($column, $targetDate)
|
||||
->orWhere("{$column} <", $today)
|
||||
->groupEnd();
|
||||
} else {
|
||||
$query->where($column, $targetDate);
|
||||
}
|
||||
|
||||
$query->groupBy('pt.id')
|
||||
->orderBy('pt.id', 'DESC');
|
||||
|
||||
return $query->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Explain why renewal reminder candidates may be empty (for dry-run diagnostics).
|
||||
*
|
||||
* @param int[] $clientTypes
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function diagnoseRenewalReminderCandidates(
|
||||
string $dateField,
|
||||
int $daysBefore,
|
||||
array $clientTypes,
|
||||
bool $includeOverdue = false
|
||||
): array {
|
||||
$allowedDateFields = ['renewal_date', 'policy_end_date'];
|
||||
if (! in_array($dateField, $allowedDateFields, true)) {
|
||||
$dateField = 'renewal_date';
|
||||
}
|
||||
|
||||
$clientTypes = array_values(array_unique(array_filter(array_map('intval', $clientTypes), static function ($type) {
|
||||
return $type > 0;
|
||||
})));
|
||||
|
||||
$daysBefore = max(0, (int) $daysBefore);
|
||||
$targetDate = date('Y-m-d', strtotime('+' . $daysBefore . ' days'));
|
||||
$today = date('Y-m-d');
|
||||
$column = 'pt.' . $dateField;
|
||||
|
||||
$base = function () {
|
||||
return $this->db->table('policy_transaction pt')
|
||||
->join('clients c', 'pt.client_id = c.id', 'left')
|
||||
->where('pt.is_active', 1)
|
||||
->where('pt.action_type', 'inception');
|
||||
};
|
||||
|
||||
$countRetailActive = 0;
|
||||
$countAfterClientType = 0;
|
||||
$countAfterStatus = 0;
|
||||
$countAfterNotRenewedExists = 0;
|
||||
$countExactTargetDate = 0;
|
||||
$countOverdue = 0;
|
||||
$countFinal = 0;
|
||||
$nearestSample = [];
|
||||
|
||||
if (! empty($clientTypes)) {
|
||||
$countRetailActive = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->countAllResults();
|
||||
|
||||
$countAfterClientType = $countRetailActive;
|
||||
|
||||
$countAfterStatus = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->countAllResults();
|
||||
|
||||
$countAfterNotRenewedExists = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->countAllResults();
|
||||
|
||||
$countExactTargetDate = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->where($column, $targetDate)
|
||||
->countAllResults();
|
||||
|
||||
$countOverdue = (int) $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->where("{$column} <", $today)
|
||||
->where("{$column} IS NOT NULL", null, false)
|
||||
->where("{$column} !=", '0000-00-00')
|
||||
->countAllResults();
|
||||
|
||||
$finalBuilder = $base()
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)");
|
||||
|
||||
if ($includeOverdue) {
|
||||
$finalBuilder->groupStart()
|
||||
->where($column, $targetDate)
|
||||
->orWhere("{$column} <", $today)
|
||||
->groupEnd();
|
||||
} else {
|
||||
$finalBuilder->where($column, $targetDate);
|
||||
}
|
||||
$countFinal = (int) $finalBuilder->countAllResults();
|
||||
|
||||
// Sample nearest upcoming renewals (next 60 days) for debugging
|
||||
$from = $today;
|
||||
$to = date('Y-m-d', strtotime('+60 days'));
|
||||
$nearestSample = $base()
|
||||
->select("pt.id, pt.policy_no, c.client_name, c.email AS client_email, pt.renewal_date, pt.policy_end_date, pt.renewal_status, {$column} AS match_date")
|
||||
->whereIn('c.client_type', $clientTypes)
|
||||
->where("IFNULL(pt.renewal_status, '') NOT IN ('renewal_mail_send', 'renewed', 'reminded')", null, false)
|
||||
->where("NOT EXISTS (SELECT 1 FROM policy_transaction p2 WHERE p2.source_client_policy_id = pt.client_policy_id AND p2.is_active = 1)")
|
||||
->where("{$column} >=", $from)
|
||||
->where("{$column} <=", $to)
|
||||
->orderBy($column, 'ASC')
|
||||
->limit(10)
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
$reasons = [];
|
||||
if (empty($clientTypes)) {
|
||||
$reasons[] = 'No clientTypes configured.';
|
||||
}
|
||||
if ($countAfterClientType === 0) {
|
||||
$reasons[] = 'No active inception policies found for configured client type(s).';
|
||||
} elseif ($countAfterStatus === 0) {
|
||||
$reasons[] = 'All matching policies already have renewal_status of renewal_mail_send/renewed/reminded.';
|
||||
} elseif ($countAfterNotRenewedExists === 0) {
|
||||
$reasons[] = 'All remaining policies already have an active renewed policy linked via source_client_policy_id.';
|
||||
} elseif (! $includeOverdue && $countExactTargetDate === 0) {
|
||||
$reasons[] = "No policies have {$dateField} exactly equal to target date {$targetDate} (today + {$daysBefore} days). includeOverdue is false, so only exact date matches are selected.";
|
||||
if ($countOverdue > 0) {
|
||||
$reasons[] = "There are {$countOverdue} overdue policies that would be included if includeOverdue=true.";
|
||||
}
|
||||
} elseif ($includeOverdue && $countFinal === 0) {
|
||||
$reasons[] = "No policies match target date {$targetDate} or overdue (< {$today}) on {$dateField}.";
|
||||
}
|
||||
|
||||
if (empty($reasons) && $countFinal === 0) {
|
||||
$reasons[] = 'No candidates matched the current filters.';
|
||||
}
|
||||
|
||||
return [
|
||||
'today' => $today,
|
||||
'target_date' => $targetDate,
|
||||
'date_field' => $dateField,
|
||||
'days_before' => $daysBefore,
|
||||
'include_overdue' => $includeOverdue,
|
||||
'client_types' => $clientTypes,
|
||||
'counts' => [
|
||||
'active_inception_for_client_types' => $countAfterClientType,
|
||||
'after_excluding_sent_or_renewed' => $countAfterStatus,
|
||||
'after_excluding_already_renewed_policy' => $countAfterNotRenewedExists,
|
||||
'exact_target_date_matches' => $countExactTargetDate,
|
||||
'overdue_matches' => $countOverdue,
|
||||
'final_candidates' => $countFinal,
|
||||
],
|
||||
'reasons' => $reasons,
|
||||
'nearest_upcoming_sample_60_days' => $nearestSample,
|
||||
'hint' => $includeOverdue
|
||||
? 'Candidates = exact target date OR overdue on selected date field.'
|
||||
: "Candidates require {$dateField} = {$targetDate} exactly. Enable includeOverdue to also include past dates, or change daysBefore / dateField in Mail Config.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side DataTables list for Retail Policy (individual clients only).
|
||||
*
|
||||
* @param int $draw
|
||||
* @param int $start
|
||||
* @param int $length
|
||||
* @param string $search
|
||||
* @return array{draw:int,recordsTotal:int,recordsFiltered:int,data:array}
|
||||
*/
|
||||
public function getRetailPolicyList(int $draw, int $start, int $length, string $search = ''): array
|
||||
{
|
||||
$exportAll = $length < 0;
|
||||
$length = $exportAll ? 0 : ($length > 0 ? $length : 10);
|
||||
$start = max(0, $start);
|
||||
$search = trim($search);
|
||||
|
||||
$applyBase = static function ($builder) {
|
||||
return $builder->join('clients c', 'pt.client_id = c.id', 'left')
|
||||
->join('policy_type', 'pt.policy_type_id = policy_type.id', 'left')
|
||||
->where('pt.is_active', 1)
|
||||
->where('pt.action_type', 'inception')
|
||||
->where('c.client_type', 2)
|
||||
->where("IFNULL(pt.renewal_status, '') !=", 'renewed');
|
||||
};
|
||||
|
||||
$applySearch = static function ($builder) use ($search) {
|
||||
if ($search !== '') {
|
||||
$builder->groupStart()
|
||||
->like('pt.policy_no', $search)
|
||||
->orLike('policy_type.policy_type', $search)
|
||||
->orLike('c.client_name', $search)
|
||||
->orLike('c.phone', $search)
|
||||
->orLike('c.email', $search)
|
||||
->groupEnd();
|
||||
}
|
||||
return $builder;
|
||||
};
|
||||
|
||||
$totalBuilder = $applyBase($this->db->table('policy_transaction pt'));
|
||||
$recordsTotal = (int) $totalBuilder->countAllResults();
|
||||
|
||||
$filteredBuilder = $applySearch($applyBase($this->db->table('policy_transaction pt')));
|
||||
$recordsFiltered = (int) $filteredBuilder->countAllResults();
|
||||
|
||||
$dataBuilder = $applySearch($applyBase($this->db->table('policy_transaction pt')));
|
||||
$dataBuilder = $dataBuilder
|
||||
->select('
|
||||
pt.id,
|
||||
pt.policy_no,
|
||||
pt.renewal_date,
|
||||
pt.policy_start_date,
|
||||
pt.policy_end_date,
|
||||
pt.renewal_status,
|
||||
pt.client_id,
|
||||
c.client_name,
|
||||
c.phone AS client_mobile,
|
||||
c.email AS client_email,
|
||||
policy_type.policy_type
|
||||
')
|
||||
->orderBy("CASE WHEN LOWER(IFNULL(pt.renewal_status, '')) IN ('renewal_mail_send', 'reminded') THEN 0 ELSE 1 END", 'ASC', false)
|
||||
->orderBy('pt.id', 'DESC');
|
||||
|
||||
if (! $exportAll) {
|
||||
$dataBuilder->limit($length, $start);
|
||||
}
|
||||
|
||||
$rows = $dataBuilder->get()->getResultArray();
|
||||
|
||||
return [
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
public function getRenewalReportDataOld($start_date = null, $end_date = null, $client_type = null, $client = null, $issuer = null)
|
||||
{
|
||||
|
||||
@ -2276,6 +2276,13 @@ body[data-sidebar-size="condensed"] .footer {
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<li>
|
||||
<a href="<?= base_url('/policy_tranction/retail-policy/list') ?>">
|
||||
<i class="ri-user-line"></i>
|
||||
<span> Retail Policy</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- This Menu Hide because of Role Merge REF : SVM,SVR,KV.
|
||||
<?php if ((get_role_id() == 1 || get_role_id() == 5) || (in_array(MANAGEMENT_TEAM_ID, user_team()) || in_array(FINANCE_TEAM_ID, user_team()) || in_array(BUSINESS_TEAM_ID, user_team()))) { ?>
|
||||
<li>
|
||||
|
||||
@ -524,6 +524,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$('#gpa_si_add_more').empty();
|
||||
$('.removeDom').remove();
|
||||
|
||||
setTimeout(function() {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
@ -567,7 +570,8 @@
|
||||
$('#police-tab').css('display', 'none');
|
||||
$('.nav.nav-pills.navtab-bg').css('display', 'none');
|
||||
$('.nav.nav-pills.navtab-bg').prev().css('display', 'none');
|
||||
$('.removeDom').remove()
|
||||
$('.removeDom').remove();
|
||||
$('#gpa_si_add_more').empty();
|
||||
|
||||
$('.loader').fadeIn();
|
||||
$('.loader-mask').fadeIn();
|
||||
@ -687,7 +691,9 @@
|
||||
if (Array.isArray(gpaJsonObjectForSpecialCondition[key])) {
|
||||
gpaJsonObjectForSpecialCondition[key].forEach((value,
|
||||
index) => {
|
||||
appendGPASIAddMore(value);
|
||||
if (value !== null && value !== undefined && String(value).trim() !== '') {
|
||||
appendGPASIAddMore(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error(`${key} is not an array.`);
|
||||
@ -892,6 +898,8 @@
|
||||
$('.nav.nav-pills.navtab-bg').css('display', '');
|
||||
$('.nav.nav-pills.navtab-bg').prev().css('display', '');
|
||||
$('#policyGPATerms')[0].reset();
|
||||
$('#gpa_si_add_more').empty();
|
||||
$('.removeDom').remove();
|
||||
$('.text-danger-2').html('');
|
||||
|
||||
});
|
||||
|
||||
223
app/Views/pt_renewal_reminder_email_template.php
Normal file
223
app/Views/pt_renewal_reminder_email_template.php
Normal file
@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f7f6;
|
||||
color: #333;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1f618d, #21618c, #117a65);
|
||||
color: #ffffff;
|
||||
padding: 24px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
.header-subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
.alert-banner {
|
||||
padding: 14px 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background: #eaf4fb;
|
||||
border-left: 4px solid #2980b9;
|
||||
color: #1f4e79;
|
||||
}
|
||||
.intro {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: #2c3e50;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
display: inline-block;
|
||||
margin: 0 0 12px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.details-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.details-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.details-table td.label {
|
||||
width: 38%;
|
||||
color: #7f8c8d;
|
||||
font-weight: 600;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.details-table td.value {
|
||||
color: #2c3e50;
|
||||
}
|
||||
.next-steps {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.next-steps h4 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.next-steps ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #566573;
|
||||
}
|
||||
.note {
|
||||
font-size: 12px;
|
||||
color: #7f8c8d;
|
||||
line-height: 1.6;
|
||||
margin-top: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
.footer {
|
||||
background: #f8f9fa;
|
||||
padding: 14px 18px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #95a5a6;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@media only screen and (max-width: 480px) {
|
||||
.content {
|
||||
padding: 16px 14px;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 19px;
|
||||
}
|
||||
.details-table td.label {
|
||||
width: 42%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="width:100%;max-width:640px;margin:0 auto;background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 4px 10px rgba(0,0,0,0.06);">
|
||||
<div class="header" style="background:#1f618d;color:#ffffff;padding:24px 20px;text-align:center;">
|
||||
<h1>Policy Renewal Reminder</h1>
|
||||
<div class="header-subtitle">Nhance India Insurance</div>
|
||||
</div>
|
||||
|
||||
<div class="content" style="padding:24px 20px;">
|
||||
<div class="alert-banner" style="padding:14px 16px;border-radius:6px;margin-bottom:20px;font-size:14px;line-height:1.5;background:<?= !empty($is_overdue) ? '#fdecea' : '#eaf4fb' ?>;border-left:4px solid <?= !empty($is_overdue) ? '#c0392b' : '#2980b9' ?>;color:<?= !empty($is_overdue) ? '#922b21' : '#1f4e79' ?>;">
|
||||
<?php if (!empty($is_overdue)): ?>
|
||||
<strong>Overdue Renewal:</strong> The policy for <strong><?= esc($client_name ?? '') ?></strong>
|
||||
was due for renewal on <strong><?= esc($reminder_date ?? '') ?></strong>. Please renew at the earliest.
|
||||
<?php else: ?>
|
||||
<strong>Renewal Reminder:</strong> The policy for <strong><?= esc($client_name ?? '') ?></strong>
|
||||
is due for renewal on <strong><?= esc($reminder_date ?? '') ?></strong>
|
||||
(in <strong><?= esc((string) ($days_before ?? '')) ?></strong> day(s)).
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<p class="intro" style="font-size:14px;line-height:1.7;color:#2c3e50;margin:0 0 18px;">
|
||||
This is a reminder that a policy renewal is <?= !empty($is_overdue) ? 'overdue' : 'upcoming' ?> for
|
||||
<strong><?= esc($client_name ?? '') ?></strong>.
|
||||
Please review the details below and initiate the renewal process in time to ensure uninterrupted coverage.
|
||||
</p>
|
||||
|
||||
<h3 class="section-title" style="font-size:16px;color:#2c3e50;border-bottom:2px solid #3498db;display:inline-block;margin:0 0 12px;padding-bottom:4px;">
|
||||
Policy Details
|
||||
</h3>
|
||||
|
||||
<table class="details-table" style="width:100%;border-collapse:collapse;margin-bottom:20px;">
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Client</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($client_name ?? '-') ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Client Type</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($client_type_label ?? '-') ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Policy Number</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($policy_no ?? 'Not Assigned') ?></td>
|
||||
</tr>
|
||||
<?php if (!empty($policy_type)): ?>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Policy Type</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($policy_type) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($insurer_name)): ?>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Insurance Company</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($insurer_name) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($policy_period)): ?>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Policy Period</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc($policy_period) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Renewal / End Date</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><strong><?= esc($reminder_date ?? '-') ?></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label" style="width:38%;padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#7f8c8d;font-weight:600;background:#f8f9fa;">Days Before</td>
|
||||
<td class="value" style="padding:10px 12px;border-bottom:1px solid #ecf0f1;font-size:13px;color:#2c3e50;"><?= esc((string) ($days_before ?? '-')) ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="next-steps" style="background:#f8f9fa;border-radius:6px;padding:14px 16px;margin-top:8px;">
|
||||
<h4 style="margin:0 0 8px;font-size:14px;color:#2c3e50;">Next Steps</h4>
|
||||
<ul style="margin:0;padding-left:18px;font-size:13px;line-height:1.7;color:#566573;">
|
||||
<li>Please initiate the renewal process before <strong><?= esc($reminder_date ?? '') ?></strong>.</li>
|
||||
<li>Confirm coverage requirements and share any updated details with the concerned team.</li>
|
||||
<li>For clarification regarding the renewal, please coordinate with your Nhance contact.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p class="note" style="font-size:12px;color:#7f8c8d;line-height:1.6;margin-top:16px;font-style:italic;">
|
||||
Timely renewal helps ensure continuous insurance coverage. Thank you for your cooperation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer" style="background:#f8f9fa;padding:14px 18px;text-align:center;font-size:11px;color:#95a5a6;line-height:1.5;">
|
||||
This is an automated notification from Nhance India Insurance. Please do not reply to this email.
|
||||
<br>Generated on <?= esc($generated_on ?? date('d-m-Y H:i')) ?>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1086
app/Views/retail_policy_list.php
Normal file
1086
app/Views/retail_policy_list.php
Normal file
File diff suppressed because it is too large
Load Diff
612
writable/sql/db_changes_2026-07-01_to_2026-08-12.sql
Normal file
612
writable/sql/db_changes_2026-07-01_to_2026-08-12.sql
Normal file
@ -0,0 +1,612 @@
|
||||
-- =============================================================================
|
||||
-- NHANCE DB changes: 2026-07-01 through 2026-08-12
|
||||
-- Consolidated from app/Database/*.sql and Migrations (chronological).
|
||||
-- Safe-ish to re-run where noted (IF NOT EXISTS / information_schema checks).
|
||||
-- Demo seed (digit_motor_demo_seed.sql) omitted — schema only + retail reminder seed.
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-07-06 | Insurer claim form columns
|
||||
-- Source: app/Database/insurers_add_claim_form_column.sql
|
||||
-- -----------------------------------------------------------------------------
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'insurers'
|
||||
AND column_name = 'insurer_claim_form'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE insurers ADD COLUMN insurer_claim_form VARCHAR(255) NULL DEFAULT NULL AFTER insurer_logo',
|
||||
'SELECT ''insurers.insurer_claim_form already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'insurers'
|
||||
AND column_name = 'insurer_claim_form_original_name'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE insurers ADD COLUMN insurer_claim_form_original_name VARCHAR(255) NULL DEFAULT NULL AFTER insurer_claim_form',
|
||||
'SELECT ''insurers.insurer_claim_form_original_name already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-07-13 | BDS report performance indexes
|
||||
-- Source: app/Database/bds_report_performance_indexes.sql
|
||||
-- -----------------------------------------------------------------------------
|
||||
SET @idx := (
|
||||
SELECT COUNT(1) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'policy_transaction'
|
||||
AND index_name = 'idx_pt_active_created'
|
||||
);
|
||||
SET @sql := IF(@idx = 0,
|
||||
'CREATE INDEX idx_pt_active_created ON policy_transaction (is_active, created_at)',
|
||||
'SELECT ''idx_pt_active_created already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idx := (
|
||||
SELECT COUNT(1) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'pt_co_share_details'
|
||||
AND index_name = 'idx_pcsd_pt_active'
|
||||
);
|
||||
SET @sql := IF(@idx = 0,
|
||||
'CREATE INDEX idx_pcsd_pt_active ON pt_co_share_details (pt_id, is_active)',
|
||||
'SELECT ''idx_pcsd_pt_active already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idx := (
|
||||
SELECT COUNT(1) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'co_share_stmt_details'
|
||||
AND index_name = 'idx_cssd_coshare_stmt_active'
|
||||
);
|
||||
SET @sql := IF(@idx = 0,
|
||||
'CREATE INDEX idx_cssd_coshare_stmt_active ON co_share_stmt_details (co_share_id, statement_id, is_active)',
|
||||
'SELECT ''idx_cssd_coshare_stmt_active already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idx := (
|
||||
SELECT COUNT(1) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'insurer_statements'
|
||||
AND index_name = 'idx_insq_active_month_invoice'
|
||||
);
|
||||
SET @sql := IF(@idx = 0,
|
||||
'CREATE INDEX idx_insq_active_month_invoice ON insurer_statements (is_active, month, invoice_status)',
|
||||
'SELECT ''idx_insq_active_month_invoice already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-07-21 / 2026-07-23 / 2026-07-30 | Digit Motor module tables (final schema)
|
||||
-- Sources: digit_motor_tables.sql, digit_motor_master_tables.sql
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `motor_token` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`environment` VARCHAR(20) NOT NULL DEFAULT 'staging',
|
||||
`access_token` TEXT NOT NULL,
|
||||
`refresh_token` TEXT,
|
||||
`expires_at` TIMESTAMP NOT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uq_motor_token_env` (`environment`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_quote` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`enquiry_id` VARCHAR(64) NOT NULL,
|
||||
`quote_number` VARCHAR(32) DEFAULT NULL,
|
||||
`application_id` VARCHAR(255) DEFAULT NULL,
|
||||
`policy_holder_type` VARCHAR(20) NOT NULL DEFAULT 'INDIVIDUAL',
|
||||
`insurance_product_code` VARCHAR(10) NOT NULL,
|
||||
`sub_insurance_product_code` VARCHAR(10) NOT NULL DEFAULT 'PB',
|
||||
`previous_insurer_code` SMALLINT DEFAULT NULL,
|
||||
`previous_policy_expiry_date` DATE DEFAULT NULL,
|
||||
`external_policy_number` VARCHAR(32) DEFAULT NULL,
|
||||
`is_ncb_transfer` TINYINT(1) DEFAULT 0,
|
||||
`start_date` DATE DEFAULT NULL,
|
||||
`end_date` DATE DEFAULT NULL,
|
||||
`pincode` VARCHAR(6) NOT NULL,
|
||||
`coverage_details` JSON DEFAULT NULL,
|
||||
`policyholder_details` JSON DEFAULT NULL,
|
||||
`premium` DECIMAL(12,2) DEFAULT NULL,
|
||||
`idv` DECIMAL(12,2) DEFAULT NULL,
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
|
||||
`created_by` INT DEFAULT NULL,
|
||||
`updated_by` INT DEFAULT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uq_motor_quote_enquiry` (`enquiry_id`),
|
||||
KEY `idx_motor_quote_status` (`status`),
|
||||
KEY `idx_motor_quote_quote_number` (`quote_number`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_vehicle` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`quote_id` BIGINT NOT NULL,
|
||||
`is_vehicle_new` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`vehicle_maincode` VARCHAR(30) NOT NULL,
|
||||
`license_plate_number` VARCHAR(12) NOT NULL,
|
||||
`vehicle_identification_number` VARCHAR(30) DEFAULT NULL,
|
||||
`engine_number` VARCHAR(30) DEFAULT NULL,
|
||||
`manufacture_date` DATE NOT NULL,
|
||||
`registration_date` DATE NOT NULL,
|
||||
`registration_authority` VARCHAR(10) DEFAULT NULL,
|
||||
`idv` DECIMAL(12,2) DEFAULT NULL,
|
||||
`default_idv` DECIMAL(12,2) DEFAULT NULL,
|
||||
`minimum_idv` DECIMAL(12,2) DEFAULT NULL,
|
||||
`maximum_idv` DECIMAL(12,2) DEFAULT NULL,
|
||||
UNIQUE KEY `uq_motor_vehicle_quote` (`quote_id`),
|
||||
CONSTRAINT `fk_motor_vehicle_quote` FOREIGN KEY (`quote_id`) REFERENCES `motor_quote`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_kyc` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`quote_id` BIGINT NOT NULL,
|
||||
`kyc_id` VARCHAR(64) DEFAULT NULL,
|
||||
`kyc_verification_status` VARCHAR(20) DEFAULT NULL,
|
||||
`reference_id` VARCHAR(64) DEFAULT NULL,
|
||||
`link` VARCHAR(512) DEFAULT NULL,
|
||||
`mismatch_type` VARCHAR(40) DEFAULT NULL,
|
||||
`id_verification_doc_type` VARCHAR(40) DEFAULT NULL,
|
||||
`address_verification_doc_type` VARCHAR(40) DEFAULT NULL,
|
||||
`mode` CHAR(1) DEFAULT 'O',
|
||||
`checked_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `idx_motor_kyc_quote` (`quote_id`),
|
||||
CONSTRAINT `fk_motor_kyc_quote` FOREIGN KEY (`quote_id`) REFERENCES `motor_quote`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_payment` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`quote_id` BIGINT NOT NULL,
|
||||
`application_id` VARCHAR(255) NOT NULL,
|
||||
`digit_payment_id` VARCHAR(64) DEFAULT NULL,
|
||||
`request_reference` VARCHAR(64) DEFAULT NULL,
|
||||
`payment_mode` VARCHAR(5) DEFAULT 'EB',
|
||||
`cancel_return_url` VARCHAR(512) DEFAULT NULL,
|
||||
`success_return_url` VARCHAR(512) DEFAULT NULL,
|
||||
`dispatcher_response` VARCHAR(512) DEFAULT NULL,
|
||||
`premium` DECIMAL(12,2) DEFAULT NULL,
|
||||
`payment_status` VARCHAR(20) DEFAULT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `idx_motor_payment_quote` (`quote_id`),
|
||||
CONSTRAINT `fk_motor_payment_quote` FOREIGN KEY (`quote_id`) REFERENCES `motor_quote`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_policy` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`quote_id` BIGINT NOT NULL,
|
||||
`policy_number` VARCHAR(32) DEFAULT NULL,
|
||||
`policy_status` VARCHAR(20) DEFAULT NULL,
|
||||
`schedule_path` VARCHAR(512) DEFAULT NULL,
|
||||
`proposal_path` VARCHAR(512) DEFAULT NULL,
|
||||
`response_code` VARCHAR(10) DEFAULT NULL,
|
||||
`response_message` VARCHAR(255) DEFAULT NULL,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uq_motor_policy_quote` (`quote_id`),
|
||||
CONSTRAINT `fk_motor_policy_quote` FOREIGN KEY (`quote_id`) REFERENCES `motor_quote`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_api_log` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`quote_id` BIGINT DEFAULT NULL,
|
||||
`integration_id` VARCHAR(20) DEFAULT NULL,
|
||||
`endpoint` VARCHAR(120) DEFAULT NULL,
|
||||
`request_body` JSON DEFAULT NULL,
|
||||
`response_body` JSON DEFAULT NULL,
|
||||
`http_status` SMALLINT DEFAULT NULL,
|
||||
`error_code` VARCHAR(10) DEFAULT NULL,
|
||||
`duration_ms` INT DEFAULT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `idx_motor_api_log_quote` (`quote_id`),
|
||||
KEY `idx_motor_api_log_created` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Digit Motor master / lookup tables
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_vehicle` (
|
||||
`vehicle_code` VARCHAR(30) NOT NULL,
|
||||
`make` VARCHAR(80) NOT NULL,
|
||||
`model` VARCHAR(120) NOT NULL,
|
||||
`variant` VARCHAR(120) DEFAULT NULL,
|
||||
`body_type` VARCHAR(60) DEFAULT NULL,
|
||||
`seating_capacity` SMALLINT DEFAULT NULL,
|
||||
`power` DECIMAL(10,2) DEFAULT NULL,
|
||||
`cubic_capacity` DECIMAL(10,2) DEFAULT NULL,
|
||||
`gross_vehicle_weight` DECIMAL(12,2) DEFAULT NULL,
|
||||
`fuel_type` VARCHAR(40) DEFAULT NULL,
|
||||
`no_of_wheels` TINYINT DEFAULT NULL,
|
||||
`abs` CHAR(1) DEFAULT NULL,
|
||||
`air_bags` SMALLINT DEFAULT NULL,
|
||||
`length_m` DECIMAL(10,3) DEFAULT NULL,
|
||||
`ex_showroom_price` DECIMAL(14,2) DEFAULT NULL,
|
||||
`price_year` SMALLINT DEFAULT NULL,
|
||||
`production_status` VARCHAR(60) DEFAULT NULL,
|
||||
`manufacturing` VARCHAR(40) DEFAULT NULL,
|
||||
`vehicle_type` VARCHAR(40) DEFAULT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`vehicle_code`),
|
||||
KEY `idx_mmv_make` (`make`),
|
||||
KEY `idx_mmv_make_model` (`make`, `model`),
|
||||
KEY `idx_mmv_make_model_variant` (`make`, `model`, `variant`),
|
||||
KEY `idx_mmv_active` (`is_active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_previous_insurer` (
|
||||
`insurer_code` VARCHAR(10) NOT NULL,
|
||||
`insurer_name` VARCHAR(180) NOT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`insurer_code`),
|
||||
KEY `idx_mmpi_name` (`insurer_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_product` (
|
||||
`product_code` VARCHAR(10) NOT NULL,
|
||||
`product_name` VARCHAR(120) NOT NULL,
|
||||
`vehicle_class` VARCHAR(10) DEFAULT NULL COMMENT '2W / 4W / CV',
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`product_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_sub_product` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`business_type` VARCHAR(20) NOT NULL COMMENT 'NEW / ROLLOVER',
|
||||
`product_label` VARCHAR(120) NOT NULL,
|
||||
`sub_product_code` VARCHAR(20) NOT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uq_mmsp` (`business_type`, `product_label`, `sub_product_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_pincode` (
|
||||
`pincode` VARCHAR(6) NOT NULL,
|
||||
`city` VARCHAR(120) DEFAULT NULL,
|
||||
`district` VARCHAR(120) DEFAULT NULL,
|
||||
`street` VARCHAR(180) DEFAULT NULL,
|
||||
`taluk` VARCHAR(120) DEFAULT NULL,
|
||||
`state_code` VARCHAR(10) DEFAULT NULL,
|
||||
`segment` VARCHAR(40) DEFAULT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`pincode`),
|
||||
KEY `idx_mmp_city` (`city`),
|
||||
KEY `idx_mmp_state` (`state_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_rto` (
|
||||
`rto_code` VARCHAR(10) NOT NULL,
|
||||
`city_state` VARCHAR(180) DEFAULT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`rto_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_ncb` (
|
||||
`ncb_code` VARCHAR(30) NOT NULL,
|
||||
`sort_order` SMALLINT NOT NULL DEFAULT 0,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`ncb_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_previous_policy_type` (
|
||||
`policy_type_code` VARCHAR(20) NOT NULL,
|
||||
`description` VARCHAR(120) DEFAULT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`policy_type_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_voluntary_deductible` (
|
||||
`deductible_code` VARCHAR(40) NOT NULL,
|
||||
`sort_order` SMALLINT NOT NULL DEFAULT 0,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`deductible_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_doc_type` (
|
||||
`doc_code` VARCHAR(10) NOT NULL,
|
||||
`doc_type` VARCHAR(60) NOT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`doc_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_nominee_relation` (
|
||||
`relation_code` VARCHAR(40) NOT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`relation_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_state` (
|
||||
`state_code` VARCHAR(10) NOT NULL,
|
||||
`state_name` VARCHAR(120) NOT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`state_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_addon_age_limit` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`addon_name` VARCHAR(120) NOT NULL,
|
||||
`age_limit_4w` VARCHAR(120) DEFAULT NULL,
|
||||
`age_limit_2w` VARCHAR(255) DEFAULT NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uq_mmaal_addon` (`addon_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `motor_master_import_log` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`master_key` VARCHAR(60) NOT NULL,
|
||||
`source_file` VARCHAR(255) DEFAULT NULL,
|
||||
`rows_upserted` INT NOT NULL DEFAULT 0,
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'OK',
|
||||
`message` TEXT DEFAULT NULL,
|
||||
`imported_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `idx_mmil_master` (`master_key`),
|
||||
KEY `idx_mmil_imported` (`imported_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Align existing Digit Motor tables if created from an earlier draft schema
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'motor_quote'
|
||||
AND column_name = 'policyholder_details'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE motor_quote ADD COLUMN policyholder_details JSON DEFAULT NULL AFTER coverage_details',
|
||||
'SELECT ''motor_quote.policyholder_details already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'motor_quote'
|
||||
AND column_name = 'application_id'
|
||||
AND character_maximum_length < 255
|
||||
),
|
||||
'ALTER TABLE motor_quote MODIFY COLUMN application_id VARCHAR(255) DEFAULT NULL',
|
||||
'SELECT ''motor_quote.application_id already wide enough'' AS info'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'motor_payment'
|
||||
AND column_name = 'application_id'
|
||||
AND character_maximum_length < 255
|
||||
),
|
||||
'ALTER TABLE motor_payment MODIFY COLUMN application_id VARCHAR(255) NOT NULL',
|
||||
'SELECT ''motor_payment.application_id already wide enough'' AS info'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-07-28 | claim_report table
|
||||
-- Sources: claim_report.sql / Migration 2026-07-28-090700_CreateClaimReportTable
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `claim_report` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`tpa_id` INT UNSIGNED NULL,
|
||||
`client_id` INT UNSIGNED NULL,
|
||||
`client_policy_id` INT UNSIGNED NOT NULL,
|
||||
`file_id` INT UNSIGNED NULL,
|
||||
`ticket_id` BIGINT UNSIGNED NULL,
|
||||
`source_table` VARCHAR(64) NULL,
|
||||
`source_row_id` BIGINT UNSIGNED NULL,
|
||||
`claim_number` VARCHAR(191) NOT NULL,
|
||||
`emp_code` VARCHAR(100) NULL,
|
||||
`tpa_no` VARCHAR(100) NULL,
|
||||
`emp_id` INT UNSIGNED NULL,
|
||||
`insured_emp_id` INT UNSIGNED NULL,
|
||||
`claim_amount` VARCHAR(50) NULL,
|
||||
`approved_amount` VARCHAR(50) NULL,
|
||||
`incurred_amount` VARCHAR(50) NULL,
|
||||
`si_amt` VARCHAR(50) NULL,
|
||||
`tpa_claim_status` VARCHAR(191) NULL,
|
||||
`claim_status_id` INT UNSIGNED NULL,
|
||||
`tpa_claim_type` VARCHAR(100) NULL,
|
||||
`tpa_ailments` TEXT NULL,
|
||||
`doa` DATE NULL,
|
||||
`dod` DATE NULL,
|
||||
`date_of_intimat` DATE NULL,
|
||||
`settled_date` DATE NULL,
|
||||
`approved_date` DATE NULL,
|
||||
`claim_dump_date` DATETIME NULL,
|
||||
`hospital_name` VARCHAR(255) NULL,
|
||||
`hospital_city` VARCHAR(150) NULL,
|
||||
`hospital_state` VARCHAR(150) NULL,
|
||||
`hospital_pin_code` VARCHAR(20) NULL,
|
||||
`hospital_address` TEXT NULL,
|
||||
`gender` VARCHAR(30) NULL,
|
||||
`age` VARCHAR(20) NULL,
|
||||
`relation` VARCHAR(50) NULL,
|
||||
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`created_at` DATETIME NULL,
|
||||
`updated_at` DATETIME NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_claim_report_policy_claim` (`client_policy_id`, `claim_number`),
|
||||
KEY `idx_claim_report_policy_active` (`client_policy_id`, `is_active`),
|
||||
KEY `idx_claim_report_ticket` (`ticket_id`),
|
||||
KEY `idx_claim_report_source` (`source_table`, `source_row_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-08-01 / 2026-08-04 | Dependent add approval tracking
|
||||
-- Migrations:
|
||||
-- 2026-08-01-043000_AddDependentApprovalTrackingColumns
|
||||
-- 2026-08-04-064500_AddRejectReasonToEmployeesAndEmployeePolices
|
||||
-- 2026-08-04-065000_RenameApprovedByToProcessedBy
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
-- employees
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employees' AND column_name = 'emp_created_by'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employees ADD COLUMN emp_created_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Creator role: HR / USER'' AFTER emp_status',
|
||||
'SELECT ''employees.emp_created_by already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Rename approved_by -> processed_by when needed
|
||||
SET @sql := (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employees' AND column_name = 'approved_by'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employees' AND column_name = 'processed_by'
|
||||
),
|
||||
'ALTER TABLE employees CHANGE COLUMN approved_by processed_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Processor role: HR / ACM (approve or reject)''',
|
||||
'SELECT ''employees.processed_by rename skipped'' AS info'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employees' AND column_name = 'processed_by'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employees ADD COLUMN processed_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Processor role: HR / ACM (approve or reject)'' AFTER emp_created_by',
|
||||
'SELECT ''employees.processed_by already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employees' AND column_name = 'reject_reason'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employees ADD COLUMN reject_reason TEXT NULL DEFAULT NULL COMMENT ''Reason when dependent addition is rejected'' AFTER processed_by',
|
||||
'SELECT ''employees.reject_reason already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- employee_polices
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employee_polices' AND column_name = 'emp_policy_created_by'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employee_polices ADD COLUMN emp_policy_created_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Creator role: HR / USER'' AFTER status',
|
||||
'SELECT ''employee_polices.emp_policy_created_by already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := (
|
||||
SELECT IF(
|
||||
EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employee_polices' AND column_name = 'approved_by'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employee_polices' AND column_name = 'processed_by'
|
||||
),
|
||||
'ALTER TABLE employee_polices CHANGE COLUMN approved_by processed_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Processor role: HR / ACM (approve or reject)''',
|
||||
'SELECT ''employee_polices.processed_by rename skipped'' AS info'
|
||||
)
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employee_polices' AND column_name = 'processed_by'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employee_polices ADD COLUMN processed_by VARCHAR(50) NULL DEFAULT NULL COMMENT ''Processor role: HR / ACM (approve or reject)'' AFTER emp_policy_created_by',
|
||||
'SELECT ''employee_polices.processed_by already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'employee_polices' AND column_name = 'reject_reason'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE employee_polices ADD COLUMN reject_reason TEXT NULL DEFAULT NULL COMMENT ''Reason when dependent addition is rejected'' AFTER processed_by',
|
||||
'SELECT ''employee_polices.reject_reason already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 2026-08-11 | Retail policy renewal reminder
|
||||
-- Source: writable/sql/retail_policy_reminder_schema.sql
|
||||
-- -----------------------------------------------------------------------------
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'policy_transaction'
|
||||
AND column_name = 'renewal_status'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE policy_transaction ADD COLUMN renewal_status VARCHAR(50) NULL DEFAULT NULL AFTER renewal_date',
|
||||
'SELECT ''policy_transaction.renewal_status already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(1) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'notifications'
|
||||
AND column_name = 'config_json'
|
||||
);
|
||||
SET @sql := IF(@col = 0,
|
||||
'ALTER TABLE notifications ADD COLUMN config_json LONGTEXT NULL AFTER common_mail',
|
||||
'SELECT ''notifications.config_json already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
INSERT INTO `notifications` (
|
||||
`client_id`,
|
||||
`template_name`,
|
||||
`subject`,
|
||||
`mail_content`,
|
||||
`enabled`,
|
||||
`config_json`
|
||||
)
|
||||
SELECT
|
||||
NULL,
|
||||
'retail_reminder_mail',
|
||||
'Policy Renewal Reminder',
|
||||
'',
|
||||
1,
|
||||
'{"enabled":true,"daily":true,"days":"mon,tue,wed,thu,fri","clientTypes":[2],"dateField":"renewal_date","daysBefore":30,"includeClientEmail":true,"includeOverdue":false,"toEmails":"","ccEmails":"","bccEmails":"","fromMail":""}'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `notifications`
|
||||
WHERE `template_name` = 'retail_reminder_mail'
|
||||
AND (`client_id` IS NULL OR `client_id` = 0)
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- END
|
||||
-- Optional demo data (not included): app/Database/digit_motor_demo_seed.sql
|
||||
-- =============================================================================
|
||||
36
writable/sql/retail_policy_reminder_schema.sql
Normal file
36
writable/sql/retail_policy_reminder_schema.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- Retail Policy Reminder: schema + seed
|
||||
-- Run manually against the application database.
|
||||
|
||||
-- 1) policy_transaction.renewal_status
|
||||
ALTER TABLE `policy_transaction`
|
||||
ADD COLUMN `renewal_status` VARCHAR(50) NULL DEFAULT NULL
|
||||
AFTER `renewal_date`;
|
||||
|
||||
-- 2) notifications.config_json
|
||||
ALTER TABLE `notifications`
|
||||
ADD COLUMN `config_json` LONGTEXT NULL
|
||||
AFTER `common_mail`;
|
||||
|
||||
-- 3) Seed global retail reminder notification (skip if row already exists)
|
||||
INSERT INTO `notifications` (
|
||||
`client_id`,
|
||||
`template_name`,
|
||||
`subject`,
|
||||
`mail_content`,
|
||||
`enabled`,
|
||||
`config_json`
|
||||
)
|
||||
SELECT
|
||||
NULL,
|
||||
'retail_reminder_mail',
|
||||
'Policy Renewal Reminder',
|
||||
'',
|
||||
1,
|
||||
'{"enabled":true,"daily":true,"days":"mon,tue,wed,thu,fri","clientTypes":[2],"dateField":"renewal_date","daysBefore":30,"includeClientEmail":true,"includeOverdue":false,"toEmails":"","ccEmails":"","bccEmails":"","fromMail":""}'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `notifications`
|
||||
WHERE `template_name` = 'retail_reminder_mail'
|
||||
AND (`client_id` IS NULL OR `client_id` = 0)
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user