MERGE_TEST_DOCS&LIVEISSEUS
This commit is contained in:
commit
a86bbc4613
@ -15,6 +15,7 @@ use App\Controllers\MediAssistApiController;
|
||||
use App\Controllers\FhplApiController;
|
||||
use App\Controllers\VoloApiController;
|
||||
use App\Models\BatchFileModel;
|
||||
use App\Models\ClaimFilesModel;
|
||||
use App\Models\FileModel;
|
||||
use App\Helpers\TPADataCompareHelper;
|
||||
use App\Helpers\TPADataCompareHelper2;
|
||||
@ -25,6 +26,7 @@ class ApiServiceController extends BaseController
|
||||
// protected $format = 'json';
|
||||
protected $db;
|
||||
protected $employeePolicyModel;
|
||||
protected $claimFilesModel;
|
||||
protected $medi_assist_primary_key;
|
||||
protected $vidal_primary_key;
|
||||
protected $icici_primary_key;
|
||||
@ -36,6 +38,7 @@ class ApiServiceController extends BaseController
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
$this->employeePolicyModel = new EmployeePolicyModel();
|
||||
$this->claimFilesModel = new ClaimFilesModel();
|
||||
$this->medi_assist_primary_key = getenv('MEDI_ASSIST_PRIMARY_KEY_CONSTANT');
|
||||
$this->vidal_primary_key = getenv('VIDAL_PRIMARY_KEY_CONSTANT');
|
||||
$this->icici_primary_key = getenv('ICICI_PRIMARY_KEY_CONSTANT');
|
||||
@ -177,26 +180,20 @@ class ApiServiceController extends BaseController
|
||||
$voloApiController = new VoloApiController();
|
||||
$data['eCardDownload'] = $voloApiController->EcardRequest( $emp_code, $policy_no , $employee_policy[0]['tpa_id'] );
|
||||
|
||||
}else{
|
||||
|
||||
if($type == "download"){
|
||||
// direct download
|
||||
$data['eCardDownload'] = base_url('download-e-card/') . $employee_policy[0]['rand_string'].'/1';
|
||||
}else{
|
||||
if(isset($all_member) && !empty($all_member)){
|
||||
// view and download all members
|
||||
$data['eCardDownload'] = base_url('download-e-card/') . $employee_policy[0]['rand_string'].'/1/1';
|
||||
}else{
|
||||
// view and download single member
|
||||
$data['eCardDownload'] = base_url('download-e-card/') . $employee_policy[0]['rand_string'].'/0/1';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['message'] = "E-card generated";
|
||||
if(empty($data['eCardDownload'])){
|
||||
$data['message'] = "E-card not generated";
|
||||
}
|
||||
if (empty($data['eCardDownload'])) {
|
||||
$data['eCardDownload'] = $this->buildDefaultEcardDownloadUrl(
|
||||
$employee_policy[0]['rand_string'],
|
||||
$type,
|
||||
$all_member ?? null
|
||||
);
|
||||
}
|
||||
|
||||
$data['message'] = "E-card generated";
|
||||
if(empty($data['eCardDownload'])){
|
||||
$data['message'] = "E-card not generated";
|
||||
}
|
||||
|
||||
} else {
|
||||
$data['eCardDownload'] = null;
|
||||
@ -220,6 +217,19 @@ class ApiServiceController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDefaultEcardDownloadUrl(string $randString, string $type = 'download', $allMember = null): string
|
||||
{
|
||||
if ($type === 'download') {
|
||||
return base_url('download-e-card/') . $randString . '/1';
|
||||
}
|
||||
|
||||
if (!empty($allMember)) {
|
||||
return base_url('download-e-card/') . $randString . '/1/1';
|
||||
}
|
||||
|
||||
return base_url('download-e-card/') . $randString . '/0/1';
|
||||
}
|
||||
|
||||
// Get TPAID
|
||||
public function getTPAID()
|
||||
{
|
||||
@ -583,6 +593,13 @@ class ApiServiceController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $this->claimFilesModel->hasPdfFileForTicket($claimId)) {
|
||||
return $this->response->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'No PDF file found for this claim'
|
||||
]);
|
||||
}
|
||||
|
||||
$result = $this->pushClaims($claimId);
|
||||
|
||||
if ($result !== null && is_array($result)) {
|
||||
|
||||
@ -763,7 +763,7 @@ class ClientController extends AdminController
|
||||
public function saveDeposit()
|
||||
{
|
||||
|
||||
$rules = [
|
||||
$rules = [
|
||||
|
||||
'amount' => [
|
||||
'rules' => 'required|numeric|greater_than_equal_to[0]',
|
||||
@ -799,7 +799,7 @@ class ClientController extends AdminController
|
||||
],
|
||||
|
||||
'cd_ac_no' => [
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9\/\-_]+$/]',
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9\/\-_ ]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'CD Account number is required.',
|
||||
'regex_match' => 'CD Account number can only contain letters, numbers, hyphens(-), underscores(_), and slashes(/).',
|
||||
@ -874,12 +874,14 @@ class ClientController extends AdminController
|
||||
$record_date = null;
|
||||
}
|
||||
|
||||
$cd_master_data = $this->CDMasterModel->where('id', $cd_ac_pk)->where('is_active', 1)->first();
|
||||
|
||||
$data = [
|
||||
'amount' => $sanitized_post_data['amount'] ?? null,
|
||||
'sub_type_id' => $sanitized_post_data['sub_type_id'] ?? null,
|
||||
'client_id' => $sanitized_post_data['client_id'] ?? null,
|
||||
'client_policy_id' => null,
|
||||
'cd_ac_no' => $cd_ac_no ?? null,
|
||||
'cd_ac_no' => $cd_master_data['cd_ac_no'] ?? null,
|
||||
'cd_ac_pk' => $cd_ac_pk ?? null,
|
||||
'endorsement_no' => null,
|
||||
'insurer_id' => $sanitized_post_data['insurer_id'] ?? null,
|
||||
|
||||
@ -63,6 +63,14 @@ class DocsController extends BaseController
|
||||
['id' => 'tpa-recon', 'label' => 'TPA Recon', 'url' => 'docs/tpa-recon'],
|
||||
['id' => 'eb-rack-rate-config', 'label' => 'EB rack rate config', 'url' => 'docs/eb-rack-rate-config'],
|
||||
['id' => 'eb-rack-rate-calculation', 'label' => 'EB rack rate calculation', 'url' => 'docs/eb-rack-rate-calculation'],
|
||||
['id' => 'bds-insurer-statement', 'label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'],
|
||||
['id' => 'bds-commission', 'label' => 'BDS commission', 'url' => 'docs/bds-commission'],
|
||||
['id' => 'inception', 'label' => 'Inception', 'url' => 'docs/inception'],
|
||||
['id' => 'deletion', 'label' => 'Deletion', 'url' => 'docs/deletion'],
|
||||
['id' => 'correction', 'label' => 'Correction', 'url' => 'docs/correction'],
|
||||
['id' => 'si-enhancement', 'label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'],
|
||||
['id' => 'non-eb-opportunities', 'label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'],
|
||||
['id' => 'non-eb-claims', 'label' => 'Non-EB Claims', 'url' => 'docs/non-eb-claims'],
|
||||
],
|
||||
],
|
||||
[
|
||||
@ -451,6 +459,226 @@ class DocsController extends BaseController
|
||||
['label' => 'Related', 'href' => '#related'],
|
||||
],
|
||||
'prev' => ['label' => 'EB rack rate config', 'url' => 'docs/eb-rack-rate-config'],
|
||||
'next' => ['label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'],
|
||||
],
|
||||
|
||||
'bds-insurer-statement' => [
|
||||
'view' => 'docs/bds-insurer-statement',
|
||||
'title' => 'BDS Insurer statement',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '12 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Row validation logic', 'href' => '#row-validation-flowchart'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files-routes'],
|
||||
['label' => 'Statement list UI', 'href' => '#statement-list-ui'],
|
||||
['label' => 'uploadInsurerStatement','href' => '#upload-flow'],
|
||||
['label' => 'validateInsurerStatement', 'href' => '#validate-flow'],
|
||||
['label' => 'Validation steps', 'href' => '#validate-steps', 'level' => 'h3'],
|
||||
['label' => 'Validation error codes', 'href' => '#validation-errors', 'level' => 'h3'],
|
||||
['label' => 'updateInsurerStatement', 'href' => '#update-flow'],
|
||||
['label' => 'Excel column mapping', 'href' => '#excel-columns'],
|
||||
['label' => 'NHance source query', 'href' => '#nhance-source-query'],
|
||||
['label' => 'Invoice and delete', 'href' => '#invoice-and-delete'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#common-pitfalls'],
|
||||
['label' => 'Related BDS features', 'href' => '#related-bds'],
|
||||
],
|
||||
'prev' => ['label' => 'EB rack rate calculation', 'url' => 'docs/eb-rack-rate-calculation'],
|
||||
'next' => ['label' => 'BDS commission', 'url' => 'docs/bds-commission'],
|
||||
],
|
||||
|
||||
'bds-commission' => [
|
||||
'view' => 'docs/bds-commission',
|
||||
'title' => 'BDS commission',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '10 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Key files', 'href' => '#key-files'],
|
||||
['label' => 'Routes', 'href' => '#routes'],
|
||||
['label' => 'Commission file format', 'href' => '#commission-file-format'],
|
||||
['label' => 'Required columns', 'href' => '#required-columns', 'level' => 'h3'],
|
||||
['label' => 'Sample rows', 'href' => '#commission-file-samples', 'level' => 'h3'],
|
||||
['label' => 'Rule upload', 'href' => '#upload-flow'],
|
||||
['label' => 'Rules editor', 'href' => '#rules-editor'],
|
||||
['label' => 'Rule JSON shape', 'href' => '#rule-json'],
|
||||
['label' => 'Calculation API', 'href' => '#calculation-api'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'BDS Insurer statement', 'url' => 'docs/bds-insurer-statement'],
|
||||
'next' => ['label' => 'Inception', 'url' => 'docs/inception'],
|
||||
],
|
||||
|
||||
'inception' => [
|
||||
'view' => 'docs/inception',
|
||||
'title' => 'Inception',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '8 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files-routes'],
|
||||
['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'],
|
||||
['label' => 'Format validation', 'href' => '#format-validation'],
|
||||
['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'],
|
||||
['label' => 'reason JSON', 'href' => '#reason-json', 'level' => 'h3'],
|
||||
['label' => 'Data validation', 'href' => '#data-validation'],
|
||||
['label' => 'Data error codes', 'href' => '#data-errors', 'level' => 'h3'],
|
||||
['label' => 'Onboard preprocess', 'href' => '#preprocess'],
|
||||
['label' => 'Excel columns', 'href' => '#excel-columns'],
|
||||
['label' => 'Family row example', 'href' => '#family-example'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'BDS commission', 'url' => 'docs/bds-commission'],
|
||||
'next' => ['label' => 'Deletion', 'url' => 'docs/deletion'],
|
||||
],
|
||||
|
||||
'deletion' => [
|
||||
'view' => 'docs/deletion',
|
||||
'title' => 'Deletion',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '7 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files-routes'],
|
||||
['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'],
|
||||
['label' => 'Format validation', 'href' => '#format-validation'],
|
||||
['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'],
|
||||
['label' => 'Data validation', 'href' => '#data-validation'],
|
||||
['label' => 'employeeDisembark', 'href' => '#disembark'],
|
||||
['label' => 'TPA auto-deletion', 'href' => '#tpa-auto'],
|
||||
['label' => 'Excel columns', 'href' => '#excel-columns'],
|
||||
['label' => 'Row examples', 'href' => '#row-example'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'Inception', 'url' => 'docs/inception'],
|
||||
'next' => ['label' => 'Correction', 'url' => 'docs/correction'],
|
||||
],
|
||||
|
||||
'correction' => [
|
||||
'view' => 'docs/correction',
|
||||
'title' => 'Correction',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '7 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files-routes'],
|
||||
['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'],
|
||||
['label' => 'Format validation', 'href' => '#format-validation'],
|
||||
['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'],
|
||||
['label' => 'Data validation', 'href' => '#data-validation'],
|
||||
['label' => 'employeesCorrectionProcess', 'href' => '#correction-process'],
|
||||
['label' => 'Excel columns', 'href' => '#excel-columns'],
|
||||
['label' => 'Row examples', 'href' => '#row-example'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'Deletion', 'url' => 'docs/deletion'],
|
||||
'next' => ['label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'],
|
||||
],
|
||||
|
||||
'si-enhancement' => [
|
||||
'view' => 'docs/si-enhancement',
|
||||
'title' => 'SI Enhancement',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '7 min read',
|
||||
'toc' => [
|
||||
['label' => 'Overview', 'href' => '#overview'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files-routes'],
|
||||
['label' => 'Sync vs jobs', 'href' => '#sync-vs-jobs'],
|
||||
['label' => 'Format validation', 'href' => '#format-validation'],
|
||||
['label' => 'Format error codes', 'href' => '#format-errors', 'level' => 'h3'],
|
||||
['label' => 'Data validation', 'href' => '#data-validation'],
|
||||
['label' => 'employeesSIEnhanceProcess', 'href' => '#si-process'],
|
||||
['label' => 'Excel columns', 'href' => '#excel-columns'],
|
||||
['label' => 'Row examples', 'href' => '#row-example'],
|
||||
['label' => 'Developer steps', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'Correction', 'url' => 'docs/correction'],
|
||||
'next' => ['label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'],
|
||||
],
|
||||
|
||||
'non-eb-opportunities' => [
|
||||
'view' => 'docs/non-eb-opportunities',
|
||||
'title' => 'Non-EB Opportunities',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '18 min read',
|
||||
'toc' => [
|
||||
['label' => 'End-to-end flow', 'href' => '#overview'],
|
||||
['label' => 'Key files', 'href' => '#key-files'],
|
||||
['label' => 'Google Sheet config', 'href' => '#google-sheet-config'],
|
||||
['label' => 'App ↔ Drive flow', 'href' => '#gsheet-app-drive-flow', 'level' => 'h3'],
|
||||
['label' => 'Before RFQ / QCR', 'href' => '#gsheet-prerequisites', 'level' => 'h3'],
|
||||
['label' => 'Sheet ID per product', 'href' => '#gsheet-product-config', 'level' => 'h3'],
|
||||
['label' => 'App-level config', 'href' => '#gsheet-app-config', 'level' => 'h3'],
|
||||
['label' => 'Create opportunity', 'href' => '#create-edit'],
|
||||
['label' => 'Edit flow', 'href' => '#edit-flow'],
|
||||
['label' => 'List table actions', 'href' => '#list-actions'],
|
||||
['label' => 'Action endpoint map', 'href' => '#action-map', 'level' => 'h3'],
|
||||
['label' => 'RFQ', 'href' => '#step-rfq'],
|
||||
['label' => 'QCR', 'href' => '#step-qcr'],
|
||||
['label' => 'Mail actions', 'href' => '#step-mails'],
|
||||
['label' => 'Placement', 'href' => '#step-placement'],
|
||||
['label' => 'Status lifecycle', 'href' => '#status-lifecycle'],
|
||||
['label' => 'Controller reference', 'href' => '#controller-reference'],
|
||||
['label' => 'Developer checklist', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'SI Enhancement', 'url' => 'docs/si-enhancement'],
|
||||
'next' => ['label' => 'Non-EB Claims', 'url' => 'docs/non-eb-claims'],
|
||||
],
|
||||
|
||||
'non-eb-claims' => [
|
||||
'view' => 'docs/non-eb-claims',
|
||||
'title' => 'Non-EB Claims',
|
||||
'breadcrumb' => 'Features',
|
||||
'last_updated' => 'May 2026',
|
||||
'author' => 'Core Team',
|
||||
'read_time' => '16 min read',
|
||||
'toc' => [
|
||||
['label' => 'End-to-end flow', 'href' => '#overview'],
|
||||
['label' => 'Why policy type 50', 'href' => '#policy-type-50-default'],
|
||||
['label' => 'Design assumption', 'href' => '#policy-type-50-assumption', 'level' => 'h3'],
|
||||
['label' => '50 vs selected policy', 'href' => '#policy-type-50-vs-selected-policy', 'level' => 'h3'],
|
||||
['label' => 'Where 50 is hardcoded', 'href' => '#policy-type-50-where-hardcoded', 'level' => 'h3'],
|
||||
['label' => 'Different status set', 'href' => '#policy-type-50-future', 'level' => 'h3'],
|
||||
['label' => 'Key files and routes', 'href' => '#key-files'],
|
||||
['label' => 'Route map', 'href' => '#route-map', 'level' => 'h3'],
|
||||
['label' => 'Access control', 'href' => '#access'],
|
||||
['label' => 'List and filter', 'href' => '#list-flow'],
|
||||
['label' => 'Create and edit', 'href' => '#create-edit'],
|
||||
['label' => 'Status-driven sections', 'href' => '#status-sections'],
|
||||
['label' => 'Auto-mail', 'href' => '#auto-mail'],
|
||||
['label' => 'Mail template CRUD', 'href' => '#mail-template-crud'],
|
||||
['label' => 'Template fields', 'href' => '#template-fields', 'level' => 'h3'],
|
||||
['label' => 'Template actions', 'href' => '#template-actions', 'level' => 'h3'],
|
||||
['label' => 'Notes', 'href' => '#notes'],
|
||||
['label' => 'Documents', 'href' => '#documents'],
|
||||
['label' => 'Reports', 'href' => '#reports'],
|
||||
['label' => 'Data model', 'href' => '#data-model'],
|
||||
['label' => 'Controller reference', 'href' => '#controller-reference'],
|
||||
['label' => 'Developer checklist', 'href' => '#developer-steps'],
|
||||
['label' => 'Common pitfalls', 'href' => '#pitfalls'],
|
||||
],
|
||||
'prev' => ['label' => 'Non-EB Opportunities', 'url' => 'docs/non-eb-opportunities'],
|
||||
'next' => ['label' => 'Endpoints', 'url' => 'docs/endpoints'],
|
||||
],
|
||||
|
||||
|
||||
@ -517,7 +517,7 @@ class EmployeeController extends AdminController
|
||||
")
|
||||
->join('client_policy', 'client_policy.id = batch_files.client_policy_id')
|
||||
->join('insurers', 'client_policy.insurer_id = insurers.id')
|
||||
->join('tpa', 'client_policy.tpa_id = tpa.id')
|
||||
->join('tpa', 'client_policy.tpa_id = tpa.id', 'left')
|
||||
->join('client_branch', 'client_branch.id = batch_files.client_branch_id')
|
||||
->join('policy_type', 'policy_type.id = client_policy.policy_type_id')
|
||||
->join('clients', 'clients.id = client_policy.client_id')
|
||||
@ -574,7 +574,6 @@ class EmployeeController extends AdminController
|
||||
->orderBy('batch_files.id', 'desc')
|
||||
->find();
|
||||
|
||||
|
||||
// dd($data['fileList']);die();
|
||||
if ($this->request->getMethod() == "get") {
|
||||
$this->loadLayout('import_export', $data);
|
||||
@ -4175,6 +4174,7 @@ class EmployeeController extends AdminController
|
||||
}
|
||||
// print_r($allUpdates);die();
|
||||
// Do a single batch update for all families/members
|
||||
log_message('error','[WellnessOnboard] DB update data - ' . json_encode($allUpdates));
|
||||
if (!empty($allUpdates)) {
|
||||
// 2nd param is the key to match on; here it's 'id'
|
||||
$this->employeePolicyModel->updateBatch($allUpdates, 'id');
|
||||
@ -4255,25 +4255,36 @@ class EmployeeController extends AdminController
|
||||
$emp_data_wo_tpa_id = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id);
|
||||
|
||||
// Reconcile DB records against TPA records and classify records for rec_type updates.
|
||||
$consumedTpaIdsByEmpCode = [];
|
||||
foreach ($emp_data_wo_tpa_id as $db_key => $db_row) {
|
||||
$tpa_temp_data = $tpaByEmpCode[$db_row['emp_code']] ?? [];
|
||||
$match = $this->reconcileDbWithTpa($db_row, $tpa_temp_data);
|
||||
$empCode = (string) ($db_row['emp_code'] ?? '');
|
||||
$empId = (string) ($db_row['id'] ?? '');
|
||||
$empName = (string) ($db_row['name'] ?? '');
|
||||
$tpa_temp_data = $tpaByEmpCode[$empCode] ?? [];
|
||||
$excludeTpaIds = $consumedTpaIdsByEmpCode[$empCode] ?? [];
|
||||
$match = $this->reconcileDbWithTpa($db_row, $tpa_temp_data, $excludeTpaIds);
|
||||
$emp_data_wo_tpa_id[$db_key]['match'] = $match;
|
||||
|
||||
if (($match['status'] ?? '') === 'matched') {
|
||||
$matchedTpaId = (int) ($match['tpa_record']['id'] ?? 0);
|
||||
// echo 'matched' . $matchedTpaId . ' for emp_code ' . $empCode . PHP_EOL . '<br>';
|
||||
if ($matchedTpaId > 0) {
|
||||
$consumedTpaIdsByEmpCode[$empCode][] = $matchedTpaId;
|
||||
// If compare-fields list has differences, the row must be reviewed.
|
||||
// Otherwise keep it as matched.
|
||||
$recTypeById[$matchedTpaId] = empty($match['not_matching']) ? 'matched' : 'need_to_review';
|
||||
}
|
||||
} else {
|
||||
// No relation-level match found for this DB member.
|
||||
// Mark all candidate TPA rows for the same emp_code as review-required.
|
||||
// echo 'not matched' . ' for emp_code ' . $empCode . PHP_EOL . ' - ' . $empId . ' - ' . $empName . ' - ' . '<br>';
|
||||
// No match for this DB member: flag same-relation TPA rows not already paired.
|
||||
$dbRel = strtolower(trim((string) ($db_row['relationship'] ?? '')));
|
||||
foreach ($tpa_temp_data as $candidate) {
|
||||
if (strtolower(trim((string) ($candidate['relation'] ?? ''))) !== $dbRel) {
|
||||
continue;
|
||||
}
|
||||
$candidateId = (int) ($candidate['id'] ?? 0);
|
||||
if ($candidateId > 0) {
|
||||
$recTypeById[$candidateId] = 'need_to_review';
|
||||
if ($candidateId > 0 && !in_array($candidateId, $excludeTpaIds, true)) {
|
||||
$recTypeById[$candidateId] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4306,76 +4317,75 @@ class EmployeeController extends AdminController
|
||||
$db->transComplete();
|
||||
}
|
||||
|
||||
// this will match tpa api data with emp/emp policy table and update ref in tpa api data once
|
||||
$this->reconTpaApiDataWithEmployeepolicies(['file_id' => $file_id]);
|
||||
// this will match tpa api data with emp/emp policy table and update ref in tpa api data once
|
||||
$this->reconTpaApiDataWithEmployeepolicies(['file_id' => $file_id]);
|
||||
} else {
|
||||
// echo 'else';die;
|
||||
// Cached mode:
|
||||
// Read previously classified rows from rec_type, keep response shape compatible
|
||||
// with existing UI/export (`mismatch_data` still contains DB row + match payload).
|
||||
// `not_in_nhance` stays sourced from persisted rec_type snapshot.
|
||||
// `mismatch_data` must match compute-mode shape: every policy row with
|
||||
// tpa_id NULL gets reconcileDbWithTpa against *all* active TPA rows for
|
||||
// that emp_code (not only rows flagged need_to_review), otherwise second+
|
||||
// loads drop rows / lose not_matching vs the first compute pass.
|
||||
$not_in_nhance = $tpaApiDataModel->select('*')
|
||||
->where('is_active', 1)
|
||||
->where('file_id', $file_id)
|
||||
->where('rec_type', 'not_in_nhance')
|
||||
->findAll();
|
||||
|
||||
$needToReviewRows = $tpaApiDataModel->select('*')
|
||||
->where('is_active', 1)
|
||||
$allActiveTpaRowsCached = $tpaApiDataModel->select('*')
|
||||
->where('file_id', $file_id)
|
||||
->where('rec_type', 'need_to_review')
|
||||
->where('is_active', 1)
|
||||
->findAll();
|
||||
|
||||
$needToReviewByEmpCode = [];
|
||||
foreach ($needToReviewRows as $row) {
|
||||
$needToReviewByEmpCode[$row['emp_code']][] = $row;
|
||||
$tpaByEmpCodeCached = [];
|
||||
foreach ($allActiveTpaRowsCached as $tpaRow) {
|
||||
$tpaByEmpCodeCached[$tpaRow['emp_code']][] = $tpaRow;
|
||||
}
|
||||
|
||||
$baseRows = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id);
|
||||
foreach ($baseRows as $db_row) {
|
||||
$candidates = $needToReviewByEmpCode[$db_row['emp_code']] ?? [];
|
||||
if (empty($candidates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedTpa = null;
|
||||
foreach ($candidates as $candidate) {
|
||||
// Prefer same-relation row to mimic reconcileDbWithTpa relation matching.
|
||||
if (strtolower((string) ($candidate['relation'] ?? '')) === strtolower((string) ($db_row['relationship'] ?? ''))) {
|
||||
$selectedTpa = $candidate;
|
||||
break;
|
||||
$emp_data_wo_tpa_id = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id);
|
||||
$consumedTpaIdsByEmpCodeCached = [];
|
||||
foreach ($emp_data_wo_tpa_id as $db_key => $db_row) {
|
||||
$empCode = (string) ($db_row['emp_code'] ?? '');
|
||||
$tpa_temp_data = $tpaByEmpCodeCached[$empCode] ?? [];
|
||||
$excludeTpaIds = $consumedTpaIdsByEmpCodeCached[$empCode] ?? [];
|
||||
$match = $this->reconcileDbWithTpa($db_row, $tpa_temp_data, $excludeTpaIds);
|
||||
$emp_data_wo_tpa_id[$db_key]['match'] = $match;
|
||||
if (($match['status'] ?? '') === 'matched') {
|
||||
$matchedTpaId = (int) ($match['tpa_record']['id'] ?? 0);
|
||||
if ($matchedTpaId > 0) {
|
||||
$consumedTpaIdsByEmpCodeCached[$empCode][] = $matchedTpaId;
|
||||
}
|
||||
}
|
||||
if ($selectedTpa === null) {
|
||||
$selectedTpa = $candidates[0];
|
||||
}
|
||||
|
||||
$db_row['match'] = [
|
||||
'status' => 'matched',
|
||||
'tpa_record' => $selectedTpa,
|
||||
'not_matching' => [],
|
||||
];
|
||||
$emp_data_wo_tpa_id[] = $db_row;
|
||||
}
|
||||
}
|
||||
|
||||
// print_rr($emp_data_wo_tpa_id);die();
|
||||
|
||||
|
||||
// Intentionally keep `not_in_tpa` live from current join/query logic
|
||||
// (as requested) and do not source it from rec_type snapshot.
|
||||
$tpa_emp_codes = $tpaApiDataModel->select('emp_code')
|
||||
$tpa_emp_codes = $tpaApiDataModel->select('ref')
|
||||
->where('file_id', $file_id)
|
||||
->where('is_active', 1)
|
||||
->groupBy('emp_code')
|
||||
->groupBy('ref')
|
||||
->findAll();
|
||||
$tpa_emp_codes = array_column($tpa_emp_codes, 'emp_code');
|
||||
$not_in_tpa = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id, $tpa_emp_codes);
|
||||
|
||||
// $tpa_emp_codes = array_column($tpa_emp_codes, 'ref');
|
||||
$tpa_ref = array_column($tpa_emp_codes, 'ref');
|
||||
$not_in_tpa = $this->employeePolicyModel->getTPADataVariationReport($client_id, $client_policy_id, $file_id, $tpa_ref);
|
||||
|
||||
// Format dates for UI/export (dd/mm/yyyy). Applied after reconciliation so
|
||||
// reconcileDbWithTpa can still compare raw Y-m-d values from the database.
|
||||
$this->formatVariationReportDataForDisplay($not_in_tpa, $not_in_nhance, $emp_data_wo_tpa_id);
|
||||
|
||||
if (!empty($not_in_tpa) || !empty($not_in_nhance) || !empty($emp_data_wo_tpa_id)) {
|
||||
$not_in_nhance_button_enable_status = false;
|
||||
if(count($not_in_tpa))
|
||||
{
|
||||
foreach($not_in_nhance as $nih)
|
||||
{
|
||||
if($nih['ref'] === '' || empty($nih['ref']))
|
||||
{
|
||||
if (count($not_in_nhance)) {
|
||||
foreach ($not_in_nhance as $nih) {
|
||||
$hasRefKey = array_key_exists('ref', $nih);
|
||||
$ref = $hasRefKey ? $nih['ref'] : null;
|
||||
$refIsEmpty = $ref === null || $ref === '';
|
||||
if ($refIsEmpty) {
|
||||
$not_in_nhance_button_enable_status = true;
|
||||
break;
|
||||
}
|
||||
@ -4410,6 +4420,21 @@ class EmployeeController extends AdminController
|
||||
$not_in_nhance_deletion_count
|
||||
);
|
||||
|
||||
$not_matched_count = 0;
|
||||
$not_matched_data = [];
|
||||
foreach ($emp_data_wo_tpa_id as $key => $row) {
|
||||
$notMatching = $row['match']['not_matching'] ?? [];
|
||||
if (isset($row['match']) && is_array($notMatching) && $notMatching !== []) {
|
||||
$not_matched_count++;
|
||||
$not_matched_data[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$need_to_review_proceed_button_text = sprintf(
|
||||
'Proceed - Need to Review ( %d )',
|
||||
$not_matched_count
|
||||
);
|
||||
|
||||
$response = [
|
||||
'not_in_tpa' => $not_in_tpa,
|
||||
'not_in_nhance' => $not_in_nhance,
|
||||
@ -4418,6 +4443,9 @@ class EmployeeController extends AdminController
|
||||
'not_in_nhance_inception_count' => $not_in_nhance_inception_count,
|
||||
'not_in_nhance_deletion_count' => $not_in_nhance_deletion_count,
|
||||
'not_in_nhance_proceed_button_text' => $not_in_nhance_proceed_button_text,
|
||||
'not_matched_count' => $not_matched_count,
|
||||
'not_matched_data' => $not_matched_data,
|
||||
'need_to_review_proceed_button_text' => $need_to_review_proceed_button_text,
|
||||
];
|
||||
|
||||
if ($type === 'view') {
|
||||
@ -4889,10 +4917,12 @@ class EmployeeController extends AdminController
|
||||
}
|
||||
|
||||
$mismatchRows = [];
|
||||
$consumedTpaIdsByEmpCode = [];
|
||||
|
||||
foreach ($dbRows as $dbRow) {
|
||||
$empCode = (string) ($dbRow['emp_code'] ?? '');
|
||||
$tpaRows = $TpaApiDataModel->select('*')
|
||||
->where('emp_code', $dbRow['emp_code'])
|
||||
->where('emp_code', $empCode)
|
||||
->where('file_id', $batchFileId)
|
||||
->where('is_active', 1)
|
||||
->findAll();
|
||||
@ -4901,7 +4931,14 @@ class EmployeeController extends AdminController
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = $this->reconcileDbWithTpa($dbRow, $tpaRows);
|
||||
$excludeTpaIds = $consumedTpaIdsByEmpCode[$empCode] ?? [];
|
||||
$match = $this->reconcileDbWithTpa($dbRow, $tpaRows, $excludeTpaIds);
|
||||
if (($match['status'] ?? '') === 'matched') {
|
||||
$matchedTpaId = (int) ($match['tpa_record']['id'] ?? 0);
|
||||
if ($matchedTpaId > 0) {
|
||||
$consumedTpaIdsByEmpCode[$empCode][] = $matchedTpaId;
|
||||
}
|
||||
}
|
||||
|
||||
if (($match['status'] ?? '') !== 'matched') {
|
||||
continue;
|
||||
@ -5190,9 +5227,12 @@ class EmployeeController extends AdminController
|
||||
$rowsSkippedNoDiff = 0;
|
||||
$rowsSkippedNoEmployee = 0;
|
||||
|
||||
$consumedTpaIdsByEmpCode = [];
|
||||
|
||||
foreach ($dbRows as $dbRow) {
|
||||
$empCode = (string) ($dbRow['emp_code'] ?? '');
|
||||
$tpaRows = $TpaApiDataModel->select('*')
|
||||
->where('emp_code', $dbRow['emp_code'] ?? '')
|
||||
->where('emp_code', $empCode)
|
||||
->where('file_id', $batchFileId)
|
||||
->where('is_active', 1)
|
||||
->findAll();
|
||||
@ -5201,7 +5241,8 @@ class EmployeeController extends AdminController
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = $this->reconcileDbWithTpa($dbRow, $tpaRows);
|
||||
$excludeTpaIds = $consumedTpaIdsByEmpCode[$empCode] ?? [];
|
||||
$match = $this->reconcileDbWithTpa($dbRow, $tpaRows, $excludeTpaIds);
|
||||
|
||||
if (($match['status'] ?? '') !== 'matched') {
|
||||
continue;
|
||||
@ -5210,6 +5251,11 @@ class EmployeeController extends AdminController
|
||||
$tpaRecord = $match['tpa_record'] ?? [];
|
||||
$notMatching = $match['not_matching'] ?? [];
|
||||
|
||||
$matchedTpaId = (int) ($tpaRecord['id'] ?? 0);
|
||||
if ($matchedTpaId > 0) {
|
||||
$consumedTpaIdsByEmpCode[$empCode][] = $matchedTpaId;
|
||||
}
|
||||
|
||||
if (!is_array($notMatching) || $notMatching === []) {
|
||||
$rowsSkippedNoDiff++;
|
||||
continue;
|
||||
@ -5524,57 +5570,169 @@ class EmployeeController extends AdminController
|
||||
];
|
||||
}
|
||||
|
||||
public function reconcileDbWithTpa(array $db, array $tpaRows): array
|
||||
/**
|
||||
* Convert a single variation-report date value to dd/mm/yyyy for display.
|
||||
*/
|
||||
private function formatVariationReportDateField($value): string
|
||||
{
|
||||
// Name normalization
|
||||
$normalizeName = function ($name) {
|
||||
if ($value === null || $value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$formatted = change_date_format((string) $value, null, 'd/m/Y');
|
||||
|
||||
return ($formatted !== null && $formatted !== '') ? (string) $formatted : (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format known date columns on one variation-report row (TPA or DB).
|
||||
*/
|
||||
private function formatVariationReportRowDates(array $row, array $dateFields = ['dob', 'doj']): array
|
||||
{
|
||||
foreach ($dateFields as $field) {
|
||||
if (!array_key_exists($field, $row)) {
|
||||
continue;
|
||||
}
|
||||
if ($row[$field] === null || $row[$field] === '') {
|
||||
continue;
|
||||
}
|
||||
$row[$field] = $this->formatVariationReportDateField($row[$field]);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply dd/mm/yyyy formatting to all variation-report payloads returned to the UI/export.
|
||||
*/
|
||||
private function formatVariationReportDataForDisplay(
|
||||
array &$notInTpa,
|
||||
array &$notInNhance,
|
||||
array &$mismatchData
|
||||
): void {
|
||||
foreach ($notInTpa as $idx => $row) {
|
||||
$notInTpa[$idx] = $this->formatVariationReportRowDates($row);
|
||||
}
|
||||
|
||||
foreach ($notInNhance as $idx => $row) {
|
||||
$notInNhance[$idx] = $this->formatVariationReportRowDates($row);
|
||||
}
|
||||
|
||||
foreach ($mismatchData as $idx => $row) {
|
||||
$row = $this->formatVariationReportRowDates($row);
|
||||
if (isset($row['match']['tpa_record']) && is_array($row['match']['tpa_record'])) {
|
||||
$row['match']['tpa_record'] = $this->formatVariationReportRowDates($row['match']['tpa_record']);
|
||||
}
|
||||
$mismatchData[$idx] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair one Nhance policy row with a TPA API row for the same emp_code.
|
||||
*
|
||||
* When multiple dependents share a relation (e.g. two Sons), candidates are scored
|
||||
* on name / DOB / gender and the best unique match wins. Already-paired TPA ids
|
||||
* (same emp_code) can be passed via $excludeTpaIds so each TPA row maps once.
|
||||
*/
|
||||
public function reconcileDbWithTpa(array $db, array $tpaRows, array $excludeTpaIds = []): array
|
||||
{
|
||||
$normalizeName = static function ($name) {
|
||||
return strtolower(
|
||||
preg_replace('/[.\s_]+/', '', trim($name))
|
||||
preg_replace('/[.\s_]+/', '', trim((string) $name))
|
||||
);
|
||||
};
|
||||
|
||||
foreach ($tpaRows as $tpa) {
|
||||
$normalizeRelation = static function ($relation) {
|
||||
return strtolower(trim((string) $relation));
|
||||
};
|
||||
|
||||
// 1️⃣ emp_code + relation must match
|
||||
if (
|
||||
// ($db['emp_code'] ?? '') !== ($tpa['emp_code'] ?? '') ||
|
||||
strtolower($db['relationship']) !== strtolower($tpa['relation'])
|
||||
) {
|
||||
$dbRel = $normalizeRelation($db['relationship'] ?? '');
|
||||
|
||||
$candidates = [];
|
||||
foreach ($tpaRows as $tpa) {
|
||||
if (isset($tpa['match']['status']) && $tpa['match']['status'] === 'matched') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2️⃣ Field comparison
|
||||
$diff = [];
|
||||
|
||||
if (
|
||||
($db['name'] ?? '') !==
|
||||
($tpa['name'] ?? '')
|
||||
) {
|
||||
$diff[] = 'name';
|
||||
$tpaId = (int) ($tpa['id'] ?? 0);
|
||||
if ($tpaId > 0 && in_array($tpaId, $excludeTpaIds, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($db['dob'] ?? '') !== ($tpa['dob'] ?? '')) {
|
||||
$diff[] = 'dob';
|
||||
if ($normalizeRelation($tpa['relation'] ?? '') !== $dbRel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
strtoupper($db['gender'] ?? '') !==
|
||||
strtoupper($tpa['gender'] ?? '')
|
||||
) {
|
||||
$diff[] = 'gender';
|
||||
}
|
||||
|
||||
// 3️⃣ Match found
|
||||
return [
|
||||
'status' => 'matched',
|
||||
'tpa_record' => $tpa,
|
||||
'not_matching' => $diff // empty = perfect match
|
||||
];
|
||||
$candidates[] = $tpa;
|
||||
}
|
||||
|
||||
if ($candidates === []) {
|
||||
return ['status' => 'no_match'];
|
||||
}
|
||||
|
||||
$scoreCandidate = static function (array $tpa) use ($db, $normalizeName) {
|
||||
$score = 0;
|
||||
if ($normalizeName($db['name'] ?? '') === $normalizeName($tpa['name'] ?? '')) {
|
||||
$score += 4;
|
||||
}
|
||||
if ((string) ($db['dob'] ?? '') === (string) ($tpa['dob'] ?? '')) {
|
||||
$score += 2;
|
||||
}
|
||||
if (strtoupper(trim((string) ($db['gender'] ?? ''))) === strtoupper(trim((string) ($tpa['gender'] ?? '')))) {
|
||||
$score += 1;
|
||||
}
|
||||
|
||||
return $score;
|
||||
};
|
||||
|
||||
$bestTpa = null;
|
||||
$bestScore = -1;
|
||||
foreach ($candidates as $tpa) {
|
||||
$score = $scoreCandidate($tpa);
|
||||
if ($score > $bestScore) {
|
||||
$bestScore = $score;
|
||||
$bestTpa = $tpa;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestTpa === null) {
|
||||
return ['status' => 'no_match'];
|
||||
}
|
||||
|
||||
// Multiple Son/Daughter rows: require a unique tie-breaker (name or DOB).
|
||||
if (count($candidates) > 1) {
|
||||
$topCount = 0;
|
||||
foreach ($candidates as $tpa) {
|
||||
if ($scoreCandidate($tpa) === $bestScore) {
|
||||
$topCount++;
|
||||
}
|
||||
}
|
||||
if ($topCount > 1 || $bestScore < 2) {
|
||||
return ['status' => 'no_match'];
|
||||
}
|
||||
}
|
||||
|
||||
$diff = [];
|
||||
|
||||
if ($normalizeName($db['name'] ?? '') !== $normalizeName($bestTpa['name'] ?? '')) {
|
||||
$diff[] = 'name';
|
||||
}
|
||||
|
||||
if ((string) ($db['dob'] ?? '') !== (string) ($bestTpa['dob'] ?? '')) {
|
||||
$diff[] = 'dob';
|
||||
}
|
||||
|
||||
if (
|
||||
strtoupper(trim((string) ($db['gender'] ?? ''))) !==
|
||||
strtoupper(trim((string) ($bestTpa['gender'] ?? '')))
|
||||
) {
|
||||
$diff[] = 'gender';
|
||||
}
|
||||
|
||||
// 4️⃣ No match found
|
||||
return [
|
||||
'status' => 'no_match'
|
||||
'status' => 'matched',
|
||||
'tpa_record' => $bestTpa,
|
||||
'not_matching' => $diff,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -3785,14 +3785,6 @@ class EmployeeRestController extends AdminController
|
||||
|
||||
$this->handleCliamFiles($file_data, $ticket_id, $ticket_message_id);
|
||||
|
||||
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||
try {
|
||||
helper('merge_pdf');
|
||||
merge_ticket_pdfs((int) $ticket_id);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'EmployeeRestController::initiateClaim | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$mail_sent_status = ($this->ticketController->sendAutoMailTrigger($ticket_id));
|
||||
if (gettype($mail_sent_status) == 'array') {
|
||||
$message = 'Claim Iniated Successfully';
|
||||
@ -3931,6 +3923,15 @@ class EmployeeRestController extends AdminController
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($insert_ids)) {
|
||||
try {
|
||||
helper('merge_pdf');
|
||||
merge_ticket_pdfs((int) $ticket_id);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'EmployeeRestController::handleCliamFiles | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$count = $this->ticketMaster->where('id', $ticket_id)->where('is_active', 1)->where('tpa_claim_push_reference_no IS NULL')->countAllResults();
|
||||
log_message('error', 'Ticket validation | Ticket ID: ' . $ticket_id . ' | Matching active tickets with NULL TPA reference: ' . $count);
|
||||
if ($count > 0 && $pdf_exist_in_the_file && $tpa_claim_push == true) {
|
||||
@ -5452,14 +5453,6 @@ class EmployeeRestController extends AdminController
|
||||
$result = $this->handleCliamFiles($file_data, $ticket_id, null, false, true);
|
||||
|
||||
if (! empty($result)) {
|
||||
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||
try {
|
||||
helper('merge_pdf');
|
||||
merge_ticket_pdfs((int) $ticket_id);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'EmployeeRestController::uploadIRDocs | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// $this->ticketMaster->where('id', $ticket_id)->set(['required_docs', $required_docs])->update();
|
||||
db_connect()->query(
|
||||
"UPDATE ticket_master SET required_docs = ? WHERE id = ?",
|
||||
|
||||
@ -554,30 +554,32 @@ class FhplApiController extends BaseController
|
||||
foreach ($employeePolicyData as $policy) {
|
||||
$hasMatchForThisPolicy = false;
|
||||
foreach ($allMembers as $m) {
|
||||
$apiRelation = map_relationship(trim($m['RELATION'] ?? ''));
|
||||
|
||||
if (
|
||||
strtolower(trim($policy['name'])) === strtolower(trim($m['BENEFICIARY_NAME'] ?? '')) &&
|
||||
($policy['emp_code'] ?? '') == ($m['EMPLOYEE_ID'] ?? '') &&
|
||||
strtolower($policy['relationship']) === strtolower($m['RELATION'] ?? '')
|
||||
strtolower(trim((string) ($policy['name'] ?? ''))) === strtolower(trim((string) ($m['BENEFICIARY_NAME'] ?? '')))
|
||||
&& trim((string) ($policy['emp_code'] ?? '')) === trim((string) ($m['EMPLOYEE_ID'] ?? ''))
|
||||
&& strtolower(trim((string) ($policy['relationship'] ?? ''))) === strtolower(trim((string) $apiRelation))
|
||||
) {
|
||||
|
||||
$hasMatchForThisPolicy = true;
|
||||
|
||||
|
||||
$sql = "UPDATE employee_polices SET tpa_id = ? WHERE id = ?";
|
||||
$this->db->query($sql, [$m['MEMBERSHIP_NO'], $policy['emp_policy_id']]);
|
||||
$tpaId = $m['MEMBERSHIP_NO'] ?? $m['MEMBERSHIP_NO'] ?? null;
|
||||
$sql = 'UPDATE employee_polices SET tpa_id = ? WHERE id = ?';
|
||||
$this->db->query($sql, [$tpaId, $policy['emp_policy_id']]);
|
||||
|
||||
// for e-card send
|
||||
if(strtolower(trim($policy['relationship'])) == 'self'){
|
||||
if (strtolower(trim((string) ($policy['relationship'] ?? ''))) === 'self') {
|
||||
$employee_policy_ids[] = $policy['emp_policy_id'];
|
||||
}
|
||||
|
||||
|
||||
if ($this->db->affectedRows() > 0) {
|
||||
$updated++;
|
||||
log_message('error', "FHPL - TPA ID Pull Updated tpa_id={$m['MEMBERSHIP_NO']} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
$updated++;
|
||||
log_message('error', "FHPL - TPA ID Pull Updated tpa_id={$tpaId} for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
} else {
|
||||
log_message('error', "FHPL - TPA ID Pull No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
log_message('error', "FHPL - TPA ID Pull No update (already set or not matched) for emp_policy_id={$policy['emp_policy_id']} policy={$policyNo}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1070,7 +1072,7 @@ class FhplApiController extends BaseController
|
||||
'gender' => format_gender_v2($row['GENDER'] ?? null),
|
||||
'self' => strtolower($row['RELATION'] ?? '') === 'self' ? 1 : 0,
|
||||
|
||||
'tpa_id' => trim($row['TPA_TPADETAIL_ID'] ?? null),
|
||||
'tpa_id' => trim($row['MEMBERSHIP_NO'] ?? null),
|
||||
'age' => is_numeric($row['AGE'] ?? null) ? (int) $row['AGE'] : null,
|
||||
|
||||
'is_active' => 1,
|
||||
|
||||
@ -368,8 +368,10 @@ class LeadsController extends BaseController
|
||||
$id = $this->request->getPost('id');
|
||||
$actual_lead_id = $this->request->getPost('actual_lead_id');
|
||||
$actual_lead_id = ! empty($actual_lead_id) ? $actual_lead_id : null;
|
||||
$postData = $this->request->getPost();
|
||||
$data = $this->prepareLeadData();
|
||||
|
||||
$this->normalizeClaimHistoryPostData();
|
||||
$postData = $this->request->getPost();
|
||||
$data = $this->prepareLeadData();
|
||||
|
||||
|
||||
$rules = [
|
||||
@ -526,47 +528,65 @@ class LeadsController extends BaseController
|
||||
];
|
||||
|
||||
if ((int) $this->request->getPost('claim_history') === 1) {
|
||||
$rules['first_year.*'] = [
|
||||
'rules' => 'required|regex_match[/^\d{4}-\d{4}$/]',
|
||||
'errors' => ['required' => 'Claim Year is required for all entries.', 'regex_match' => 'Year must be in format YYYY-YYYY.'],
|
||||
];
|
||||
$rules['first_policy_type_.*'] = [
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9 _-]+$/]',
|
||||
'errors' => ['required' => 'Policy Type is required in Claim History.',
|
||||
'regex_match' => 'Policy Type only letters, numbers, space, hyphens and underscores are allowed',
|
||||
],
|
||||
];
|
||||
$rules['first_date_of_loss_.*'] = [
|
||||
'rules' => 'required|regex_match[/^[0-9]{2}-[0-9]{2}-[0-9]{4}$/]',
|
||||
'errors' => ['required' => 'Date of Loss is required.',
|
||||
'regex_match' => 'Date of Loss must be inValid format.'],
|
||||
];
|
||||
$rules['first_cause_of_loss.*'] = [
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9 _-]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Cause of Loss is required.',
|
||||
'regex_match' => 'Cause of Loss only letters, numbers, space, hyphens and underscores are allowed',
|
||||
],
|
||||
];
|
||||
$rules['first_claim_amount.*'] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Claim Amount is required.',
|
||||
'numeric' => 'Claim Amount must be a number.',
|
||||
],
|
||||
];
|
||||
$rules['first_settled_amount.*'] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => ['required' => 'Settled Amount is required.',
|
||||
'numeric' => 'Settled Amount must be a number.'],
|
||||
];
|
||||
$rules['first_claim_status.*'] = [
|
||||
'rules' => 'required|alpha_space',
|
||||
'errors' => [
|
||||
'required' => 'Claim Status is required.',
|
||||
'alpha_space' => 'Claim Status should only contain letters and spaces.',
|
||||
],
|
||||
];
|
||||
$nilClaimsRows = $this->resolveNilClaimRowFlags($postData);
|
||||
$claimRowCount = $this->getClaimHistoryRowCount($postData, $nilClaimsRows);
|
||||
|
||||
for ($claimIndex = 0; $claimIndex < $claimRowCount; $claimIndex++) {
|
||||
$rules["first_year.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^\d{4}-\d{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Claim Year is required for all entries.',
|
||||
'regex_match' => 'Year must be in format YYYY-YYYY.',
|
||||
],
|
||||
];
|
||||
|
||||
if (! empty($nilClaimsRows[$claimIndex])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules["first_policy_type_.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9 _-]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Policy Type is required in Claim History.',
|
||||
'regex_match' => 'Policy Type only letters, numbers, space, hyphens and underscores are allowed',
|
||||
],
|
||||
];
|
||||
$rules["first_date_of_loss_.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^[0-9]{2}-[0-9]{2}-[0-9]{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Date of Loss is required.',
|
||||
'regex_match' => 'Date of Loss must be inValid format.',
|
||||
],
|
||||
];
|
||||
$rules["first_cause_of_loss.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^[a-zA-Z0-9 _-]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Cause of Loss is required.',
|
||||
'regex_match' => 'Cause of Loss only letters, numbers, space, hyphens and underscores are allowed',
|
||||
],
|
||||
];
|
||||
$rules["first_claim_amount.$claimIndex"] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Claim Amount is required.',
|
||||
'numeric' => 'Claim Amount must be a number.',
|
||||
],
|
||||
];
|
||||
$rules["first_settled_amount.$claimIndex"] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Settled Amount is required.',
|
||||
'numeric' => 'Settled Amount must be a number.',
|
||||
],
|
||||
];
|
||||
$rules["first_claim_status.$claimIndex"] = [
|
||||
'rules' => 'required|alpha_space',
|
||||
'errors' => [
|
||||
'required' => 'Claim Status is required.',
|
||||
'alpha_space' => 'Claim Status should only contain letters and spaces.',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -576,65 +596,75 @@ class LeadsController extends BaseController
|
||||
$claimHistoryPolicyTypes = [1, 6, 7];
|
||||
$hasClaimHistoryPolicy = count(array_intersect($postedPolicyTypeIds, $claimHistoryPolicyTypes)) > 0;
|
||||
$isEbClaimHistory = $leadFormType === 1
|
||||
&& in_array((int) ($postData['lead_type'] ?? 0), [2, 3], true)
|
||||
&& in_array((int) ($postData['lead_type'] ?? 0), [1, 2, 3], true)
|
||||
&& $hasClaimHistoryPolicy
|
||||
&& (int) ($postData['claim_history'] ?? 0) === 1;
|
||||
|
||||
if ($isEbClaimHistory) {
|
||||
$rules['first_year.*'] = [
|
||||
'rules' => 'required|regex_match[/^\d{4}-\d{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Claim Year is required for all entries.',
|
||||
'regex_match' => 'Year must be in format YYYY-YYYY.',
|
||||
],
|
||||
];
|
||||
$rules['emp_id.*'] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Employee ID is required in Claim History.'],
|
||||
];
|
||||
$rules['emp_name.*'] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Employee Name is required in Claim History.'],
|
||||
];
|
||||
$rules['gender.*'] = [
|
||||
'rules' => 'required|in_list[Female,Male]',
|
||||
'errors' => [
|
||||
'required' => 'Gender is required in Claim History.',
|
||||
'in_list' => 'Gender must be Female or Male.',
|
||||
],
|
||||
];
|
||||
$rules['designation.*'] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Designation is required in Claim History.'],
|
||||
];
|
||||
$rules['sum_insured.*'] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Sum Insured is required in Claim History.',
|
||||
'numeric' => 'Sum Insured must be a number.',
|
||||
],
|
||||
];
|
||||
$rules['first_death_date.*'] = [
|
||||
'rules' => 'required|regex_match[/^[0-9]{2}-([0-9]{2}|[A-Za-z]{3})-[0-9]{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Date of Death is required.',
|
||||
'regex_match' => 'Date of Death must be in DD-MM-YYYY or DD-MMM-YYYY format.',
|
||||
],
|
||||
];
|
||||
$rules['first_cause_of_death.*'] = [
|
||||
'rules' => 'required|in_list[natural_death,suicide,accident,cardiac_arrest,septic_shock,heart_attack]',
|
||||
'errors' => [
|
||||
'required' => 'Nature/Cause Of Death is required.',
|
||||
'in_list' => 'Nature/Cause Of Death is invalid.',
|
||||
],
|
||||
];
|
||||
$rules['first_claim_amount.*'] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Claim/Settled Amount is required.',
|
||||
'numeric' => 'Claim/Settled Amount must be a number.',
|
||||
],
|
||||
];
|
||||
$nilClaimsRows = $this->resolveNilClaimRowFlags($postData);
|
||||
$claimRowCount = $this->getClaimHistoryRowCount($postData, $nilClaimsRows);
|
||||
|
||||
for ($claimIndex = 0; $claimIndex < $claimRowCount; $claimIndex++) {
|
||||
$rules["first_year.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^\d{4}-\d{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Claim Year is required for all entries.',
|
||||
'regex_match' => 'Year must be in format YYYY-YYYY.',
|
||||
],
|
||||
];
|
||||
|
||||
if (! empty($nilClaimsRows[$claimIndex])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules["emp_id.$claimIndex"] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Employee ID is required in Claim History.'],
|
||||
];
|
||||
$rules["emp_name.$claimIndex"] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Employee Name is required in Claim History.'],
|
||||
];
|
||||
$rules["gender.$claimIndex"] = [
|
||||
'rules' => 'required|in_list[Female,Male]',
|
||||
'errors' => [
|
||||
'required' => 'Gender is required in Claim History.',
|
||||
'in_list' => 'Gender must be Female or Male.',
|
||||
],
|
||||
];
|
||||
$rules["designation.$claimIndex"] = [
|
||||
'rules' => 'required',
|
||||
'errors' => ['required' => 'Designation is required in Claim History.'],
|
||||
];
|
||||
$rules["sum_insured.$claimIndex"] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Sum Insured is required in Claim History.',
|
||||
'numeric' => 'Sum Insured must be a number.',
|
||||
],
|
||||
];
|
||||
$rules["first_death_date.$claimIndex"] = [
|
||||
'rules' => 'required|regex_match[/^[0-9]{2}-([0-9]{2}|[A-Za-z]{3})-[0-9]{4}$/]',
|
||||
'errors' => [
|
||||
'required' => 'Date of Death is required.',
|
||||
'regex_match' => 'Date of Death must be in DD-MM-YYYY or DD-MMM-YYYY format.',
|
||||
],
|
||||
];
|
||||
$rules["first_cause_of_death.$claimIndex"] = [
|
||||
'rules' => 'required|in_list[natural_death,suicide,accident,cardiac_arrest,septic_shock,heart_attack]',
|
||||
'errors' => [
|
||||
'required' => 'Nature/Cause Of Death is required.',
|
||||
'in_list' => 'Nature/Cause Of Death is invalid.',
|
||||
],
|
||||
];
|
||||
$rules["first_claim_amount.$claimIndex"] = [
|
||||
'rules' => 'required|numeric',
|
||||
'errors' => [
|
||||
'required' => 'Claim/Settled Amount is required.',
|
||||
'numeric' => 'Claim/Settled Amount must be a number.',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 1. MANUALLY VALIDATE FILES BEFORE PROCESSING
|
||||
@ -2866,7 +2896,9 @@ class LeadsController extends BaseController
|
||||
//claim history new sheet;
|
||||
if ($claim_history == 1 && !empty($rfq_data['fin_years_claims'])) {
|
||||
|
||||
$claim_details = json_decode($rfq_data['fin_years_claims'], true) ?? [];
|
||||
$claim_details = $this->sanitizeClaimDetailsForExport(
|
||||
json_decode($rfq_data['fin_years_claims'], true) ?? []
|
||||
);
|
||||
|
||||
if (! empty($claim_details['finyear'])) {
|
||||
|
||||
@ -4654,6 +4686,209 @@ class LeadsController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeClaimHistoryPostData(): void
|
||||
{
|
||||
$post = $this->request->getPost();
|
||||
|
||||
if ((int) ($post['claim_history'] ?? 0) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = $this->getFinyearRowsFromPost($post);
|
||||
|
||||
if ($rows === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$leadFormType = (int) ($post['lead_form_type'] ?? 1);
|
||||
$claimArrays = $leadFormType === 2
|
||||
? $this->buildNonEbClaimHistoryPostArrays($rows)
|
||||
: $this->buildEbClaimHistoryPostArrays($rows);
|
||||
|
||||
$this->request->setGlobal('post', array_merge($post, $claimArrays));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove internal nil_claims flag from stored/exported claim rows.
|
||||
*
|
||||
* @param array<string, mixed> $claimDetails
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeClaimDetailsForExport(array $claimDetails): array
|
||||
{
|
||||
if (empty($claimDetails['finyear']) || ! is_array($claimDetails['finyear'])) {
|
||||
return $claimDetails;
|
||||
}
|
||||
|
||||
$claimDetails['finyear'] = array_map(static function ($record) {
|
||||
if (! is_array($record)) {
|
||||
return $record;
|
||||
}
|
||||
|
||||
unset($record['nil_claims']);
|
||||
|
||||
return $record;
|
||||
}, $claimDetails['finyear']);
|
||||
|
||||
return $claimDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function getFinyearRowsFromPost(array $postData): array
|
||||
{
|
||||
$jsonPayload = $postData['finyear'] ?? $postData['fin_years_claims'] ?? null;
|
||||
|
||||
if (! is_string($jsonPayload) || $jsonPayload === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($jsonPayload, true);
|
||||
|
||||
if (! is_array($decoded) || empty($decoded['finyear']) || ! is_array($decoded['finyear'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $decoded['finyear'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
*
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
private function buildEbClaimHistoryPostArrays(array $rows): array
|
||||
{
|
||||
$arrays = [
|
||||
'first_year' => [],
|
||||
'nil_claims' => [],
|
||||
'emp_id' => [],
|
||||
'emp_name' => [],
|
||||
'gender' => [],
|
||||
'designation' => [],
|
||||
'sum_insured' => [],
|
||||
'first_death_date' => [],
|
||||
'first_cause_of_death' => [],
|
||||
'first_claim_amount' => [],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$isNil = ! empty($row['nil_claims']);
|
||||
|
||||
$arrays['first_year'][] = (string) ($row['year'] ?? '');
|
||||
$arrays['nil_claims'][] = $isNil ? '1' : '0';
|
||||
|
||||
if ($isNil) {
|
||||
$arrays['emp_id'][] = 'Nil';
|
||||
$arrays['emp_name'][] = 'Nil';
|
||||
$arrays['gender'][] = 'Nil';
|
||||
$arrays['designation'][] = 'Nil';
|
||||
$arrays['sum_insured'][] = 'Nil';
|
||||
$arrays['first_death_date'][] = 'Nil';
|
||||
$arrays['first_cause_of_death'][] = 'Nil';
|
||||
$arrays['first_claim_amount'][] = 'Nil';
|
||||
} else {
|
||||
$arrays['emp_id'][] = (string) ($row['emp_id'] ?? '');
|
||||
$arrays['emp_name'][] = (string) ($row['emp_name'] ?? '');
|
||||
$arrays['gender'][] = (string) ($row['gender'] ?? '');
|
||||
$arrays['designation'][] = (string) ($row['designation'] ?? '');
|
||||
$arrays['sum_insured'][] = (string) ($row['sum_insured'] ?? '');
|
||||
$arrays['first_death_date'][] = (string) ($row['death_date'] ?? '');
|
||||
$arrays['first_cause_of_death'][] = (string) ($row['cause_of_death'] ?? '');
|
||||
$arrays['first_claim_amount'][] = (string) ($row['settled'] ?? ($row['claim_amount'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
return $arrays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
*
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
private function buildNonEbClaimHistoryPostArrays(array $rows): array
|
||||
{
|
||||
$arrays = [
|
||||
'first_year' => [],
|
||||
'nil_claims' => [],
|
||||
'first_policy_type_' => [],
|
||||
'first_date_of_loss_' => [],
|
||||
'first_cause_of_loss' => [],
|
||||
'first_claim_amount' => [],
|
||||
'first_settled_amount' => [],
|
||||
'first_claim_status' => [],
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$isNil = ! empty($row['nil_claims']);
|
||||
|
||||
$arrays['first_year'][] = (string) ($row['year'] ?? '');
|
||||
$arrays['nil_claims'][] = $isNil ? '1' : '0';
|
||||
|
||||
if ($isNil) {
|
||||
$arrays['first_policy_type_'][] = 'Nil';
|
||||
$arrays['first_date_of_loss_'][] = 'Nil';
|
||||
$arrays['first_cause_of_loss'][] = 'Nil';
|
||||
$arrays['first_claim_amount'][] = 'Nil';
|
||||
$arrays['first_settled_amount'][] = 'Nil';
|
||||
$arrays['first_claim_status'][] = 'Nil';
|
||||
} else {
|
||||
$arrays['first_policy_type_'][] = (string) ($row['policy_type'] ?? '');
|
||||
$arrays['first_date_of_loss_'][] = (string) ($row['date_of_loss'] ?? '');
|
||||
$arrays['first_cause_of_loss'][] = (string) ($row['cause_of_loss'] ?? '');
|
||||
$arrays['first_claim_amount'][] = (string) ($row['claim_amount'] ?? '');
|
||||
$arrays['first_settled_amount'][] = (string) ($row['settled_amount'] ?? '');
|
||||
$arrays['first_claim_status'][] = (string) ($row['status'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return $arrays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int> Row index => 1 when nil claims, else 0
|
||||
*/
|
||||
private function resolveNilClaimRowFlags(array $postData): array
|
||||
{
|
||||
$flags = [];
|
||||
|
||||
foreach ((array) ($postData['nil_claims'] ?? []) as $idx => $val) {
|
||||
$flags[(int) $idx] = (int) $val;
|
||||
}
|
||||
|
||||
foreach ($this->getFinyearRowsFromPost($postData) as $idx => $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! empty($row['nil_claims'])) {
|
||||
$flags[(int) $idx] = 1;
|
||||
} elseif (! isset($flags[(int) $idx])) {
|
||||
$flags[(int) $idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $nilClaimFlags
|
||||
*/
|
||||
private function getClaimHistoryRowCount(array $postData, array $nilClaimFlags): int
|
||||
{
|
||||
$counts = [
|
||||
count((array) ($postData['first_year'] ?? [])),
|
||||
count($nilClaimFlags),
|
||||
];
|
||||
|
||||
$counts[] = count($this->getFinyearRowsFromPost($postData));
|
||||
|
||||
return max(0, ...$counts);
|
||||
}
|
||||
|
||||
public function getLastFiveFinancialYears(): array
|
||||
{
|
||||
$year = (int) date('Y');
|
||||
@ -4921,8 +5156,11 @@ class LeadsController extends BaseController
|
||||
$data['lead_edit_data']['multi_file_html'] = trim($html) !== '' ? $html : null;
|
||||
}
|
||||
|
||||
if (! in_array((int) $data['lead_edit_data']['lead_type'], [1, 3], true) && $data['lead_edit_data']['lead_form_type'] == 2) {
|
||||
$data['lead_edit_data']['claims_details_html'] = view('rfq/claims_details_non_eb', $data['lead_edit_data']);
|
||||
if (in_array((int) $data['lead_edit_data']['lead_type'], [1, 2, 3], true) && $data['lead_edit_data']['lead_form_type'] == 2) {
|
||||
$data['lead_edit_data']['claims_details_html'] = view('rfq/claims_details_non_eb', [
|
||||
'lead_edit_data' => $data['lead_edit_data'],
|
||||
'lastFiveYears' => $data['lastFiveYears'] ?? $this->getLastFiveFinancialYears(),
|
||||
]);
|
||||
} else {
|
||||
$data['lead_edit_data']['claims_details_html'] = "";
|
||||
}
|
||||
@ -5372,6 +5610,16 @@ class LeadsController extends BaseController
|
||||
{
|
||||
helper('excel_util_helper');
|
||||
|
||||
$finyear = array_map(static function ($record) {
|
||||
if (! is_array($record)) {
|
||||
return $record;
|
||||
}
|
||||
|
||||
unset($record['nil_claims']);
|
||||
|
||||
return $record;
|
||||
}, $finyear);
|
||||
|
||||
$first = $finyear[0] ?? null;
|
||||
|
||||
if (! is_array($first) || $first === []) {
|
||||
@ -5844,8 +6092,11 @@ class LeadsController extends BaseController
|
||||
|
||||
public function generateViewPageHtml($policy_type_id, $data = [])
|
||||
{
|
||||
$data['insurer'] = $this->insurerBranchModel->getInsurerBranchesWithInsurerNames();
|
||||
$data['tpa'] = $this->tpaBranchModel->getTpaBranchesWithTpaNames();
|
||||
$data['insurer'] = $this->insurerBranchModel->getInsurerBranchesWithInsurerNames();
|
||||
$data['tpa'] = $this->tpaBranchModel->getTpaBranchesWithTpaNames();
|
||||
$data['lastFiveYears'] = $data['lastFiveYears'] ?? $this->getLastFiveFinancialYears();
|
||||
$data['gpaClaimType'] = $data['gpaClaimType'] ?? $this->claim_type_for_gpa;
|
||||
$data['causeOfDeath'] = $data['causeOfDeath'] ?? $this->cause_of_death;
|
||||
|
||||
$viewMap = [
|
||||
1 => 'rfq/gpa',
|
||||
@ -6483,7 +6734,9 @@ class LeadsController extends BaseController
|
||||
|
||||
if (! empty($rfq_data['fin_years_claims']) && $claim_history == 1) {
|
||||
|
||||
$claim_details = json_decode($rfq_data['fin_years_claims'], true) ?? [];
|
||||
$claim_details = $this->sanitizeClaimDetailsForExport(
|
||||
json_decode($rfq_data['fin_years_claims'], true) ?? []
|
||||
);
|
||||
|
||||
if (! empty($claim_details['finyear'])) {
|
||||
|
||||
|
||||
@ -20,12 +20,9 @@ class LoginController extends BaseController
|
||||
public function index()
|
||||
{
|
||||
|
||||
// $session_data = ['isLoggedIn' => True ,'userid' => '25'];
|
||||
// set_session_data($session_data);
|
||||
// $isLoggedIn = check_session();
|
||||
$isLoggedIn = check_session();
|
||||
$hasCookie = check_cookie();
|
||||
if ($isLoggedIn && $hasCookie) {
|
||||
if ($isLoggedIn || $hasCookie) {
|
||||
return redirect()->to(base_url('/dashboard/view'));
|
||||
}
|
||||
return view('login');
|
||||
|
||||
@ -3677,13 +3677,6 @@ class TicketController extends BaseController
|
||||
$employeeRest = new EmployeeRestController();
|
||||
$result = $employeeRest->handleCliamFiles($file_data, $ticket_id);
|
||||
if(!empty($result)){
|
||||
// Merge all uploaded PDFs for this ticket into a single combined PDF
|
||||
try {
|
||||
helper('merge_pdf');
|
||||
merge_ticket_pdfs((int) $ticket_id);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'TicketController::upload_url | merge_ticket_pdfs failed | ticket_id=' . $ticket_id . ' | ' . $e->getMessage());
|
||||
}
|
||||
return $this->respond(['status' => true, 'message' => 'File uploaded successfully ']);
|
||||
}else{
|
||||
return $this->respond(['status' => false, 'message' => 'Failed to upload file ']);
|
||||
|
||||
@ -1297,6 +1297,7 @@ class TicketServiceController extends AdminController
|
||||
} else {
|
||||
|
||||
//check the employee and insured
|
||||
$params['is_from'] = 'claim_dump';
|
||||
$employee_data = $ticketMasterModal->getEmployeeAndEmployeePolicyDetails($params) ?? [];
|
||||
// dd($employee_data);
|
||||
if (count($employee_data) == 0) {
|
||||
|
||||
@ -189,7 +189,7 @@ if (! function_exists('merge_ticket_pdfs')) {
|
||||
|
||||
$insertData = [
|
||||
'ticket_id' => $ticket_master_id,
|
||||
'ticket_type' => $opts['ticket_type'],
|
||||
// 'ticket_type' => $opts['ticket_type'],
|
||||
'file_type' => MERGED_CLAIM_FILE_TYPE,
|
||||
'doc_name' => 'MERGED_CLAIM_DOCS_PDF',
|
||||
'file_name' => $mergedName,
|
||||
|
||||
@ -19,6 +19,7 @@ abstract class BaseTpaClaimImportService
|
||||
protected $claimDumpFileModel;
|
||||
protected $clientPolicyModel;
|
||||
protected $policyNumberMapping;
|
||||
protected $tpaTableMapping;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -33,6 +34,14 @@ abstract class BaseTpaClaimImportService
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'Policy Number',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'POLICY_NO',
|
||||
];
|
||||
$this->tpaTableMapping = [
|
||||
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal',
|
||||
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi',
|
||||
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist',
|
||||
(int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl',
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,8 +67,7 @@ abstract class BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->db->transRollback(); // ROLLBACK BEFORE RETURN
|
||||
return ['status' => false, 'message' => 'Excel file contains no data or wrong file upload'];
|
||||
return $this->failTpaClaimDumpInsert($fileId, 'Excel file contains no data or wrong file upload');
|
||||
}
|
||||
|
||||
if(isset($this->policyNumberMapping[$fileData['tpa_id']]) && !empty($this->policyNumberMapping[$fileData['tpa_id']])){
|
||||
@ -70,22 +78,19 @@ abstract class BaseTpaClaimImportService
|
||||
|
||||
|
||||
if($client_policy_data['policy_no'] != ($rows[0][$policy_number_column] ?? '')){
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Policy number mismatch in the file and in the system'];
|
||||
return $this->failTpaClaimDumpInsert($fileId, 'Policy number mismatch in the file and in the system');
|
||||
}
|
||||
|
||||
$tpaInsertData = $this->mapTPAData($rows, $fileId);
|
||||
|
||||
if (empty($tpaInsertData)) {
|
||||
$this->db->transRollback(); // ROLLBACK BEFORE RETURN
|
||||
return ['status' => false, 'message' => 'These records already exist in the system.'];
|
||||
return $this->failTpaClaimDumpInsert($fileId, 'These records already exist in the system.');
|
||||
}
|
||||
|
||||
$return_res = $this->bulkInsertTPATable($tpaInsertData);
|
||||
|
||||
if ($return_res !== true) {
|
||||
$this->db->transRollback(); // ROLLBACK BEFORE RETURN
|
||||
return ['status' => false, 'message' => 'TPA Import bulk insert failed'];
|
||||
return $this->failTpaClaimDumpInsert($fileId, 'TPA Import bulk insert failed');
|
||||
}
|
||||
|
||||
// 2. Commit if everything is fine
|
||||
@ -93,9 +98,7 @@ abstract class BaseTpaClaimImportService
|
||||
return ['status' => true, 'message' => 'File uploaded successfully', 'record_count' => count($tpaInsertData)];
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
// 3. Rollback on any crash/exception
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'System error : ' . $e->getMessage()];
|
||||
return $this->failTpaClaimDumpInsert($fileId, 'System error : ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +116,7 @@ abstract class BaseTpaClaimImportService
|
||||
|
||||
// Check if mapping failed
|
||||
if (!$ticketMasterData['status']) {
|
||||
$this->db->transRollback(); // ALWAYS rollback before early return
|
||||
$this->rollbackAndCleanupClaimDumpData($file_id);
|
||||
return $ticketMasterData;
|
||||
}
|
||||
|
||||
@ -125,13 +128,11 @@ abstract class BaseTpaClaimImportService
|
||||
if (!empty($ticketMasterData['mapped_array'])) {
|
||||
$insert_res = $this->importClaimMaster($ticketMasterData['mapped_array']);
|
||||
if (!$insert_res) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Ticket Master Claim bulk insert failed'];
|
||||
return $this->failTicketMasterInsert($file_id, 'Ticket Master Claim bulk insert failed');
|
||||
}
|
||||
// Map newly created ticket IDs back to the TPA staging table
|
||||
if (!$this->updateTicketIdInTPATable($file_id)) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Updating ticket_id in TPA table failed'];
|
||||
return $this->failTicketMasterInsert($file_id, 'Updating ticket_id in TPA table failed');
|
||||
}
|
||||
|
||||
$message .= 'Ticket Master Claim bulk insert success. ';
|
||||
@ -144,8 +145,7 @@ abstract class BaseTpaClaimImportService
|
||||
if (!empty($ticketMasterData['rejected_reason_array'])) {
|
||||
$update_res = $this->updateTicketMasterRejectedReasonInTPATable($ticketMasterData['rejected_reason_array']);
|
||||
if (!$update_res) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Updating rejected reasons failed'];
|
||||
return $this->failTicketMasterInsert($file_id, 'Updating rejected reasons failed');
|
||||
}
|
||||
|
||||
$message .= empty($ticketMasterData['mapped_array'])
|
||||
@ -156,24 +156,69 @@ abstract class BaseTpaClaimImportService
|
||||
|
||||
// If nothing was processed but no error occurred
|
||||
if (!$hasExecutedTask) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'No data found to process.'];
|
||||
return $this->failTicketMasterInsert($file_id, 'No data found to process.');
|
||||
}
|
||||
|
||||
// 2. Commit the transaction
|
||||
$this->db->transCommit();
|
||||
|
||||
if (!$status) {
|
||||
$this->cleanupClaimDumpData($file_id);
|
||||
}
|
||||
|
||||
return ['status' => $status, 'message' => trim($message)];
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
// 3. Rollback on crash
|
||||
$this->db->transRollback();
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'System error during Ticket Master Insert: ' . $th->getMessage()
|
||||
];
|
||||
$fileId = (int) ($params['file_id'] ?? 0);
|
||||
return $this->failTicketMasterInsert(
|
||||
$fileId,
|
||||
'System error during Ticket Master Insert: ' . $th->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove TPA staging rows and ticket_master rows created for a failed claim dump upload.
|
||||
*/
|
||||
protected function cleanupClaimDumpData(int $fileId): void
|
||||
{
|
||||
if ($fileId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->where('id', $fileId)->first();
|
||||
if (empty($fileData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tpaId = (int) ($fileData['tpa_id'] ?? 0);
|
||||
$tpaTable = $this->tpaTableMapping[$tpaId] ?? null;
|
||||
|
||||
if ($tpaTable !== null) {
|
||||
$this->db->table($tpaTable)->where('file_id', $fileId)->delete();
|
||||
}
|
||||
|
||||
$this->db->table('ticket_master')->where('file_id', $fileId)->delete();
|
||||
}
|
||||
|
||||
protected function rollbackAndCleanupClaimDumpData(int $fileId): void
|
||||
{
|
||||
$this->db->transRollback();
|
||||
$this->cleanupClaimDumpData($fileId);
|
||||
}
|
||||
|
||||
protected function failTpaClaimDumpInsert(int $fileId, string $message): array
|
||||
{
|
||||
$this->rollbackAndCleanupClaimDumpData($fileId);
|
||||
return ['status' => false, 'message' => $message];
|
||||
}
|
||||
|
||||
protected function failTicketMasterInsert(int $fileId, string $message): array
|
||||
{
|
||||
$this->rollbackAndCleanupClaimDumpData($fileId);
|
||||
return ['status' => false, 'message' => $message];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Excel and return associative rows (header based)
|
||||
*/
|
||||
@ -324,7 +369,15 @@ abstract class BaseTpaClaimImportService
|
||||
* Dublicate check in the TPA specific table records
|
||||
*/
|
||||
public function getEmployeeDetails(int $client_id, int $client_policy_id, string $emp_code, string $relation): array
|
||||
{
|
||||
{
|
||||
|
||||
$client_policy_data = $this->clientPolicyModel->where('id', $client_policy_id)->first();
|
||||
if(!empty($client_policy_data)){
|
||||
if(!empty($client_policy_data['base_policy'])){
|
||||
$client_policy_id = $client_policy_data['base_policy'];
|
||||
}
|
||||
}
|
||||
|
||||
$EmployeeModel = new EmployeeModel();
|
||||
$employeeData = $EmployeeModel
|
||||
->select([
|
||||
|
||||
@ -59,4 +59,18 @@ class ClaimFilesModel extends Model
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the claim (ticket) has at least one active PDF in claim_files.
|
||||
*/
|
||||
public function hasPdfFileForTicket($ticketId): bool
|
||||
{
|
||||
return $this->where('ticket_id', $ticketId)
|
||||
->where('is_active', 1)
|
||||
->groupStart()
|
||||
->where('mime_type', 'pdf')
|
||||
->orWhere('mime_type', 'application/pdf')
|
||||
->groupEnd()
|
||||
->countAllResults(false) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,14 +756,17 @@ class EmployeePolicyModel extends Model
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
emp_code,
|
||||
group_key,
|
||||
CAST(MAX(CASE WHEN field_name = 'basic_cover_si' THEN new_value END) AS UNSIGNED) AS new_basic_cover_si,
|
||||
CAST(MAX(CASE WHEN field_name = 'premium' THEN new_value END) AS DECIMAL(10,2)) AS new_si_premium,
|
||||
CAST(MAX(CASE WHEN field_name = 'premium' THEN old_value END) AS DECIMAL(10,2)) AS old_si_premium,
|
||||
MAX(CASE WHEN field_name = 'si_enhancement_date' THEN new_value END) AS date_of_coverage
|
||||
FROM emp_endorsement
|
||||
$subquery_endorsement_condition
|
||||
GROUP BY emp_code
|
||||
) AS sidata ON a.emp_code = sidata.emp_code
|
||||
AND actions = 'si'
|
||||
AND status != 'truncated'
|
||||
GROUP BY emp_code, group_key
|
||||
) AS sidata ON a.emp_code = sidata.emp_code AND a.group_key = sidata.group_key
|
||||
WHERE employee_polices.client_policy_id = '{$client_policy_id}'
|
||||
AND employees.client_branch_id = '{$client_branch_id}'
|
||||
$endorsement_condition
|
||||
@ -2252,7 +2255,7 @@ class EmployeePolicyModel extends Model
|
||||
}
|
||||
else if(count($emp_codes) > 0 && $all == false)
|
||||
{
|
||||
$result->whereNotIn('emp.emp_code',$emp_codes);
|
||||
$result->whereNotIn('employee_polices.id',$emp_codes);
|
||||
}
|
||||
else if(count($emp_codes) == 0 && $all == true)
|
||||
{
|
||||
|
||||
@ -1118,12 +1118,23 @@ class TicketMasterModel extends Model
|
||||
$policy_no = $params['policy_no'] ?? null;
|
||||
$relationship = $params['relationship'] ?? null;
|
||||
$insured_name = $params['insured_name'] ?? null;
|
||||
$is_from = $params['is_from'] ?? null;
|
||||
|
||||
// $client_name = "6-Eleven";
|
||||
// $emp_code = "EMP0020K12";
|
||||
// $emp_name = "Lokesh";
|
||||
// $policy_no = "GMC-1999/2000/2021/2022";
|
||||
|
||||
$base_policy_id = null;
|
||||
if($is_from == "claim_dump"){
|
||||
$client_policy_data = $this->db->table('client_policy')->where('policy_no', trim($policy_no))->where('is_active', 1)->get()->getRowArray();
|
||||
if(isset($client_policy_data) && !empty($client_policy_data)){
|
||||
if(!empty($client_policy_data['base_policy'])) {
|
||||
$base_policy_id = $client_policy_data['base_policy'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$builder = $this->db->table('employees e');
|
||||
$builder->select("
|
||||
e.id as emp_id,
|
||||
@ -1161,7 +1172,13 @@ class TicketMasterModel extends Model
|
||||
$builder->where('e.emp_code', trim($emp_code));
|
||||
$builder->where('e.name', trim($emp_name));
|
||||
$builder->where('c.client_name', trim($client_name));
|
||||
$builder->where('cp.policy_no', trim($policy_no));
|
||||
|
||||
if(!empty($base_policy_id)) {
|
||||
$builder->where('cp.id', trim($base_policy_id));
|
||||
} else {
|
||||
$builder->where('cp.policy_no', trim($policy_no));
|
||||
}
|
||||
|
||||
$builder->where('LOWER(e.relationship)', strtolower('self'));
|
||||
|
||||
$query = $builder->get();
|
||||
|
||||
@ -112,18 +112,44 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
/* Make the modal content a flex column so only the body scrolls */
|
||||
#tpa_variation_modal .modal-content {
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Internal vertical scroll for large tables, keep header/footer fixed */
|
||||
#tpa_variation_modal .modal-body {
|
||||
max-height: calc(85vh - 50px);
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Tab panes must shrink inside flex modal-body so scrollX width is correct */
|
||||
#tpa_variation_modal .tab-content,
|
||||
#tpa_variation_modal .tab-pane {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Outer .table-responsive must not scroll when DataTables scrollX is active (see custom.css) */
|
||||
#tpa_variation_modal .table-responsive {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#tpa_variation_modal .dataTables_wrapper {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#tpa_variation_modal .dataTables_scrollHead {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
#tpa_variation_modal .dataTables_scrollBody {
|
||||
overflow-x: auto !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#tpa_variation_modal table {
|
||||
@ -548,6 +574,10 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
let tpaNotInNhanceProceedEnabled = true;
|
||||
/** From API `not_in_nhance_proceed_button_text` — label for Proceed on "Not in Nhance" tab. */
|
||||
let tpaNotInNhanceProceedButtonText = 'Proceed - Not in Nhance';
|
||||
/** From API `not_matched_count` — when 0, Proceed is disabled on "Need to Review" tab. */
|
||||
let tpaNeedToReviewProceedEnabled = true;
|
||||
/** From API `need_to_review_proceed_button_text` — label for Proceed on "Need to Review" tab. */
|
||||
let tpaNeedToReviewProceedButtonText = 'Proceed - Need to Review ( 0 )';
|
||||
|
||||
const tpaVariationColumns = {
|
||||
not_in_nhance: [
|
||||
@ -694,6 +724,16 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
tpaNotInNhanceProceedButtonText = 'Proceed - Not in Nhance';
|
||||
}
|
||||
|
||||
const notMatchedCount = parseInt(data.not_matched_count, 10);
|
||||
tpaNeedToReviewProceedEnabled = !isNaN(notMatchedCount) && notMatchedCount > 0;
|
||||
if (typeof data.need_to_review_proceed_button_text === 'string' && data.need_to_review_proceed_button_text.trim() !== '') {
|
||||
tpaNeedToReviewProceedButtonText = data.need_to_review_proceed_button_text.trim();
|
||||
} else if (!isNaN(notMatchedCount)) {
|
||||
tpaNeedToReviewProceedButtonText = 'Proceed - Need to Review ( ' + notMatchedCount + ' )';
|
||||
} else {
|
||||
tpaNeedToReviewProceedButtonText = 'Proceed - Need to Review ( 0 )';
|
||||
}
|
||||
|
||||
showTPAVariationTabByLinkId('not-in-nhance-tab');
|
||||
updateTPAProceedButton('not-in-nhance-tab');
|
||||
adjustVariationTableByTabId('not-in-nhance-tab');
|
||||
@ -711,6 +751,20 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
updateTPAProceedButton($activeTab.attr('id'));
|
||||
}
|
||||
|
||||
function bindVariationScrollHeadSync($wrap) {
|
||||
const $body = $wrap.find('.dataTables_scrollBody');
|
||||
const $head = $wrap.find('.dataTables_scrollHead');
|
||||
if (!$body.length || !$head.length) {
|
||||
return;
|
||||
}
|
||||
$body.off('scroll.nhTpaVarHScroll').on('scroll.nhTpaVarHScroll', function () {
|
||||
$head.scrollLeft($(this).scrollLeft());
|
||||
});
|
||||
$head.off('scroll.nhTpaVarHScroll').on('scroll.nhTpaVarHScroll', function () {
|
||||
$body.scrollLeft($(this).scrollLeft());
|
||||
});
|
||||
}
|
||||
|
||||
function getVariationDataTableConfig() {
|
||||
return {
|
||||
dom: "<'dt-top'lf>" +
|
||||
@ -722,7 +776,15 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
paging: true,
|
||||
ordering: false,
|
||||
info: false,
|
||||
scrollX: true,
|
||||
scrollY: false,
|
||||
scrollCollapse: false,
|
||||
autoWidth: false,
|
||||
initComplete: function () {
|
||||
const $wrap = $(this.api().table().container());
|
||||
$wrap.closest('.table-responsive').addClass('nh-dt-no-outer-scroll');
|
||||
bindVariationScrollHeadSync($wrap);
|
||||
},
|
||||
language: {
|
||||
search: `
|
||||
<div class="datatable-search-wrapper" style="position:relative; display:inline-block;">
|
||||
@ -773,8 +835,17 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
}
|
||||
|
||||
const dt = $table.DataTable();
|
||||
const $wrap = $(dt.table().container());
|
||||
bindVariationScrollHeadSync($wrap);
|
||||
dt.columns.adjust().draw(false);
|
||||
bindVariationSearchIcons($table);
|
||||
|
||||
/* Re-measure after tab/modal becomes visible — hidden tables mis-size columns */
|
||||
setTimeout(function () {
|
||||
if ($table.is(':visible')) {
|
||||
dt.columns.adjust().draw(false);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function adjustVariationTableByTabId(tabId) {
|
||||
@ -814,7 +885,10 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
$button.prop('disabled', true);
|
||||
}
|
||||
} else if (activeId === 'need-to-review-tab') {
|
||||
$button.text('Proceed - Need to Review');
|
||||
$button.text(tpaNeedToReviewProceedButtonText);
|
||||
if (!tpaNeedToReviewProceedEnabled) {
|
||||
$button.prop('disabled', true);
|
||||
}
|
||||
} else if (activeId === 'not-in-tpa-tab') {
|
||||
$button.addClass('d-none');
|
||||
} else {
|
||||
@ -829,12 +903,32 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
const activeId = $(e.target).attr('id');
|
||||
updateTPAProceedButton(activeId);
|
||||
adjustVariationTableByTabId(activeId);
|
||||
setTimeout(function () {
|
||||
const tabToTableMap = {
|
||||
'not-in-nhance-tab': '#not_in_nhance_table',
|
||||
'not-in-tpa-tab': '#not_in_tpa_table',
|
||||
'need-to-review-tab': '#need_to_review_table'
|
||||
};
|
||||
const sel = tabToTableMap[activeId];
|
||||
const $t = sel ? $(sel) : $();
|
||||
if ($.fn.DataTable.isDataTable($t)) {
|
||||
$t.DataTable().columns.adjust().draw(false);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
|
||||
$('#tpa_variation_modal').on('shown.bs.modal', function () {
|
||||
showTPAVariationTabByLinkId('not-in-nhance-tab');
|
||||
updateTPAProceedButton('not-in-nhance-tab');
|
||||
adjustVariationTableByTabId('not-in-nhance-tab');
|
||||
setTimeout(function () {
|
||||
['#not_in_nhance_table', '#not_in_tpa_table', '#need_to_review_table'].forEach(function (sel) {
|
||||
const $t = $(sel);
|
||||
if ($.fn.DataTable.isDataTable($t) && $t.is(':visible')) {
|
||||
$t.DataTable().columns.adjust().draw(false);
|
||||
}
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
|
||||
$('#tpa_variation_modal').on('hidden.bs.modal', function () {
|
||||
@ -1155,6 +1249,10 @@ for ($i = 0; $i < $batch_col_count; $i++) {
|
||||
} else if (activeId === 'not-in-tpa-tab') {
|
||||
proceedNotInTPA();
|
||||
} else if (activeId === 'need-to-review-tab') {
|
||||
if (!tpaNeedToReviewProceedEnabled) {
|
||||
toastr.warning('No mismatched records to proceed.', 'WARNING');
|
||||
return;
|
||||
}
|
||||
proceedNeedToReview();
|
||||
} else {
|
||||
toastr.warning('Unknown tab selected.', 'WARNING');
|
||||
|
||||
292
app/Views/docs/bds-commission.php
Normal file
292
app/Views/docs/bds-commission.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
/**
|
||||
* BDS commission — content only
|
||||
* app/Views/docs/bds-commission.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/commission_file_upload.php
|
||||
* - app/Views/commission_rules_list.php
|
||||
* - app/Controllers/RuleImportController.php
|
||||
* - app/Controllers/InsuranceCommissionController.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>BDS commission</strong> is a two-part flow: admins upload commission <strong>rules</strong>
|
||||
(Excel) per insurer, month, and department; partner/BDS systems then call an API to
|
||||
<strong>calculate payout</strong> for a policy using those rules.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload rules Excel"] --> B["Save JSON on disk"]
|
||||
B --> C["Edit rules in UI"]
|
||||
D["Policy data POST"] --> E["Load matching JSON"]
|
||||
E --> F["Match rule and return payout"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Setup:</strong> <code>RuleImportController</code> + <code>commission_file_upload.php</code> — import and manage rules.</li>
|
||||
<li><strong>Runtime:</strong> <code>POST getCommission</code> → <code>InsuranceCommissionController::initiateCommissionCalc</code> — read JSON, pick first matching rule, return payout.</li>
|
||||
<li>Rules file path: <code>writable/uploads/commission/rules/{MONYYYY}/{insurer_id}_{department}.json</code> (e.g. <code>SEP2025/5_motor.json</code>).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files">Key files</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Part</th><th>File</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Upload list UI</td><td><code>app/Views/commission_file_upload.php</code></td></tr>
|
||||
<tr><td>Rules editor UI</td><td><code>app/Views/commission_rules_list.php</code></td></tr>
|
||||
<tr><td>Upload and editor API</td><td><code>app/Controllers/RuleImportController.php</code></td></tr>
|
||||
<tr><td>Excel parsing</td><td><code>ruleImportService</code> (via <code>Config\Services::ruleImportService()</code>)</td></tr>
|
||||
<tr><td>Payout calculation API</td><td><code>app/Controllers/InsuranceCommissionController.php</code></td></tr>
|
||||
<tr><td>Upload metadata DB</td><td><code>commission_files</code> (<code>CommissionFilesModel</code>)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="routes">Routes</h2>
|
||||
|
||||
<p><strong>Admin (commission group):</strong></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Route</th><th>Handler</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>GET/POST commission/list</code></td><td><code>commissionFileUploadList</code></td></tr>
|
||||
<tr><td><code>POST commission/upload</code></td><td><code>upload</code></td></tr>
|
||||
<tr><td><code>GET commission/sample_file</code></td><td>Sample CSV download</td></tr>
|
||||
<tr><td><code>GET commission/downloadErrorFile</code></td><td>Annotated error Excel</td></tr>
|
||||
<tr><td><code>GET commission/checkSameEntry</code></td><td>Duplicate insurer + month + department check</td></tr>
|
||||
<tr><td><code>GET commission/rules/list/(:id)</code></td><td><code>ruleList</code> — rules editor page</td></tr>
|
||||
<tr><td><code>POST commission/rules/save/</code></td><td><code>saveRule</code></td></tr>
|
||||
<tr><td><code>POST commission/rules/remove/</code></td><td><code>removeRule</code></td></tr>
|
||||
<tr><td><code>GET commission/checkRuleUsage</code></td><td>Whether rule is used on partner policies</td></tr>
|
||||
<tr><td><code>GET commission/deleteCommissionData/(:id)</code></td><td>Soft-delete file + mark rules deleted</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Runtime API:</strong></p>
|
||||
<pre><code class="language-text">POST getCommission
|
||||
→ InsuranceCommissionController::initiateCommissionCalc
|
||||
→ filter: CommissionApiFilter</code></pre>
|
||||
|
||||
<h2 id="commission-file-format">How to build the commission file</h2>
|
||||
|
||||
<p>
|
||||
Use the template from the upload screen (<strong>Download sample file</strong>) or copy from
|
||||
<code>public/sample_excel/sample_commission.csv</code>. Dev copies also live under
|
||||
<code>writable/uploads/commission/files/</code> (e.g. <code>sample_commission.csv</code>,
|
||||
<code>Sample_commission_file-New.xlsx</code>). After a successful import, see the generated JSON under
|
||||
<code>writable/uploads/commission/rules/{MONYYYY}/</code> (example: <code>NOV2025/1_motor.json</code>).
|
||||
</p>
|
||||
|
||||
<div class="callout warning">
|
||||
<span>!</span>
|
||||
<div>
|
||||
<strong>Use the current column layout</strong>
|
||||
Some older CSVs in <code>writable/uploads/commission/files/</code> use legacy headers
|
||||
(<code>Rule Name</code>, <code>Commission Params(TP:OD:PA)</code>). The importer expects the
|
||||
<strong>S.No</strong> layout below (<code>RuleImportService</code>). Wrong headers fail with
|
||||
“Missing required columns”.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="required-columns">Required columns (row 1 headers)</h3>
|
||||
|
||||
<p>Header text must match <strong>exactly</strong> (one row per rule, starting row 2). Empty cells are allowed and simply skip that condition.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Column</th><th>Purpose</th><th>Example</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>S.No</td><td>Serial (not used in logic)</td><td>1</td></tr>
|
||||
<tr><td>Premium Type</td><td>Maps to <code>policy_type</code></td><td>OD, TP, COM</td></tr>
|
||||
<tr><td>Vehicle Type</td><td><code>vehicle_type</code>; comma = multiple (IN)</td><td>Two Wheeler, Four Wheeler</td></tr>
|
||||
<tr><td>Vehicle Sub Type</td><td><code>vehicle_sub_type</code></td><td>Car, PCV</td></tr>
|
||||
<tr><td>Make / Model</td><td>Vehicle make and model</td><td>Honda, i10</td></tr>
|
||||
<tr><td>CC Min / CC Max</td><td>Cubic capacity range</td><td>1000, 3000</td></tr>
|
||||
<tr><td>Fuel Type</td><td>Comma-separated fuels</td><td>Petrol, Diesel</td></tr>
|
||||
<tr><td>Vehicle Age Min / Max</td><td>Vehicle age range (years)</td><td>1, 5</td></tr>
|
||||
<tr><td>Vehicle Weight Min / Max</td><td>Weight range (kg)</td><td>2000, 3000</td></tr>
|
||||
<tr><td>RTO State / RTO Code</td><td><code>geo_rto_state</code>, <code>geo_rto_city</code></td><td>RJ, 41</td></tr>
|
||||
<tr><td>Renewal Type</td><td><code>renewal_type</code></td><td>Online, Cash, Card</td></tr>
|
||||
<tr><td>Commission Type</td><td><code>percentage</code>, <code>composite</code>, <code>flat</code>, <code>tiered</code></td><td>percentage</td></tr>
|
||||
<tr><td>Commission Value</td><td>% or flat amount (required for percentage / flat)</td><td>15 or 500</td></tr>
|
||||
<tr><td>Commission Params (TP)</td><td>Composite: % on TP premium</td><td>18</td></tr>
|
||||
<tr><td>Commission Params (OD)</td><td>Composite: % on OD premium</td><td>10</td></tr>
|
||||
<tr><td>Commission Params (PA)</td><td>Composite: % on PA premium</td><td>0 or empty</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="commission-types">Commission types (what to fill)</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Type</th><th>Fill in sheet</th><th>Becomes in JSON</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>percentage</code></td>
|
||||
<td>Commission Value = e.g. <code>10</code></td>
|
||||
<td>10% of <code>premium</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>composite</code></td>
|
||||
<td>Leave Value empty; set TP / OD / PA param columns (percent each)</td>
|
||||
<td>Split % on <code>tp_premium</code>, <code>od_premium</code>, <code>pa_premium</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>flat</code></td>
|
||||
<td>Commission Value = fixed rupee amount e.g. <code>500</code></td>
|
||||
<td>Fixed payout on <code>premium</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="commission-file-samples">Sample rows (from project files)</h3>
|
||||
|
||||
<p><strong>Template header + rows</strong> (<code>public/sample_excel/sample_commission.csv</code>):</p>
|
||||
<pre><code class="language-text">S.No,Premium Type,Vehicle Type,...,Commission Type,Commission Value,Commission Params (TP),Commission Params (OD),Commission Params (PA)
|
||||
1,OD,Car,...,percentage,15,,,
|
||||
2,TP,Two Wheeler,...,composite,,18,10,
|
||||
3,COM,PCV,...,percentage,25,,,
|
||||
4,COM,GCV,...,composite,,,10,</code></pre>
|
||||
|
||||
<p><strong>Example A — composite two-wheeler</strong> (from <code>writable/.../rules/NOV2025/1_motor.json</code>):</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Two Wheeler</code>, CC Min/Max = <code>100</code></li>
|
||||
<li>Commission Type = <code>composite</code>, TP = <code>10</code>, OD = <code>25</code></li>
|
||||
<li>Result: 10% on TP premium + 25% on OD premium when policy matches</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example B — percentage four-wheeler</strong>:</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Four Wheeler</code>, CC = <code>1000</code>, Vehicle Age Min/Max = <code>5</code></li>
|
||||
<li>Commission Type = <code>percentage</code>, Commission Value = <code>10</code></li>
|
||||
<li>Result: 10% of total premium</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example C — composite TP-only</strong>:</p>
|
||||
<ul>
|
||||
<li>Premium Type = <code>TP</code>, Vehicle Type = <code>Four Wheeler</code></li>
|
||||
<li>Commission Type = <code>composite</code>, Commission Params (TP) = <code>10</code></li>
|
||||
<li>Result: 10% on TP premium only</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example D — flat amount</strong>:</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Two Wheeler,Four Wheeler</code> (comma → matches either)</li>
|
||||
<li>Commission Type = <code>flat</code>, Commission Value = <code>500</code></li>
|
||||
<li>Result: fixed ₹500 when matched</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Tips:</strong></p>
|
||||
<ul>
|
||||
<li>Min must not be greater than Max (CC, age, weight) or the row fails validation.</li>
|
||||
<li>On failure, download the <strong>annotated</strong> file — errors are written into the sheet.</li>
|
||||
<li>Supported formats: <code>.csv</code>, <code>.xlsx</code>, <code>.xls</code>, <code>.ods</code>.</li>
|
||||
<li>Upload UI departments: <code>motor</code>, <code>health</code>; import logic is built for <strong>motor</strong> columns today.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="upload-flow">Rule upload</h2>
|
||||
|
||||
<p>From <code>commission_file_upload.php</code> the user picks insurer, commission month, department (motor / health), and an Excel/CSV file.</p>
|
||||
|
||||
<ol>
|
||||
<li><code>checkSameEntry</code> — if a successful upload already exists for the same trio, SweetAlert offers <strong>Overwrite</strong> or <strong>Append</strong> (<code>overwrite=1</code> or <code>0</code> on POST).</li>
|
||||
<li><code>upload</code> — stores file under <code>writable/uploads/commission/files/</code>, inserts <code>commission_files</code> row (<code>pending</code>).</li>
|
||||
<li><code>ruleImportService->processUpload()</code> — validates Excel rows; on success returns <code>rules</code> array; on failure returns <code>annotated_file</code> for download.</li>
|
||||
<li>On success — writes JSON to <code>rules/{MONYYYY}/{insurer_id}_{department}.json</code>; sets <code>file_status=success</code> and <code>rules_count</code>.</li>
|
||||
<li>On failure — <code>file_status=failed</code>; user downloads <code>annotated_{filename}</code> via <code>downloadErrorFile</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="rules-editor">Rules editor</h2>
|
||||
|
||||
<p>
|
||||
For successful uploads, action <strong>View Rules</strong> opens
|
||||
<code>commission/rules/list/{file_id}</code> (<code>commission_rules_list.php</code>).
|
||||
</p>
|
||||
<ul>
|
||||
<li>Lists rules from the JSON file for that upload’s insurer, month, and department.</li>
|
||||
<li><code>saveRule</code> — create or update a rule (conditions + calculation + name) in the JSON via <code>updateCommissionRules()</code>.</li>
|
||||
<li><code>removeRule</code> — soft-delete one rule (<code>is_deleted=true</code>).</li>
|
||||
<li><code>checkRuleUsage</code> — warns if <code>partner_policy.commission_applied_rule</code> references the rule.</li>
|
||||
<li>Deleting the whole upload marks all rules with that <code>file_id</code> as deleted in JSON, then sets <code>commission_files.is_active=0</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="rule-json">Rule JSON shape</h2>
|
||||
|
||||
<p>Each rule is roughly:</p>
|
||||
<pre><code class="language-json">{
|
||||
"id": "rule_…",
|
||||
"name": "Rule name",
|
||||
"department": "motor",
|
||||
"file_id": 12,
|
||||
"is_deleted": false,
|
||||
"conditions": [
|
||||
{ "field": "vehicle_type", "operator": "==", "value": "car" }
|
||||
],
|
||||
"calculation": {
|
||||
"type": "percentage",
|
||||
"value": 10,
|
||||
"on": "premium"
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Calculation types in <code>InsuranceCommissionController</code>: <code>percentage</code>, <code>composite</code>, <code>fixed</code>. Conditions support <code>==</code>, <code>!=</code>, <code>></code>, <code>>=</code>, <code><</code>, <code><=</code>, <code>between</code>, <code>in</code>.</p>
|
||||
|
||||
<h2 id="calculation-api">Commission calculation API</h2>
|
||||
|
||||
<p><code>initiateCommissionCalc()</code> expects POST/JSON including at least:</p>
|
||||
<ul>
|
||||
<li><code>policy_issue_date</code> — used to pick folder <code>{MON}{YEAR}</code> (e.g. SEP2025)</li>
|
||||
<li><code>insurer_id</code></li>
|
||||
<li><code>department</code> — motor, health, etc.</li>
|
||||
<li>Fields referenced in rule conditions and calculation bases (e.g. <code>premium</code>, <code>od_premium</code>)</li>
|
||||
</ul>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["POST getCommission"] --> B{"Required fields present?"}
|
||||
B -->|No| C["Validation error"]
|
||||
B -->|Yes| D["Load rules JSON for month, insurer, department"]
|
||||
D --> E{"File exists?"}
|
||||
E -->|No| F["Rules file not found"]
|
||||
E -->|Yes| G["Find first rule where all conditions match"]
|
||||
G --> H{"Rule found?"}
|
||||
H -->|No| I["No matching rules"]
|
||||
H -->|Yes| J["Apply calculation type"]
|
||||
J --> K["Return payout and rule"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Rules with <code>is_deleted: false</code> are loaded; the <strong>first</strong> matching rule wins (no priority field yet).</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Open <code>/commission/list</code> (logged-in admin).</li>
|
||||
<li>Download sample file, fill rules for insurer + month + department, upload.</li>
|
||||
<li>If validation fails, download the annotated error file and fix the sheet.</li>
|
||||
<li>Use <strong>View Rules</strong> to tweak conditions or calculation without re-uploading the whole file.</li>
|
||||
<li>Test payout: <code>POST getCommission</code> with the same insurer, department, and a <code>policy_issue_date</code> in that commission month.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Month folder must match policy date</strong> — upload uses commission month; API uses <code>policy_issue_date</code> to resolve the same <code>MONYYYY</code> folder.</li>
|
||||
<li><strong>Append vs overwrite</strong> — append merges JSON arrays; overwrite backs up the old file then replaces.</li>
|
||||
<li><strong>Departments</strong> — upload UI currently offers motor and health; API department string must match the JSON filename slug.</li>
|
||||
<li><strong>HTTP 200 on upload errors</strong> — check <code>status</code> and <code>code</code> in the JSON body, not only HTTP status.</li>
|
||||
</ul>
|
||||
309
app/Views/docs/bds-insurer-statement.php
Normal file
309
app/Views/docs/bds-insurer-statement.php
Normal file
@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/**
|
||||
* BDS Insurer statement — content only
|
||||
* app/Views/docs/bds-insurer-statement.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/insurer_statement_list.php
|
||||
* - app/Controllers/PolicyTransactionController.php
|
||||
* (statementList, uploadInsurerStatement, validateInsurerStatement, updateInsurerStatement)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>BDS Insurer statement</strong> lets finance users upload an insurer-provided Excel statement,
|
||||
validate each row against NHance policy transactions (<code>pt_co_share_details</code> +
|
||||
<code>policy_transaction</code>), and persist matched brokerage amounts into
|
||||
<code>co_share_stmt_details</code>. The admin UI is
|
||||
<code>app/Views/insurer_statement_list.php</code>; all server logic lives in
|
||||
<code>PolicyTransactionController</code> under the <code>policy_tranction/statement</code> route group.
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong>Auth</strong>
|
||||
Statement routes use the <code>authMVC</code> filter. Upload and list are browser AJAX/form calls from an authenticated session, not public API endpoints.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A[Upload Excel] --> B[validateInsurerStatement]
|
||||
B --> C{All rows OK?}
|
||||
C -->|Yes| D[updateInsurerStatement]
|
||||
C -->|No| E[file_status failed]
|
||||
D --> F[co_share_stmt_details]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="row-validation-flowchart">Row validation logic</h2>
|
||||
|
||||
<p>
|
||||
<code>validateInsurerStatement()</code> checks that every Excel line maps to a real NHance transaction
|
||||
for the selected insurer branch. Each row is matched on <strong>policy number</strong> (column B) and
|
||||
<strong>endorsement number</strong> (column C).
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read Excel"] --> B["Fetch NHance rows for policies in the file"]
|
||||
B --> C["Check each non-empty row"]
|
||||
C --> D{"Policy and endorsement found in NHance?"}
|
||||
D -->|"Yes, first time in file"| E["Row valid"]
|
||||
D -->|"Same combo again"| F["Duplicate"]
|
||||
D -->|"Policy not found"| G["Invalid policy"]
|
||||
D -->|"Policy OK, wrong endorsement"| H["Invalid endorsement"]
|
||||
E --> I{"Any bad rows?"}
|
||||
F --> I
|
||||
G --> I
|
||||
H --> I
|
||||
I -->|No| J["Validation passes"]
|
||||
I -->|Yes| K["Validation fails"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li>Empty rows are ignored.</li>
|
||||
<li>A row is <strong>valid</strong> only if that policy + endorsement exists in NHance and appears once in the upload.</li>
|
||||
<li>If anything fails, the whole file is marked <code>failed</code> and errors are shown per row in the UI.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/insurer_statement_list.php</code> — DataTable list, upload modal, validation error modal, invoice modal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Controller</td>
|
||||
<td><code>app/Controllers/PolicyTransactionController.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Statement header model</td>
|
||||
<td><code>app/Models/InsurerStatements.php</code> → <code>insurer_statements</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Line-item model</td>
|
||||
<td><code>app/Models/COShareStmtDetailsModel.php</code> → <code>co_share_stmt_details</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NHance source rows</td>
|
||||
<td><code>app/Models/PTCOShareDetailsModel.php</code> → <code>getNonReconcileredPolicyTransactionByPolicyAndEndorsement()</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sample Excel</td>
|
||||
<td><code>public/sample_excel/insurer_stament_sample.xlsx</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Routes (prefix <code>policy_tranction/statement</code>, filter <code>authMVC</code>):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Route</th><th>Handler</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>GET</td><td><code>list</code></td><td><code>statementList</code></td></tr>
|
||||
<tr><td>POST</td><td><code>upload</code></td><td><code>uploadInsurerStatement</code></td></tr>
|
||||
<tr><td>GET</td><td><code>downloadSampleInsurerStatement</code></td><td>Sample file download</td></tr>
|
||||
<tr><td>GET</td><td><code>downloadInsurerStatement/(:num)</code></td><td>Uploaded file download</td></tr>
|
||||
<tr><td>GET</td><td><code>getFileErr/(:any)</code></td><td>Validation failure JSON for modal</td></tr>
|
||||
<tr><td>GET</td><td><code>getInsurerStatementMonth</code></td><td>Used to disable already-used statement numbers</td></tr>
|
||||
<tr><td>GET</td><td><code>deleteStatement/(:any)</code></td><td>Soft-delete statement + related rows</td></tr>
|
||||
<tr><td>GET</td><td><code>getPaymentDetails/(:any)</code></td><td>Invoice modal data</td></tr>
|
||||
<tr><td>POST</td><td><code>saveInvoicePaymentDetails</code></td><td>Invoice / payment save</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<pre><code class="language-php">$routes->group('policy_tranction', ['filter' => 'authMVC'], function ($routes) {
|
||||
$routes->group('statement', ['filter' => 'authMVC'], function ($routes) {
|
||||
$routes->get('list', 'PolicyTransactionController::statementList');
|
||||
$routes->post('upload', 'PolicyTransactionController::uploadInsurerStatement');
|
||||
// ...
|
||||
});
|
||||
});</code></pre>
|
||||
|
||||
<h2 id="statement-list-ui">Statement list UI</h2>
|
||||
|
||||
<p>
|
||||
<code>statementList()</code> loads insurers/branches via
|
||||
<code>insurerBranchModel::getInsurerBranchesWithInsurerNames()</code>, invoice status labels, and
|
||||
statements from the last <strong>180 days</strong> (<code>is_active = 1</code>). The view shows:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Insurer branch, statement month, statement serial no, filename (download link), line items count</li>
|
||||
<li><code>file_status</code> — <code>success</code> or <code>failed</code> (failed rows show an alert icon → error modal)</li>
|
||||
<li><code>invoice_status</code> — pending / generated / sent / payment received</li>
|
||||
<li>Actions (non-failed only): invoice status update, delete</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Upload form fields (<code>#insurer_statement_upload_form</code>): insurer
|
||||
(<code>insurer_id-branch_id</code>), statement month (flatpickr), statement no (1–7), Excel file.
|
||||
Submit is AJAX POST to relative <code>upload</code>. On success the page reloads; on validation failure
|
||||
the API still returns HTTP 200 with <code>dataStatus: false</code> and <code>error_data</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="upload-flow">uploadInsurerStatement()</h2>
|
||||
|
||||
<ol>
|
||||
<li>Validates uploaded file: Excel MIME types, max 16 MB (<code>max_size[statement,16384]</code> KB).</li>
|
||||
<li>Moves file to <code>WRITEPATH . 'uploads/statements/'</code> (see <code>createStatementFolder()</code> for folder creation).</li>
|
||||
<li>Parses POST: <code>insurer</code> as <code>{insurer_id}-{branch_id}</code>, <code>statement_month</code> (converted to first-of-month <code>Y-m-d</code>), <code>statement_no</code> → <code>stmt_sno</code>.</li>
|
||||
<li>Inserts <code>insurer_statements</code> row via <code>InsurerStatements</code> model.</li>
|
||||
<li>Calls <code>validateInsurerStatement(['file_id' => $file_id])</code>.</li>
|
||||
<li>If validation passes, calls <code>updateInsurerStatement(['file_id' => $file_id])</code>.</li>
|
||||
<li>On validation failure: responds with <code>dataStatus: false</code>, <code>error_data</code>, <code>error_code</code> (HTTP 200).</li>
|
||||
<li>On success: sets <code>invoice_status = 'pending'</code> and returns <code>dataStatus: true</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="validate-flow">validateInsurerStatement($params)</h2>
|
||||
|
||||
<p>Runs immediately after upload (and can be re-run manually in dev with a hard-coded <code>file_id</code> in <code>statementList()</code> comment).</p>
|
||||
|
||||
<h3 id="validate-steps">Steps</h3>
|
||||
|
||||
<ol>
|
||||
<li>Load <code>insurer_statements</code> by <code>file_id</code>; fail if missing or physical file absent under <code>writable/uploads/statements/</code>.</li>
|
||||
<li>Load active sheet via PhpSpreadsheet; drop header row; sanitize with <code>ExcelSanitizeHelper::sanitizeArrayData()</code>.</li>
|
||||
<li>Collect unique policy numbers from column <strong>B</strong> (index <code>1</code>), skipping empty rows via <code>check_row_is_empty_or_null()</code>.</li>
|
||||
<li>Fetch NHance candidates:
|
||||
<code>PTCOShareDetailsModel::getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id, branch_id, policy_no[])</code>
|
||||
— matches <code>pt.policy_no</code> for completed transactions on that insurer branch.</li>
|
||||
<li>Build lookup keys <code>policy_no|endorsement_no</code> (sanitized) for source and Excel rows.</li>
|
||||
<li>For each non-empty Excel row, match policy + endorsement; track duplicates in <code>$matched_entry</code>.</li>
|
||||
<li>On mismatch, append row-wise HTML messages under <code>error_data[row_index]</code>.</li>
|
||||
<li>Update <code>insurer_statements.line_items</code>, <code>file_status</code> (<code>success</code> / <code>failed</code>), <code>reason</code> (JSON).</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="sanitize">sanitizeStatementLookupValue()</h3>
|
||||
|
||||
<p>Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel.</p>
|
||||
|
||||
<h3 id="validation-errors">Validation error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>error_code</th><th>Meaning</th><th>UI</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>0</code></td><td>DB row missing or physical file not found</td><td>Plain message in modal</td></tr>
|
||||
<tr><td><code>1</code></td><td>Legacy: list of row numbers (old format)</td><td>Comma-separated row list</td></tr>
|
||||
<tr><td><code>2</code></td><td>Row-wise validation (current)</td><td>Modal lists each row with policy / endorsement / duplicate messages</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Row keys in <code>error_data</code> are the <strong>array index</strong> from the Excel loop (first data row is typically <code>1</code> after header removal), not necessarily the Excel row number on sheet.</p>
|
||||
|
||||
<h2 id="update-flow">updateInsurerStatement($params)</h2>
|
||||
|
||||
<p>Runs only when validation returned <code>status: true</code>.</p>
|
||||
|
||||
<ol>
|
||||
<li>Same file load / sanitize path as validation.</li>
|
||||
<li>Rebuild policy list and source lookup (policy + endorsement → array of <code>pt_co_share_details</code> rows).</li>
|
||||
<li>For each Excel row with a matching source entry, compute brokerage totals and variance:
|
||||
<ul>
|
||||
<li>BP: amount col <code>3</code>, brokerage col <code>5</code></li>
|
||||
<li>TP: amount col <code>4</code>, brokerage col <code>6</code></li>
|
||||
<li>TEP: forced to <code>0</code> in current code</li>
|
||||
<li><code>reward</code> from col <code>7</code></li>
|
||||
<li><code>variance = exp_amt - total_amt</code> (from source <code>exp_amt</code>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>COShareStmtDetailsModel::insertBatch($data_to_update)</code> — one row per matched Excel line.</li>
|
||||
<li>Sets <code>file_status = success</code>, <code>invoice_status = pending</code>, clears/sets <code>reason</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="excel-columns">Excel column mapping (0-based index)</h2>
|
||||
|
||||
<p>Header row is removed; data columns used by validation/update:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Index</th><th>Column</th><th>Use</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>1</code></td><td>B</td><td>Policy number (required for matching)</td></tr>
|
||||
<tr><td><code>2</code></td><td>C</td><td>Endorsement number</td></tr>
|
||||
<tr><td><code>3</code></td><td>D</td><td>Actual BP amount</td></tr>
|
||||
<tr><td><code>4</code></td><td>E</td><td>Actual TP amount</td></tr>
|
||||
<tr><td><code>5</code></td><td>F</td><td>Actual BP brokerage</td></tr>
|
||||
<tr><td><code>6</code></td><td>G</td><td>Actual TP brokerage</td></tr>
|
||||
<tr><td><code>7</code></td><td>H</td><td>Reward</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Download the canonical layout from the list page link → <code>downloadSampleInsurerStatement</code>.</p>
|
||||
|
||||
<h2 id="nhance-source-query">NHance source query</h2>
|
||||
|
||||
<p><code>getNonReconcileredPolicyTransactionByPolicyAndEndorsement()</code> joins:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>pt_co_share_details</code> (active) → <code>policy_transaction</code> (active, <code>status = completed</code>)</li>
|
||||
<li>Filtered by <code>insurer_id</code>, <code>insurer_branch_id</code>, and <code>pt.policy_no IN (...)</code></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Matching is on sanitized <code>policy_no</code> + <code>endorsement_no</code>. The method name suggests
|
||||
“non-reconciled” but the current query does <strong>not</strong> filter <code>statement_id IS NULL</code>;
|
||||
be aware when re-uploading or debugging duplicate reconciliation.
|
||||
</p>
|
||||
|
||||
<h2 id="invoice-and-delete">Invoice status and delete</h2>
|
||||
|
||||
<p>After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):</p>
|
||||
|
||||
<ul>
|
||||
<li><code>invoice_status</code>: <code>pending</code>, <code>generated</code>, <code>sent</code>, <code>payment_received</code></li>
|
||||
<li><code>saveInvoicePaymentDetails</code> — JSON POST from invoice modal</li>
|
||||
<li><code>deleteStatement($id)</code> — soft-deletes <code>co_share_stmt_details</code>, <code>inv_payment_details</code>, and <code>insurer_statements</code> for that id</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<code>getInsurerStatementMonth</code> returns successful statements for insurer+month so the UI can
|
||||
disable statement numbers already used (<code>disableStatementNo()</code> in the view).
|
||||
</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Ensure <code>writable/uploads/statements/</code> exists and is writable (or call <code>createStatementFolder()</code> once).</li>
|
||||
<li>Open <code>policy_tranction/statement/list</code> in a logged-in session.</li>
|
||||
<li>Use sample Excel; pick insurer branch and month; choose an unused statement number (1–7 per insurer/month).</li>
|
||||
<li>Confirm policy/endorsement exist on a <strong>completed</strong> BDS transaction for that insurer branch.</li>
|
||||
<li>On failure, open the alert icon → modal calls <code>getFileErr/{id}</code> and renders <code>reason</code> JSON.</li>
|
||||
<li>To debug validation only: temporarily uncomment the <code>validateInsurerStatement</code> / <code>updateInsurerStatement</code> one-liner in <code>statementList()</code> with a known <code>file_id</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="common-pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Hidden Excel characters</strong> — policy/endorsement must pass <code>sanitizeStatementLookupValue()</code>; re-type values if NHance shows a match but upload fails.</li>
|
||||
<li><strong>Duplicate policy + endorsement</strong> in the same file → <code>error_code 2</code>, duplicate message on second row.</li>
|
||||
<li><strong>Validation failed but file on disk</strong> — <code>insurer_statements</code> row remains; user sees <code>failed</code> status; re-upload needs a new statement or delete the failed row.</li>
|
||||
<li><strong>Statement number reuse</strong> — only successful uploads for that insurer/month block numbers in the dropdown via <code>getInsurerStatementMonth</code>.</li>
|
||||
<li><strong>Upload response HTTP 200 on error</strong> — front-end checks <code>dataStatus</code>, not status code alone.</li>
|
||||
<li><strong>Legacy handlers</strong> — <code>validateInsurerStatementOld</code>, <code>updateInsurerStatementOLD</code> remain in the controller; production path is the non-<code>Old</code> methods documented here.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="related-bds">Related BDS features</h2>
|
||||
|
||||
<ul>
|
||||
<li>BDS reports: <code>policy_tranction/report/list</code>, variance, finance, outstanding lists</li>
|
||||
<li>Daily BDS cron mail: <code>cronDailyBDSReport</code> (separate from statement upload)</li>
|
||||
</ul>
|
||||
265
app/Views/docs/correction.php
Normal file
265
app/Views/docs/correction.php
Normal file
@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* Correction (employee Excel upload) — content only
|
||||
* app/Views/docs/correction.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesCorrectionProcess)
|
||||
* - app/Controllers/EmployeeController.php (employeesUplodWithEvents)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Correction</strong> updates existing member data on an active policy via Excel upload
|
||||
(<code>files.action = correction</code>). The final step creates <strong>pending correction endorsements</strong>
|
||||
on the <code>employees</code> table — data is not updated until those endorsements are applied downstream.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Processing is in <code>EmployeeServiceController::employeesCorrectionProcess</code>, queued after the same
|
||||
format and data validation steps used for inception and deletion.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action correction"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesCorrectionProcess job"]
|
||||
D --> E["emp_endorsement pending on employees"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|loop done| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 8 columns (A–H); field must be one of four allowed names; dates <code>d-M-Y</code>.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist (emp code + name + active policy); code <strong>10</strong> if not found.</li>
|
||||
<li><strong>Step 3 — Correction:</strong> One pending endorsement per row per field (skips duplicate pending corrections).</li>
|
||||
<li>Each Excel row = one field change for one member (not a full-family operation).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>Correction</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Correction process</td>
|
||||
<td><code>EmployeeServiceController::employeesCorrectionProcess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Column config API</td>
|
||||
<td><code>getCorrectionExcelColumns()</code> — used when building correction Excel programmatically</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeesCorrectionProcess</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endorsements</td>
|
||||
<td><code>EmpEndorsementModel</code> — <code>actions = c</code>, <code>table_name = employees</code>, <code>status = pending</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=correction</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — validation errors</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the client policy id. Lookup requires active
|
||||
<code>employees</code> + <code>employee_polices</code> on that policy and branch.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>Correction process</td><td>Job <code>employeesCorrectionProcess</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$correction_excel_columns</code> — <strong>8 columns (A–H)</strong>.</li>
|
||||
<li>Action code <code>C</code> for mandatory rules on correction-specific columns.</li>
|
||||
<li><strong>Field</strong> (column D): only <code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code>.</li>
|
||||
<li><strong>Date of Correction</strong> (F): <code>d-M-Y</code>.</li>
|
||||
<li><strong>Change event</strong> (G) and <strong>Value</strong> (E) are mandatory.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list (e.g. invalid Field value)</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed</td></tr>
|
||||
<tr><td>5</td><td>File / policy problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Rows grouped by EMP ID; for correction the critical check is <code>name_and_empid_check_in_db</code>:</p>
|
||||
<ul>
|
||||
<li>Each row must match an active employee on the uploaded policy (emp code + name).</li>
|
||||
<li>Not found → error code <strong>10</strong> (“Record Not found”).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeesCorrectionProcess</code> (with optional <code>batch_file_id</code> for TPA multi-file flows).</p>
|
||||
|
||||
<h2 id="correction-process">Step 3: employeesCorrectionProcess</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read each Excel row"] --> B["Match emp_code plus name on active policy"]
|
||||
B --> C{"Pending correction for same field?"}
|
||||
C -->|Yes| D["Skip row"]
|
||||
C -->|No| E["Insert emp_endorsement actions c"]
|
||||
E --> F["old_value from DB new_value from Excel"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per row:</strong></p>
|
||||
<ul>
|
||||
<li><code>field_name</code> ← column D (<code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code>)</li>
|
||||
<li><code>new_value</code> ← column E; if field is <code>dob</code>, converted from <code>d-M-Y</code> to <code>Y-m-d</code></li>
|
||||
<li><code>date_of_correction</code> ← column F (converted to <code>Y-m-d</code>)</li>
|
||||
<li><code>remarks</code> ← column H (optional)</li>
|
||||
<li><code>old_value</code> ← current value from <code>employees.{field_name}</code></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Skips insert when a pending correction endorsement already exists for the same
|
||||
<code>emp_code</code>, <code>name</code>, and <code>field_name</code>
|
||||
(<code>actions = c</code>, <code>endorsement_id IS NULL</code>, <code>status != truncated</code>).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sets <code>files.status = success</code> when the loop completes and sends a success pull notification.
|
||||
Rows with no DB match are not endorsed (no row-level failure on the file record).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>TPA batch:</strong> If <code>batch_file_id</code> is set, also queues
|
||||
<code>updateEmployeeDataFromTpa</code>, <code>reconTpaApiDataWithEmployeepolicies</code>, and
|
||||
<code>initializeDeletionProcessForTpaApiData</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">Correction Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB before correction</td></tr>
|
||||
<tr><td>D</td><td>Field</td><td>Yes</td><td><code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code></td></tr>
|
||||
<tr><td>E</td><td>Value</td><td>Yes</td><td>New value; DOB as <code>d-M-Y</code></td></tr>
|
||||
<tr><td>F</td><td>Date of Correction</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
<tr><td>G</td><td>Change event</td><td>Yes</td><td>e.g. <code>correction</code></td></tr>
|
||||
<tr><td>H</td><td>Remarks</td><td>No</td><td>Stored on endorsement</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Fix DOB</strong> — one row, one field:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Field</th><th>Value</th><th>Date of Correction</th><th>Change event</th><th>Remarks</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>EMP001</td>
|
||||
<td>Raj Kumar</td>
|
||||
<td>dob</td>
|
||||
<td>15-Jan-1985</td>
|
||||
<td>19-May-2026</td>
|
||||
<td>correction</td>
|
||||
<td>Typo in upload</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Multiple fixes</strong> — use separate rows (same or different members):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Field</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Raj Kumar</td><td>email_corporate</td><td>raj.kumar@company.com</td></tr>
|
||||
<tr><td>EMP001</td><td>Priya Kumar</td><td>relationship</td><td>Spouse</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Upload with <code>upload-action-type=correction</code>; note <code>file_id</code>.</li>
|
||||
<li>On validation failure, check <code>/employee/excel_error/{file_id}</code> for code <strong>10</strong>.</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'c'</code>, <code>status = 'pending'</code>.</li>
|
||||
<li>Compare <code>field_name</code>, <code>old_value</code>, <code>new_value</code> per row to the Excel.</li>
|
||||
<li>To generate correction Excel in code, use <code>getCorrectionExcelColumns()</code> for header layout.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Name must match DB</strong> — correction identifies the member by current <code>emp_code</code> + <code>name</code>; rename via a <code>name</code> field row uses the old name in column C.</li>
|
||||
<li><strong>Only four fields</strong> — mobile, SI, band, etc. are not supported in this upload path.</li>
|
||||
<li><strong>Duplicate pending correction</strong> — second upload for the same field is skipped until the first endorsement is processed or truncated.</li>
|
||||
<li><strong>File success vs rows</strong> — <code>files.status = success</code> does not mean every row created an endorsement.</li>
|
||||
<li><strong>Not live update</strong> — <code>employees</code> columns change only after endorsement approval/application.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Related:
|
||||
<a href="<?= base_url('docs/inception') ?>">Inception</a>,
|
||||
<a href="<?= base_url('docs/deletion') ?>">Deletion</a>.
|
||||
</p>
|
||||
259
app/Views/docs/deletion.php
Normal file
259
app/Views/docs/deletion.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Deletion (employee Excel upload) — content only
|
||||
* app/Views/docs/deletion.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeeDisembark)
|
||||
* - app/Controllers/EmployeeController.php
|
||||
* (employeesUplodWithEvents, initializeDeletionProcessForTpaApiData)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Deletion</strong> removes active members from a client policy via Excel upload
|
||||
(<code>files.action = deletion</code>). Unlike inception, the final step does not delete rows immediately —
|
||||
it creates <strong>pending endorsement</strong> records on <code>employee_polices</code> for approval/processing later.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Core processing lives in <code>EmployeeServiceController::employeeDisembark</code>.
|
||||
<code>EmployeeController::initializeDeletionProcessForTpaApiData</code> is a separate TPA-reconcile path that
|
||||
builds a deletion Excel file and calls the same disembark function.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action deletion"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeeDisembark job"]
|
||||
D --> E["emp_endorsement pending rows"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|always| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 7 columns (A–G), mandatory exit fields per action code <code>D</code>.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist in DB (emp code + name + active policy); code 10 if not found.</li>
|
||||
<li><strong>Step 3 — Disembark:</strong> Writes pending deletion endorsements; Self row removes whole family, dependent row removes one member.</li>
|
||||
<li>Files < 1 MB run format validation inline; data validation and disembark are always queued.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>Deletion</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>EmployeeServiceController::excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deletion process</td>
|
||||
<td><code>EmployeeServiceController::employeeDisembark</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TPA auto-deletion</td>
|
||||
<td><code>EmployeeController::initializeDeletionProcessForTpaApiData</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeeDisembark</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endorsements</td>
|
||||
<td><code>EmpEndorsementModel</code> → <code>emp_endorsement</code> (<code>actions = d</code>, <code>status = pending</code>)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=deletion</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — read <code>files.reason</code> after validation failure</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the <strong>client policy id</strong>. Member lookup joins
|
||||
<code>employees</code> + <code>employee_polices</code> on that policy and branch.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>Disembark</td><td>Job <code>employeeDisembark</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$deletion_excel_columns</code> — <strong>7 columns (A–G)</strong>.</li>
|
||||
<li>Action code <code>D</code> drives mandatory fields: Change event, Date of exit, Reason for exit, Claim status.</li>
|
||||
<li>Date of exit format: <code>d-M-Y</code>.</li>
|
||||
<li>Claim status allowed: <code>0</code> or <code>1</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed</td></tr>
|
||||
<tr><td>5</td><td>File / policy problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Rows are grouped by EMP ID. For deletion, the main check is <code>name_and_empid_check_in_db</code>:</p>
|
||||
<ul>
|
||||
<li>Each row must match an <strong>active</strong> employee + <strong>active</strong> <code>employee_polices</code> row on the uploaded policy/branch.</li>
|
||||
<li>If not found → error code <strong>10</strong> (“Record Not found”) on that Excel row.</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeeDisembark</code> (not <code>employeesOnboardPreprocess</code>).</p>
|
||||
|
||||
<h2 id="disembark">Step 3: employeeDisembark</h2>
|
||||
|
||||
<p>Loads the Excel again and processes each non-empty row.</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Match emp_code plus name on active policy"] --> B{"Found?"}
|
||||
B -->|No| C["Log error skip row"]
|
||||
B -->|Yes| D{"Pending deletion endorsement?"}
|
||||
D -->|Yes| E["Log skip row"]
|
||||
D -->|No| F{"Relationship Self?"}
|
||||
F -->|No| G["Endorse this member only"]
|
||||
F -->|Yes| H["Endorse all active family members"]
|
||||
G --> I["emp_endorsement pending"]
|
||||
H --> I
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per matched member</strong>, four pending endorsement rows are inserted on <code>employee_polices</code>:</p>
|
||||
<ul>
|
||||
<li><code>date_of_exit</code> ← Excel column E (converted to <code>Y-m-d</code>)</li>
|
||||
<li><code>reason_for_exit</code> ← column F</li>
|
||||
<li><code>status</code> → <code>inactive</code></li>
|
||||
<li><code>claim_status</code> ← column G</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Returns an array of processed <code>employee.id</code> values. Sets <code>files.status = success</code> when the loop finishes
|
||||
(even if some rows were skipped — check logs for “not found” or “existing endorsement pending”).
|
||||
</p>
|
||||
|
||||
<h2 id="tpa-auto">TPA auto-deletion (EmployeeController)</h2>
|
||||
|
||||
<p><code>initializeDeletionProcessForTpaApiData($file_id)</code>:</p>
|
||||
<ol>
|
||||
<li>Reads TPA reconcile file context (<code>tpa_api_data</code>, <code>action_flag_status = D</code>).</li>
|
||||
<li>Builds candidate members and writes <code>writable/uploads/excel/tpa_auto_deletion_{fileId}_{timestamp}.xls</code>.</li>
|
||||
<li>Inserts a new <code>files</code> row with <code>action = deletion</code>.</li>
|
||||
<li>Calls <code>employeeDisembark(['file_id' => $newFileId])</code> synchronously.</li>
|
||||
<li>Exports endorsement data for TPA via <code>getDeletionEmployeeDataForExportExcel</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="excel-columns">Deletion Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td>Employee / family code</td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB name exactly</td></tr>
|
||||
<tr><td>D</td><td>Change event</td><td>Yes</td><td>e.g. <code>deletion</code></td></tr>
|
||||
<tr><td>E</td><td>Date of exit</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
<tr><td>F</td><td>Reason for exit</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>G</td><td>Claim status</td><td>Yes</td><td><code>0</code> or <code>1</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Delete one dependent</strong> — only that name appears; Self row is not required in the file.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Row</th><th>EMP ID</th><th>NAME</th><th>Change event</th><th>Date of exit</th><th>Reason</th><th>Claim</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>2</td><td>EMP001</td><td>Arjun Kumar</td><td>deletion</td><td>19-May-2026</td><td>Resigned</td><td>0</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Result: endorsements for <strong>Arjun only</strong> (relationship ≠ Self).</p>
|
||||
|
||||
<p><strong>Delete entire family</strong> — list the <strong>Self</strong> row; disembark loads all active family members for that EMP ID.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Row</th><th>EMP ID</th><th>NAME</th><th>Change event</th><th>Date of exit</th><th>Reason</th><th>Claim</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>2</td><td>EMP001</td><td>Raj Kumar</td><td>deletion</td><td>19-May-2026</td><td>Resigned</td><td>0</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Result: pending endorsements for <strong>Self + all active dependents</strong> on that policy (same exit date/reason/claim from the row).</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Upload via <code>/employee/upload</code> with action <code>deletion</code>; note <code>file_id</code>.</li>
|
||||
<li>If validation fails, use <code>GET /employee/excel_error/{file_id}</code> — look for code <strong>10</strong> (member not in DB).</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'd'</code>, <code>status = 'pending'</code>.</li>
|
||||
<li>If rows were skipped, search logs for <code>not found</code> or <code>Existing endorsement pending</code>.</li>
|
||||
<li>TPA path: trace <code>initializeDeletionProcessForTpaApiData</code> and the generated <code>tpa_auto_deletion_*.xls</code> file.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Name mismatch</strong> — Excel name must match <code>employees.name</code> exactly (case/spacing).</li>
|
||||
<li><strong>Not active</strong> — only <code>emp_status = active</code> and <code>employee_polices.status = active</code> match.</li>
|
||||
<li><strong>Duplicate pending deletion</strong> — row skipped if a pending deletion endorsement already exists for that policy row.</li>
|
||||
<li><strong>Self vs dependent</strong> — wrong relationship in the row changes scope (one member vs whole family).</li>
|
||||
<li><strong>File always success after disembark</strong> — <code>files.status</code> does not reflect per-row skips; use endorsements table + logs.</li>
|
||||
<li><strong>Not immediate delete</strong> — members stay active until endorsements are approved/applied downstream.</li>
|
||||
</ul>
|
||||
|
||||
<p>Related: <a href="<?= base_url('docs/inception') ?>">Inception</a> (onboard pipeline uses the same upload screen and first two validation steps).</p>
|
||||
@ -39,6 +39,7 @@ $base = base_url();
|
||||
/* ─── TOKENS ─────────────────────────────── */
|
||||
:root {
|
||||
--sidebar-w : 260px;
|
||||
--toc-w : 200px;
|
||||
--topbar-h : 56px;
|
||||
--bg : #ffffff;
|
||||
--bg2 : #f7f8fa;
|
||||
@ -143,6 +144,8 @@ $base = base_url();
|
||||
/* ─── LAYOUT WRAPPER ─────────────────────── */
|
||||
.docs-layout {
|
||||
display : flex;
|
||||
width : 100%;
|
||||
align-items: flex-start;
|
||||
margin-top: var(--topbar-h);
|
||||
min-height: calc(100vh - var(--topbar-h));
|
||||
}
|
||||
@ -208,6 +211,15 @@ $base = base_url();
|
||||
.callout.success { background: #f0fdf4; border-color: #4ade80; color: #15803d; }
|
||||
|
||||
/* ─── TABLES ─────────────────────────────── */
|
||||
.docs-table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.docs-table-wrap table { margin: 0; min-width: 640px; }
|
||||
.docs-table-wrap--fluid table { min-width: 0; width: 100%; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13.5px; }
|
||||
th {
|
||||
background : var(--bg2);
|
||||
|
||||
@ -27,10 +27,11 @@ $toc = $toc ?? [];
|
||||
<style>
|
||||
/* ─── MAIN CONTENT ───────────────────────── */
|
||||
.docs-main {
|
||||
flex : 1;
|
||||
max-width: 760px;
|
||||
padding : 48px 56px 80px;
|
||||
min-width: 0;
|
||||
flex : 1 1 auto;
|
||||
min-width : 0;
|
||||
max-width : none;
|
||||
width : 0;
|
||||
padding : 48px 40px 80px 48px;
|
||||
}
|
||||
|
||||
.docs-main__breadcrumb {
|
||||
@ -62,13 +63,18 @@ $toc = $toc ?? [];
|
||||
|
||||
/* ─── RIGHT TOC ──────────────────────────── */
|
||||
.docs-toc {
|
||||
width : 200px;
|
||||
min-width: 200px;
|
||||
padding : 56px 20px 0;
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
align-self: flex-start;
|
||||
width : var(--toc-w);
|
||||
min-width : var(--toc-w);
|
||||
max-width : var(--toc-w);
|
||||
padding : 56px 20px 48px 16px;
|
||||
border-left: 1px solid var(--border);
|
||||
background : var(--bg);
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
max-height : calc(100vh - var(--topbar-h) - 48px);
|
||||
overflow-y : auto;
|
||||
align-self : flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -92,6 +98,14 @@ $toc = $toc ?? [];
|
||||
|
||||
.docs-toc a:hover { color: var(--accent); }
|
||||
.docs-toc a.is-sub { padding-left: 10px; font-size: 12px; }
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.docs-toc { display: none; }
|
||||
.docs-main {
|
||||
width: auto;
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
|
||||
@ -285,34 +285,7 @@ flowchart TD
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="flow-group-slabs"><code>group_slab_rates_basedon_name</code></h2>
|
||||
|
||||
<p>
|
||||
Flattens the list returned from the model into a map keyed by <code>rack_rate_name</code>. Each bucket keeps
|
||||
<code>['slab_rates' => [...], 'grid_master' => ...]</code> where <code>grid_master</code> comes from the row’s policy grid record
|
||||
(<code>ui_type</code> becomes the numeric grid id used later in <code>premium_calculation_manager</code>).
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper" id="flowchart-group-slab-rates">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
S["slab_rates rows"] --> L["iterate in DB order"]
|
||||
L --> K{"rack_rate_name changed"}
|
||||
K -->|yes| B["open new bucket"]
|
||||
K -->|no| U["same bucket"]
|
||||
B --> P["append row to slab_rates array"]
|
||||
U --> P
|
||||
P --> G["store grid_master from row"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
When maintaining this helper, inspect the implementation for <strong>duplicate pushes</strong> on the first row of a new rack name;
|
||||
downstream code tolerates duplicate slab rows but it can confuse debugging of premium matches.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="flow-family-composition"><code>get_familiy_composition</code></h2>
|
||||
|
||||
@ -321,13 +294,122 @@ flowchart LR
|
||||
(already grouped to one employee). Keys align with the JSON used in rack configuration (<code>additional_relationship</code>), except
|
||||
<code>either-parents-pil</code> and <code>elders_count</code> which are stripped before comparison.
|
||||
</p>
|
||||
<p>
|
||||
This function mainly:
|
||||
<ul>
|
||||
<li>Reads all family members</li>
|
||||
<li>Normalizes relationship names</li>
|
||||
<li>Builds a summarized family composition object</li>
|
||||
<li>Maintains counts for:
|
||||
<ul style="padding-left: 20px;">
|
||||
<li>self</li>
|
||||
<li>spouse</li>
|
||||
<li>children</li>
|
||||
<li>parents</li>
|
||||
<li>parents-in-law</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper" id="flowchart-family-composition">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["single pass over family_data rows"] --> B["slugify column 5 per row"]
|
||||
B --> C["bump self spouse childrens parents parents-in-law counters"]
|
||||
C --> D["return family_composition map"]
|
||||
|
||||
START([Start get_familiy_composition])
|
||||
|
||||
START --> INIT["Initialize family_composition array and slug service"]
|
||||
|
||||
INIT --> LOOP_MEMBERS{"Loop each family member"}
|
||||
|
||||
LOOP_MEMBERS --> GET_REL["Read relationship from row[5]"]
|
||||
|
||||
GET_REL --> SLUGIFY["Slugify relationship"]
|
||||
|
||||
%% SELF
|
||||
|
||||
SLUGIFY --> CHECK_SELF{"relationship == self"}
|
||||
|
||||
CHECK_SELF -->|Yes| SET_SELF_ONE["Set self = 1"]
|
||||
|
||||
CHECK_SELF -->|No| CHECK_SELF_EXISTS{"self key exists?"}
|
||||
|
||||
CHECK_SELF_EXISTS -->|No| SET_SELF_ZERO["Set self = 0"]
|
||||
|
||||
CHECK_SELF_EXISTS -->|Yes| CHECK_SPOUSE
|
||||
|
||||
SET_SELF_ONE --> CHECK_SPOUSE
|
||||
SET_SELF_ZERO --> CHECK_SPOUSE
|
||||
|
||||
%% SPOUSE
|
||||
|
||||
CHECK_SPOUSE{"relationship == spouse"}
|
||||
|
||||
CHECK_SPOUSE -->|Yes| SET_SPOUSE_ONE["Set spouse = 1"]
|
||||
|
||||
CHECK_SPOUSE -->|No| CHECK_SPOUSE_EXISTS{"spouse key exists?"}
|
||||
|
||||
CHECK_SPOUSE_EXISTS -->|No| SET_SPOUSE_ZERO["Set spouse = 0"]
|
||||
|
||||
CHECK_SPOUSE_EXISTS -->|Yes| CHECK_CHILDREN
|
||||
|
||||
SET_SPOUSE_ONE --> CHECK_CHILDREN
|
||||
SET_SPOUSE_ZERO --> CHECK_CHILDREN
|
||||
|
||||
%% CHILDREN
|
||||
|
||||
CHECK_CHILDREN{"relationship == son OR daughter"}
|
||||
|
||||
CHECK_CHILDREN -->|Yes| INC_CHILDREN["Increment childrens count"]
|
||||
|
||||
CHECK_CHILDREN -->|No| CHECK_CHILDREN_EXISTS{"childrens key exists?"}
|
||||
|
||||
CHECK_CHILDREN_EXISTS -->|No| SET_CHILDREN_ZERO["Set childrens = 0"]
|
||||
|
||||
CHECK_CHILDREN_EXISTS -->|Yes| CHECK_PARENTS
|
||||
|
||||
INC_CHILDREN --> CHECK_PARENTS
|
||||
SET_CHILDREN_ZERO --> CHECK_PARENTS
|
||||
|
||||
%% PARENTS
|
||||
|
||||
CHECK_PARENTS{"relationship == father OR mother"}
|
||||
|
||||
CHECK_PARENTS -->|Yes| INC_PARENTS["Increment parents count"]
|
||||
|
||||
CHECK_PARENTS -->|No| CHECK_PARENTS_EXISTS{"parents key exists?"}
|
||||
|
||||
CHECK_PARENTS_EXISTS -->|No| SET_PARENTS_ZERO["Set parents = 0"]
|
||||
|
||||
CHECK_PARENTS_EXISTS -->|Yes| CHECK_INLAWS
|
||||
|
||||
INC_PARENTS --> CHECK_INLAWS
|
||||
SET_PARENTS_ZERO --> CHECK_INLAWS
|
||||
|
||||
%% PARENTS IN LAW
|
||||
|
||||
CHECK_INLAWS{"relationship == father-in-law OR mother-in-law"}
|
||||
|
||||
CHECK_INLAWS -->|Yes| INC_INLAWS["Increment parents-in-law count"]
|
||||
|
||||
CHECK_INLAWS -->|No| CHECK_INLAW_EXISTS{"parents-in-law key exists?"}
|
||||
|
||||
CHECK_INLAW_EXISTS -->|No| SET_INLAW_ZERO["Set parents-in-law = 0"]
|
||||
|
||||
CHECK_INLAW_EXISTS -->|Yes| NEXT_MEMBER
|
||||
|
||||
INC_INLAWS --> NEXT_MEMBER
|
||||
SET_INLAW_ZERO --> NEXT_MEMBER
|
||||
|
||||
%% LOOP
|
||||
|
||||
NEXT_MEMBER --> MORE_MEMBERS{"More family members?"}
|
||||
|
||||
MORE_MEMBERS -->|Yes| LOOP_MEMBERS
|
||||
|
||||
MORE_MEMBERS -->|No| RETURN_RESULT
|
||||
|
||||
RETURN_RESULT(["Return family_composition"])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -348,12 +430,87 @@ flowchart TD
|
||||
<div class="mermaid-wrapper" id="flowchart-compare-slab">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
J["decode additional_relationship JSON"] --> U["strip either-parents-pil and elders_count"]
|
||||
U --> E{"non-NA rules exist"}
|
||||
E -->|no| F["rack not applicable"]
|
||||
E -->|yes| K["sequential AND each non-NA key"]
|
||||
K -->|incoming matches value or any| Y["rack applicable plus relationship tokens"]
|
||||
K -->|missing key or mismatch| X["rack not applicable"]
|
||||
|
||||
START([Start compare_incoming_family_slab_with_configured_slab])
|
||||
|
||||
START --> INIT_MAP["Initialize relationship mapping"]
|
||||
|
||||
INIT_MAP --> INIT_RESULT["Initialize result
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INIT_RESULT --> READ_CONFIG["Read configured family composition from slab"]
|
||||
|
||||
READ_CONFIG --> REMOVE_KEYS["Remove ignored keys
|
||||
either-parents-pil
|
||||
elders_count"]
|
||||
|
||||
REMOVE_KEYS --> CHECK_CONFIG{"Configured composition has values?"}
|
||||
|
||||
%% EMPTY CONFIGURATION
|
||||
|
||||
CHECK_CONFIG -->|No| RESET_RESULT["Set result as not applicable"]
|
||||
|
||||
RESET_RESULT --> RETURN_RESULT
|
||||
|
||||
%% LOOP START
|
||||
|
||||
CHECK_CONFIG -->|Yes| LOOP_CONFIG{"Loop configured relationships"}
|
||||
|
||||
%% CHECK NA
|
||||
|
||||
LOOP_CONFIG --> CHECK_NA{"Value != NA ?"}
|
||||
|
||||
CHECK_NA -->|No| NEXT_RELATION
|
||||
|
||||
%% CHECK KEY EXISTS
|
||||
|
||||
CHECK_NA -->|Yes| CHECK_KEY_EXISTS{"Relationship exists in incoming composition?"}
|
||||
|
||||
CHECK_KEY_EXISTS -->|No| INVALID_RESULT_1["Set:
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INVALID_RESULT_1 --> BREAK_LOOP
|
||||
|
||||
%% VALUE COMPARISON
|
||||
|
||||
CHECK_KEY_EXISTS -->|Yes| CHECK_MATCH{
|
||||
Incoming count == configured count
|
||||
OR
|
||||
configured value == any
|
||||
}
|
||||
|
||||
%% MATCH FOUND
|
||||
|
||||
CHECK_MATCH -->|Yes| SET_APPLICABLE["Set is_applicable = true"]
|
||||
|
||||
SET_APPLICABLE --> MERGE_MEMBERS["Merge mapped relationships into applicable_members"]
|
||||
|
||||
MERGE_MEMBERS --> NEXT_RELATION
|
||||
|
||||
%% MATCH FAILED
|
||||
|
||||
CHECK_MATCH -->|No| INVALID_RESULT_2["Set:
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INVALID_RESULT_2 --> BREAK_LOOP
|
||||
|
||||
%% LOOP CONTROL
|
||||
|
||||
NEXT_RELATION --> MORE_RELATIONS{"More relationships?"}
|
||||
|
||||
MORE_RELATIONS -->|Yes| LOOP_CONFIG
|
||||
|
||||
MORE_RELATIONS -->|No| RETURN_RESULT
|
||||
|
||||
BREAK_LOOP --> RETURN_RESULT
|
||||
|
||||
%% RETURN
|
||||
|
||||
RETURN_RESULT(["Return result array"])
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -378,13 +535,60 @@ flowchart TD
|
||||
<div class="mermaid-wrapper" id="flowchart-applicable-members">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["applicable_members from compare"] --> B["scan family_data by index"]
|
||||
B --> C{"relationship slug in list"}
|
||||
C -->|yes| D["record index and age from DOB"]
|
||||
C -->|no| B
|
||||
D --> B
|
||||
B -->|done| E["max_count equals number of indexes"]
|
||||
E --> F["walk indexes in order first gets acting_self true remainder false"]
|
||||
|
||||
START([Start get_applicable_familiy_members])
|
||||
|
||||
START --> INIT["Initialize:
|
||||
index = []
|
||||
max_age = []
|
||||
max_count = 0"]
|
||||
|
||||
INIT --> INIT_SLUG["Initialize slug service"]
|
||||
|
||||
INIT_SLUG --> LOOP_MEMBERS{"Loop each family member"}
|
||||
|
||||
%% READ RELATIONSHIP
|
||||
|
||||
LOOP_MEMBERS --> READ_REL["Read relationship from family_member[5]"]
|
||||
|
||||
READ_REL --> SLUGIFY["Slugify relationship"]
|
||||
|
||||
%% CHECK APPLICABLE
|
||||
|
||||
SLUGIFY --> CHECK_APPLICABLE{
|
||||
Relationship exists in applicable_members?
|
||||
}
|
||||
|
||||
%% MATCH FOUND
|
||||
|
||||
CHECK_APPLICABLE -->|Yes| STORE_INDEX["Add member index into result.index"]
|
||||
|
||||
STORE_INDEX --> CALCULATE_AGE["Calculate member age from DOB"]
|
||||
|
||||
CALCULATE_AGE --> STORE_AGE["Add age into result.max_age"]
|
||||
|
||||
STORE_AGE --> NEXT_MEMBER
|
||||
|
||||
%% NO MATCH
|
||||
|
||||
CHECK_APPLICABLE -->|No| NEXT_MEMBER
|
||||
|
||||
%% LOOP CONTROL
|
||||
|
||||
NEXT_MEMBER --> MORE_MEMBERS{"More family members?"}
|
||||
|
||||
MORE_MEMBERS -->|Yes| LOOP_MEMBERS
|
||||
|
||||
%% FINAL COUNT
|
||||
|
||||
MORE_MEMBERS -->|No| CALCULATE_COUNT["Set max_count =
|
||||
count(result.index)"]
|
||||
|
||||
CALCULATE_COUNT --> RETURN_RESULT
|
||||
|
||||
%% RETURN
|
||||
|
||||
RETURN_RESULT(["Return result array"])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
389
app/Views/docs/inception.php
Normal file
389
app/Views/docs/inception.php
Normal file
@ -0,0 +1,389 @@
|
||||
<?php
|
||||
/**
|
||||
* Inception (employee Excel upload) — content only
|
||||
* app/Views/docs/inception.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesOnboardPreprocess)
|
||||
* - app/Controllers/EmployeeController.php (upload entry)
|
||||
* - app/Controllers/JobWorker.php (background jobs)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Inception</strong> here means onboarding employees and dependents from an Excel upload
|
||||
(<code>files.action = inception</code> or related actions like <code>missed_inception</code>,
|
||||
<code>addition</code>, <code>dependent_addition</code>). The same three-step pipeline runs for those
|
||||
actions; this page focuses on the <strong>inception</strong> path.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Manual policy inception (form UI under <code>policy_tranction/inception</code>) is a separate flow
|
||||
in <code>PolicyTransactionController</code> — not covered by these three functions.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel on employee upload"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesOnboardPreprocess"]
|
||||
D --> E["employeesOnboardProcess plus policy_transaction"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|no families inserted| F
|
||||
D -->|OK| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> Headers, column count/order, per-cell type and mandatory rules.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Family-level checks (Self row, duplicates, policy terms, DB conflicts).</li>
|
||||
<li><strong>Step 3 — Preprocess:</strong> Premium via rack rates, then insert employees and inception policy transaction.</li>
|
||||
<li>Large files (> 1 MB) run steps 1–3 as background jobs via <code>JobWorker</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — client, branch, policy, action <strong>Inception</strong>, file upload</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload handler</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pipeline logic</td>
|
||||
<td><code>EmployeeServiceController</code> — the three functions on this page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job dispatch</td>
|
||||
<td><code>app/Controllers/JobWorker.php</code> — maps job names to <code>EmployeeServiceController</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Column / Excel helpers</td>
|
||||
<td><code>app/Helpers/excel_util_helper.php</code> — <code>check_columns_name</code>, custom validators</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload record</td>
|
||||
<td><code>files</code> table via <code>FileModel</code> — <code>status</code>, <code>action</code>, <code>reason</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Premium</td>
|
||||
<td><a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a> — <code>calculate_premium_new</code>, <code>employeesOnboardProcess</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, filter <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — upload + start validation (<code>upload-action-type=inception</code>)</li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — returns <code>files.reason</code> JSON for the error modal</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
REST upload (mobile/HR): <code>POST employeeRest/employeeUpload</code> — same pipeline via
|
||||
<code>EmployeeRestController</code>.
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> stores <strong>client policy id</strong> (<code>client_policies.id</code>),
|
||||
not the insurer policy master id. Slab/rack lookups use this id with <code>client_id</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>File size</th><th>Step 1</th><th>Steps 2–3</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>< 1 MB</td>
|
||||
<td><code>excelFileFormatValidation</code> runs inline in the upload request</td>
|
||||
<td>Always queued: <code>excelFileDataValidation</code> → <code>employeesOnboardPreprocess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>≥ 1 MB</td>
|
||||
<td>Job <code>excelFileFormatValidation</code></td>
|
||||
<td>Same job chain after format passes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
After upload the UI usually shows <code>files.status = inprogress</code> until jobs finish.
|
||||
Poll notifications or refresh the upload list; use <code>/employee/excel_error/{id}</code> when status is <code>failed</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="entry-points">Entry points</h2>
|
||||
|
||||
<p>Job chain for inception (and missed_inception / addition / dependent_addition):</p>
|
||||
<p><code>excelFileFormatValidation</code> → <code>excelFileDataValidation</code> → <code>employeesOnboardPreprocess</code></p>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<p>Runs on the uploaded sheet before any DB business rules.</p>
|
||||
|
||||
<ul>
|
||||
<li>Loads file from <code>writable/uploads/excel/{file_name}</code>.</li>
|
||||
<li>Uses <code>$inception_excel_columns</code> when <code>action</code> is inception (same column set for addition / dependent_addition / missed_inception).</li>
|
||||
<li>Checks policy has terms and slab rates configured — otherwise fails early.</li>
|
||||
<li>Validates each data row: mandatory (by action code <code>I</code>), date/mobile formats, allowed lists, custom helpers (DOB, relationship, SI, mobile duplicate, etc.).</li>
|
||||
<li>Stops at first empty row (treated as end of data).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Common format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory value missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format (e.g. date, mobile)</td></tr>
|
||||
<tr><td>3</td><td>Value not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed (DOB, relationship, SI, etc.)</td></tr>
|
||||
<tr><td>5</td><td>File / policy / slab configuration problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong or out of order</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>On failure: <code>files.status = failed</code>, <code>reason</code> JSON with row/column errors; user notification via pull notification.</p>
|
||||
|
||||
<p><strong>Lead-policy branch:</strong> If inception file is for a policy created from leads (<code>policy_entry_from == 3</code> and <code>is_from_lead</code> set), format validation queues <code>compareMemberDataAndInceptionData</code> instead of going straight to data validation.</p>
|
||||
|
||||
<h3 id="reason-json">files.reason shape (debugging)</h3>
|
||||
|
||||
<p>Stored as JSON string on <code>files.reason</code>. Typical failure payload:</p>
|
||||
<pre><code>{
|
||||
"error_type": 1,
|
||||
"error_summary": { "4": 2, "1": 1 },
|
||||
"error_data": {
|
||||
"3": {
|
||||
"dob": { "error": ["Invalid date format"], "value": "01/01/1990" }
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
<ul>
|
||||
<li><code>error_type</code> — <code>1</code> = format step, <code>2</code> = data step</li>
|
||||
<li><code>error_summary</code> — counts per error code (after aggregation)</li>
|
||||
<li><code>error_data</code> — keyed by <strong>Excel row number</strong> (1-based, header is row 1)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Runs after format passes. Groups rows by <strong>EMP ID</strong> (family) and applies business rules.</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Group rows by emp_id"] --> B{"Self in family?"}
|
||||
B -->|No| C["Error: Self not found"]
|
||||
B -->|Yes| D{"Duplicate name in family?"}
|
||||
D -->|Yes| E["Error: Twofold name"]
|
||||
D -->|No| F{"Emp code already in DB?"}
|
||||
F -->|Yes| G["Error: duplicate emp code"]
|
||||
F -->|No| H{"Dependents match policy terms?"}
|
||||
H -->|No| I["Dependent conflict errors"]
|
||||
H -->|Yes| J["Row OK for this family"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Typical inception checks:</p>
|
||||
<ul>
|
||||
<li><strong>Self</strong> row required (unless GMC parents policy allows otherwise).</li>
|
||||
<li>No duplicate names within the same family in the file.</li>
|
||||
<li>Employee code must not already exist for inception / addition / enrollment.</li>
|
||||
<li>Dependent rules vs <code>policy_terms</code> (family composition, LGBTQ flag, etc.).</li>
|
||||
<li>Name + emp id consistency vs database (<code>name_and_empid_check_in_db</code>).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success for inception: queues job <code>employeesOnboardPreprocess</code>. Other actions queue different jobs (deletion, correction, etc.).</p>
|
||||
|
||||
<h3 id="data-errors">Common data-validation error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>7</td><td>Duplicate name within same family in the Excel file</td></tr>
|
||||
<tr><td>9</td><td>Record already exists in DB (inception / addition)</td></tr>
|
||||
<tr><td>10</td><td>Record not found (used on deletion flows)</td></tr>
|
||||
<tr><td>14</td><td>Self row missing in family</td></tr>
|
||||
<tr><td>26</td><td>Duplicate employee code in file or DB</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="preprocess">Step 3: employeesOnboardPreprocess</h2>
|
||||
|
||||
<p>Calculates premium and writes members to the database.</p>
|
||||
|
||||
<ol>
|
||||
<li>Reload Excel; group by <code>emp_id</code>.</li>
|
||||
<li>For each family: <code>calculate_premium_new()</code> using policy terms + slab rates + rack config.</li>
|
||||
<li><code>employeesOnboardProcess()</code> — insert/update <code>employees</code>, <code>employee_polices</code>, create <code>policy_transaction</code> (inception).</li>
|
||||
<li>If at least one family inserted → <code>files.status = success</code>; else failed with rack-rate message.</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
Optional second path: <code>client_policy_id</code> without <code>file_id</code> — converts enrolled DB members
|
||||
to inception (enrollment → inception), not from Excel.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>TPA batch:</strong> When <code>batch_file_id</code> is present on the job payload, success also queues
|
||||
<code>updateEmployeeDataFromTpa</code>, <code>reconTpaApiDataWithEmployeepolicies</code>, and
|
||||
<code>initializeDeletionProcessForTpaApiData</code> (multi-file TPA reconcile flow).
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">Inception Excel columns</h2>
|
||||
|
||||
<p>
|
||||
Defined in <code>EmployeeServiceController::$inception_excel_columns</code>.
|
||||
Header row must match exactly — <strong>19 columns (A–S)</strong>, row 1 only.
|
||||
Dates use format <code>d-M-Y</code> (e.g. <code>4-Apr-1990</code>).
|
||||
One <strong>Self</strong> row per <strong>EMP ID</strong>; other rows are dependents.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required (inception)</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td>Family key</td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>D</td><td>DOB</td><td>Yes</td><td><code>d-M-Y</code>; age vs relationship checked</td></tr>
|
||||
<tr><td>E</td><td>Gender</td><td>Yes</td><td>M / F (several casings allowed)</td></tr>
|
||||
<tr><td>F</td><td>RELATIONSHIP</td><td>Yes</td><td>Self, Spouse, Son, Daughter, …</td></tr>
|
||||
<tr><td>G</td><td>BASIC COVER SI</td><td>Conditional</td><td>Validated against slab when applicable</td></tr>
|
||||
<tr><td>H</td><td>Date of Coverage</td><td>No</td><td>Mandatory for addition / DA only</td></tr>
|
||||
<tr><td>I</td><td>DOJ</td><td>No</td><td></td></tr>
|
||||
<tr><td>J</td><td>Basic Pay</td><td>No</td><td>Used when policy terms need it</td></tr>
|
||||
<tr><td>K</td><td>Band/Grade</td><td>No</td><td></td></tr>
|
||||
<tr><td>L</td><td>Designation</td><td>No</td><td></td></tr>
|
||||
<tr><td>M</td><td>Phone</td><td>No</td><td>Mobile format; duplicate check</td></tr>
|
||||
<tr><td>N</td><td>Email</td><td>No</td><td>Duplicate check in file</td></tr>
|
||||
<tr><td>O</td><td>PRE EXISTING AILMENTS</td><td>Yes</td><td><code>0</code> or <code>1</code></td></tr>
|
||||
<tr><td>P</td><td>Change event</td><td>No</td><td>Not used for pure inception</td></tr>
|
||||
<tr><td>Q</td><td>Date of exit</td><td>No</td><td>Deletion only</td></tr>
|
||||
<tr><td>R</td><td>Reason for exit</td><td>No</td><td>Deletion only</td></tr>
|
||||
<tr><td>S</td><td>Unit</td><td>No</td><td>Must match branch units when filled</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Sample file: use the download link on the employee upload screen (environment-specific).</p>
|
||||
|
||||
<h2 id="family-example">Family row layout example</h2>
|
||||
|
||||
<p>
|
||||
All rows with the same <strong>EMP ID</strong> (column B) are treated as one family.
|
||||
Step 2 requires exactly one <strong>Self</strong> row in that group; dependents share the same EMP ID.
|
||||
Step 3 runs premium and DB insert once per family.
|
||||
</p>
|
||||
|
||||
<p><strong>Example:</strong> one employee (<code>EMP001</code>) with spouse and son — three data rows plus header.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row</th>
|
||||
<th>S.No</th>
|
||||
<th>EMP ID</th>
|
||||
<th>NAME</th>
|
||||
<th>DOB</th>
|
||||
<th>Gender</th>
|
||||
<th>RELATIONSHIP</th>
|
||||
<th>BASIC COVER SI</th>
|
||||
<th>PED</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td colspan="8"><em>Header row (all 19 columns A–S required in file)</em></td></tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>1</td>
|
||||
<td>EMP001</td>
|
||||
<td>Raj Kumar</td>
|
||||
<td>15-Jan-1985</td>
|
||||
<td>M</td>
|
||||
<td>Self</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>2</td>
|
||||
<td>EMP001</td>
|
||||
<td>Priya Kumar</td>
|
||||
<td>20-Mar-1988</td>
|
||||
<td>F</td>
|
||||
<td>Spouse</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>3</td>
|
||||
<td>EMP001</td>
|
||||
<td>Arjun Kumar</td>
|
||||
<td>10-Jun-2015</td>
|
||||
<td>M</td>
|
||||
<td>Son</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Rules illustrated:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Same EMP ID</strong> on rows 2–4 → one family processed in step 3.</li>
|
||||
<li><strong>Self</strong> on row 2 only — row 3 without Self would fail with code 14.</li>
|
||||
<li><strong>DOB</strong> uses <code>d-M-Y</code>; ages are checked against relationship (e.g. Son vs Self).</li>
|
||||
<li><strong>PED</strong> (PRE EXISTING AILMENTS) = <code>0</code> or <code>1</code> on every member for inception.</li>
|
||||
<li><strong>BASIC COVER SI</strong> must match slab rules when the policy uses SI-based racks (often same amount across the family).</li>
|
||||
<li>Columns H–S can be blank for inception when not mandatory; phone/email must be unique in the file if filled.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
A second employee in the same file uses a <strong>different EMP ID</strong> (e.g. <code>EMP002</code>) with its own Self row — each EMP ID is a separate family loop in preprocess.
|
||||
</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Confirm policy has <code>policy_terms</code> JSON and slab/rack rates for the client policy.</li>
|
||||
<li>Upload via <code>/employee/upload</code> with action <code>inception</code>; note <code>file_id</code> in response or <code>files</code> table.</li>
|
||||
<li>If <code>status = failed</code>, call <code>GET /employee/excel_error/{file_id}</code> or read <code>files.reason</code>.</li>
|
||||
<li>Map <code>error_data</code> row keys back to Excel (row 1 = header).</li>
|
||||
<li>If format passes but preprocess fails with rack message, debug <code>calculate_premium_new</code> (see rack-rate doc) and SI/slab config.</li>
|
||||
<li>For stuck <code>inprogress</code>, check job queue / <code>JobWorker</code> logs for the three job names.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Policy not ready</strong> — missing <code>policy_terms</code> or slab rates fails step 1 with code 5.</li>
|
||||
<li><strong>Missing Self row</strong> — one Self per EMP ID in the file (code 14 in step 2).</li>
|
||||
<li><strong>Wrong header row</strong> — column count or name mismatch (code 5 / 6).</li>
|
||||
<li><strong>Rack rate / SI</strong> — preprocess succeeds only if <code>calculate_premium_new</code> returns data for every family.</li>
|
||||
<li><strong>File status</strong> — watch <code>files.reason</code> JSON for row-level errors after failure.</li>
|
||||
</ul>
|
||||
488
app/Views/docs/non-eb-claims.php
Normal file
488
app/Views/docs/non-eb-claims.php
Normal file
@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/**
|
||||
* Non-EB Claims — content only
|
||||
* app/Views/docs/non-eb-claims.php
|
||||
*
|
||||
* Scope: web MVC only (/non-eb-claim/*). REST API under employeeRest is out of scope.
|
||||
* Sources:
|
||||
* - app/Controllers/NonEbClaimController.php
|
||||
* - app/Views/non_eb_claim_*.php
|
||||
* - app/Config/Routes.php (authMVC group)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
This page documents the <strong>Non-EB Claims</strong> web workflow: list and filter claims,
|
||||
create and edit tickets, status-driven form sections, document uploads, manual email reply,
|
||||
<strong>mail template CRUD</strong>, auto-mail on create/status change, and claim reports.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Scope:</strong> authenticated MVC routes under <code>/non-eb-claim/*</code> only.
|
||||
Mobile/REST endpoints in <code>Api\NonEbClaimApiController</code> are not covered here.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Policy types are limited to <code>policy_type.allocg IN ('Non-EB', 'Marine')</code>.
|
||||
Claim records live in <code>non_eb_ticket_master</code>; claim files use <code>ticket_type = 2</code>
|
||||
in <code>claim_files</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">End-to-end flow</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
L["GET /non-eb-claim/list"] --> F["Filter sidebar → POST /list"]
|
||||
F --> T["Table non_eb_claim_list.php"]
|
||||
T --> A{"Action"}
|
||||
A -->|Add| N["GET /non-eb-claim/new/50 or new/{policy_type_id}"]
|
||||
N --> C["POST /non-eb-claim/create"]
|
||||
C --> AM["sendAutoMailTrigger if template is_auto_mail=1"]
|
||||
A -->|Row click / View| V["GET /non-eb-claim/view/{id}"]
|
||||
V --> U["POST /non-eb-claim/update"]
|
||||
U --> SC{"claim_status_id changed?"}
|
||||
SC -->|yes| AM2["sendAutoMailTrigger"]
|
||||
MT["GET /non-eb-claim/mail_template"] --> CRUD["POST crud_mail_template/1|2|3"]
|
||||
R["GET /non-eb-claim/reports"] --> RP["POST /reports — UI only; see pitfalls"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Typical user path:</strong></p>
|
||||
<ol>
|
||||
<li>Open claim list (default: open claims excluding settled/closed/rejected/withdrawn).</li>
|
||||
<li>Add claim → form opens with canonical status policy type <code>50</code> — see <a href="#policy-type-50-default">why 50 is hardcoded</a>.</li>
|
||||
<li>On save, optional auto-mail fires if a matching template has <code>is_auto_mail = 1</code>.</li>
|
||||
<li>Open claim from list → edit view with history, messages, files, notes.</li>
|
||||
<li>Change status → allowed next statuses from <code>ticket_claim_status.allowed_status</code>; section visibility updates.</li>
|
||||
<li>Configure templates at <code>/non-eb-claim/mail_template</code> (direct URL; not in main Claims sidebar today).</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="policy-type-50-default">Why policy type <code>50</code> is hardcoded (read this before changing Non-EB Claims)</h2>
|
||||
|
||||
<div style="border-left: 4px solid #c0392b; background: #fdf2f2; padding: 1rem 1.25rem; margin: 1.25rem 0;">
|
||||
<p style="margin-top: 0;">
|
||||
<strong>Non-EB has many real policy products</strong> (Fire, Marine, Liability, etc. — each row in
|
||||
<code>policy_type</code> with <code>allocg</code> Non-EB or Marine). Claim <strong>status workflows</strong> are
|
||||
<em>not</em> stored separately per product today. The team configured <strong>one canonical policy type,
|
||||
id <code>50</code></strong>, in the database as the single source of status definitions. The application
|
||||
assumes <strong>every</strong> Non-EB/Marine claim uses that same status set unless you deliberately change
|
||||
backend and frontend.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 id="policy-type-50-assumption">Design assumption</h3>
|
||||
|
||||
<p>
|
||||
Claim statuses live in <code>ticket_claim_status</code>, keyed by <code>ticket_type</code> (which equals
|
||||
<code>policy_type.id</code>). Mail templates in <code>ticket_mail_template</code> also key off
|
||||
<code>ticket_type</code> + <code>trigger_type</code> from that status row.
|
||||
</p>
|
||||
|
||||
<p>Instead of maintaining duplicate status trees for every Non-EB product, developers assumed:</p>
|
||||
|
||||
<blockquote>
|
||||
<p style="margin: 0;">
|
||||
<strong>All Non-EB and Marine policy types share one identical claim status lifecycle.</strong>
|
||||
That lifecycle is configured only under policy type <code>50</code> in
|
||||
<code>ticket_claim_status</code> (and matching templates under <code>ticket_type = 50</code>).
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
New claims opened via the list <strong>Add</strong> button or <strong>New Non EB Claim</strong> menu therefore
|
||||
pass <code>50</code> into the form URL. Status dropdowns, section visibility, and initial status resolution in
|
||||
<code>getClaimStatusForPolicyType($policy_type_id)</code> all use that id on create — not the id of the policy
|
||||
the user later picks from <code>client_policy</code>.
|
||||
</p>
|
||||
|
||||
<h3 id="policy-type-50-vs-selected-policy">Policy type 50 vs policy selected on the form</h3>
|
||||
|
||||
<p>On create (<code>non_eb_claim_form.php</code>):</p>
|
||||
<ul>
|
||||
<li>Hidden <code>policy_type_id</code> is set from the URL segment (typically <code>50</code>) and is <strong>not</strong> updated when the user selects a branch policy.</li>
|
||||
<li>The UI shows the real product name in <code>policy_type_display</code> from the selected policy row (<code>data-policy-type</code>).</li>
|
||||
<li><code>POST /non-eb-claim/create</code> persists <code>policy_type_id</code> from that hidden field — so new tickets from Add often store <code>policy_type_id = 50</code> even when the linked policy is Fire, Marine, etc.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
On edit (<code>non_eb_claim_edit.php</code>), <code>policy_type_id</code> comes from
|
||||
<code>non_eb_ticket_master</code> as saved. Status changes and templates use that stored id.
|
||||
</p>
|
||||
|
||||
<h3 id="policy-type-50-where-hardcoded">Where <code>50</code> appears in code (change all if this design changes)</h3>
|
||||
|
||||
<div class="docs-table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Location</th><th>Usage</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>app/Config/Routes.php</code></td><td><code>GET non-eb-claim/new</code> → <code>claimForm/50</code></td></tr>
|
||||
<tr><td><code>app/Views/non_eb_claim_list.php</code></td><td>DataTable Add button → <code>/non-eb-claim/new/50</code></td></tr>
|
||||
<tr><td><code>app/Views/non_eb_claim_form.php</code></td><td>Hidden <code>policy_type_id</code> from route; status/section AJAX uses this value</td></tr>
|
||||
<tr><td><code>ticket_claim_status</code> (DB)</td><td>Master status rows maintained with <code>ticket_type = 50</code></td></tr>
|
||||
<tr><td><code>ticket_mail_template</code> (DB)</td><td>Non-EB auto-mail templates should use <code>ticket_type = 50</code> if they follow the shared workflow</td></tr>
|
||||
<tr><td><code>Api\NonEbClaimApiController::listClaimStatuses()</code></td><td>API hardcodes <code>where('ticket_type', 50)</code> (out of scope for this page but same assumption)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 id="policy-type-50-future">When a Non-EB product needs a <em>different</em> status set</h3>
|
||||
|
||||
<p>
|
||||
If a new (or existing) policy type must have its <strong>own</strong> statuses, triggers, or allowed transitions
|
||||
(not the shared tree under <code>50</code>), you cannot only change the product row in
|
||||
<code>policy_type</code>. You must update <strong>both backend and frontend</strong>:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Database</strong> — Add full <code>ticket_claim_status</code> rows with
|
||||
<code>ticket_type = <that policy_type.id></code> (claim_status, display_name, trigger_type,
|
||||
allowed_status JSON). Add matching <code>ticket_mail_template</code> rows for that
|
||||
<code>ticket_type</code> if auto-mail applies.</li>
|
||||
<li><strong>Routes / entry URL</strong> — Stop routing every new claim through <code>50</code>: e.g. change
|
||||
<code>claimForm/50</code>, restore policy-type picker modal (commented in
|
||||
<code>non_eb_claim_search.php</code>), or pass the correct <code>policy_type_id</code> per product.</li>
|
||||
<li><strong>List Add button</strong> — Replace hardcoded <code>new/50</code> in
|
||||
<code>non_eb_claim_list.php</code> with the correct id or dynamic selection.</li>
|
||||
<li><strong>Create form</strong> — On policy selection, set hidden <code>#policy_type_id</code> to the real
|
||||
<code>policy_type_id</code> from <code>getBranchAndPolicy</code> (field <code>p.policy_type_id</code> is
|
||||
already returned) so create/update and <code>getVisibleSections</code> use the right status tree.</li>
|
||||
<li><strong>Controller logic</strong> — Ensure <code>getClaimStatusForPolicyType</code>,
|
||||
<code>getTemplateDataByTicketID</code>, and filters that assume a single Non-EB status catalog are tested for
|
||||
the new <code>ticket_type</code>.</li>
|
||||
<li><strong>API</strong> — Replace hardcoded <code>50</code> in <code>listClaimStatuses()</code> if mobile
|
||||
clients need per-product statuses.</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
Until those steps are done, pointing Add at another id without cloning the full status + template set under
|
||||
that id will produce <strong>empty status lists</strong>, <strong>wrong section visibility</strong>, or
|
||||
<strong>missing auto-mail</strong>.
|
||||
</p>
|
||||
|
||||
<h2 id="key-files">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>File / route</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Controller</td><td><code>app/Controllers/NonEbClaimController.php</code></td></tr>
|
||||
<tr><td>Model</td><td><code>app/Models/NonEbTicketMasterModel.php</code></td></tr>
|
||||
<tr><td>List + filters</td><td><code>app/Views/non_eb_claim_search.php</code>, <code>non_eb_claim_list.php</code></td></tr>
|
||||
<tr><td>New claim</td><td><code>app/Views/non_eb_claim_form.php</code></td></tr>
|
||||
<tr><td>View / edit</td><td><code>app/Views/non_eb_claim_edit.php</code></td></tr>
|
||||
<tr><td>Mail templates</td><td><code>app/Views/non_eb_claim_mail_template.php</code></td></tr>
|
||||
<tr><td>Reports</td><td><code>app/Views/non_eb_claim_reports.php</code></td></tr>
|
||||
<tr><td>Routes</td><td><code>app/Config/Routes.php</code> — group <code>/non-eb-claim</code>, filter <code>authMVC</code></td></tr>
|
||||
<tr><td>ACL</td><td><code>app/Config/Acl.php</code> — <code>#^/non-eb-claim#</code> (Claims team roles)</td></tr>
|
||||
<tr><td>App menu</td><td><code>app/Views/layout/header.php</code> — New / List only (EB mail template link is separate)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="route-map">MVC route map</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Route</th><th>Controller</th><th>Purpose</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/list</code></td><td><code>claimList</code></td><td>Search page + default open claims</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/list</code></td><td><code>claimList</code></td><td>Filter → HTML partial for DataTable</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/new</code></td><td><code>claimForm/50</code></td><td>New claim (default policy type 50)</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/new/{policy_type_id}</code></td><td><code>claimForm</code></td><td>New claim for selected product</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/create</code></td><td><code>createClaim</code></td><td>Create ticket + assets + history + auto-mail</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/view/{id}</code></td><td><code>view_claim</code></td><td>Edit layout (<code>non_eb_claim_edit</code>)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/update</code></td><td><code>updateClaim</code></td><td>Update; auto-mail on status change</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/remove?ticket_id=</code></td><td><code>removeClaim</code></td><td>Soft delete (<code>is_active = 0</code>)</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/mail_template</code></td><td><code>mailTemplate</code></td><td>Template list + modal CRUD UI</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/1</code></td><td><code>crudTemplate</code></td><td>Save template</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/2</code></td><td><code>crudTemplate</code></td><td>Fetch one template (edit)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/3</code></td><td><code>crudTemplate</code></td><td>Soft delete template</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/note/1</code></td><td><code>crudNote</code></td><td>Get note</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/note/2</code></td><td><code>crudNote</code></td><td>Save note</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/reply</code></td><td><code>saveReply</code></td><td>Manual outbound mail + message row</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/reports</code></td><td><code>claimReports</code></td><td>Reports UI shell</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/reports</code></td><td><code>claimReports</code></td><td><em>Not implemented in controller</em> — see <a href="#reports">Reports</a></td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getBranchAndPolicy</code></td><td><code>getBranchAndPolicyByClientID</code></td><td>Branches, policies, contacts for client</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getVisibleSections</code></td><td><code>getVisibleSectionsAjax</code></td><td>Section keys for status</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getMoreInfo</code></td><td><code>getMoreInfo</code></td><td>Ticket row JSON</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/uploadFile</code></td><td><code>uploadFile</code></td><td>Claim docs (file or Drive URL)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getClaimFiles</code></td><td><code>getClaimFiles</code></td><td>List files for ticket</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/removeFile?id=</code></td><td><code>removeFile</code></td><td>Soft delete file</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/saveIRDocs</code></td><td><code>saveIRDocs</code></td><td>Persist required-docs JSON on ticket</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/testAutoMail/{id}</code></td><td><code>testAutoMailTrigger</code></td><td>Dev/test auto-mail (optional)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="access">Access control</h2>
|
||||
|
||||
<p>
|
||||
All routes in the <code>/non-eb-claim</code> group use the <code>authMVC</code> filter.
|
||||
<code>Acl.php</code> allows roles <code>HEAD</code>, <code>ADMIN</code>, <code>MANAGER</code>,
|
||||
<code>ACCOUNT_MANAGER</code> on the Claims team.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
List row actions (view / delete) render only for roles <code>1, 2, 5</code> in
|
||||
<code>non_eb_claim_list.php</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="list-flow">List and filter</h2>
|
||||
|
||||
<p><strong>GET <code>/non-eb-claim/list</code></strong> loads <code>non_eb_claim_search.php</code>, which includes the table partial. Initial data comes from <code>claimSearch(1)</code>: active tickets where status display name is <em>not</em> in Claim Settled, Claim Closed, Claim Rejected, Claim Withdrawn.</p>
|
||||
|
||||
<p><strong>Filter sidebar</strong> posts the same URL with criteria (at least one required):</p>
|
||||
<ul>
|
||||
<li><code>policy_type_id</code>, <code>insurer_id</code>, <code>claim_number</code>, <code>nhance_claim_ref_no</code></li>
|
||||
<li><code>client_id</code>, <code>claim_status_id</code> (status options filtered by policy type in JS)</li>
|
||||
<li><code>date_type</code> + <code>start_date</code> / <code>end_date</code> (<code>created_date</code> or <code>updated_date</code>)</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Response is JSON <code>{ status: true, html: "…" }</code>; JS replaces <code>#claim_list_div</code>
|
||||
and re-initializes the DataTable. Row click navigates to <code>/non-eb-claim/view/{id}</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Add</strong> button: <code>window.location.href = '/non-eb-claim/new/50'</code>.
|
||||
Header menu uses <code>/non-eb-claim/new</code> (routes to <code>claimForm/50</code>).
|
||||
See <a href="#policy-type-50-default">Policy type 50</a> for why this id is fixed and what to change if a product needs its own statuses.
|
||||
</p>
|
||||
|
||||
<h2 id="create-edit">Create and edit claim</h2>
|
||||
|
||||
<h3 id="create">Create</h3>
|
||||
|
||||
<ol>
|
||||
<li><code>GET /non-eb-claim/new/{policy_type_id}</code> → <code>getFormData()</code>: ACMs (role 3), clients, insurers, initial claim status for product, visible sections.</li>
|
||||
<li>User selects client → <code>POST getBranchAndPolicy</code> fills branch, policy (Non-EB/Marine only), branch contact.</li>
|
||||
<li>Status change → <code>POST getVisibleSections</code> toggles accordion sections client-side.</li>
|
||||
<li><code>POST /non-eb-claim/create</code> — validation via <code>getValidationRules()</code>, <code>sanitizeInputArrayAdvanced</code>, date normalization, optional asset file upload.</li>
|
||||
<li>Duplicate guard: same <code>client_id</code> + <code>loss_date</code> + <code>policy_no</code> (if policy set) → HTTP 409-style JSON.</li>
|
||||
<li>On success: insert <code>non_eb_ticket_master</code>, <code>saveAssets()</code>, history row, <code>sendAutoMailTrigger()</code>, redirect to list.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="edit">View / edit</h3>
|
||||
|
||||
<p>
|
||||
<code>view_claim($id)</code> loads ticket via <code>NonEbTicketMasterModel::getTicketDataByTicketID()</code>,
|
||||
merges mail template preview (<code>getTemplateDataByTicketID</code> + placeholder replace),
|
||||
messages, history, assets, and reuses the edit view <code>non_eb_claim_edit.php</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>POST /non-eb-claim/update</code> mirrors create validation. If <code>claim_status_id</code> changes,
|
||||
auto-mail runs again. <code>remark_mode=append</code> appends to <code>closure_remark</code> with a separator.
|
||||
</p>
|
||||
|
||||
<h2 id="status-sections">Status-driven sections</h2>
|
||||
|
||||
<p>
|
||||
<code>$statusSectionVisibility</code> in the controller maps each <code>ticket_claim_status.claim_status</code>
|
||||
label to section keys: <code>policy_account</code>, <code>loss_incident</code>, <code>intimation</code>,
|
||||
<code>insured_contact</code>, <code>asset</code>, <code>documents</code>, <code>surveyor</code>, <code>settlement</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Allowed next statuses come from <code>getClaimStatusForPolicyType()</code>: current status plus IDs in
|
||||
<code>allowed_status</code> JSON on the status row. Both create and edit forms call
|
||||
<code>getVisibleSectionsAjax</code> when the user changes status.
|
||||
</p>
|
||||
|
||||
<h2 id="auto-mail">Auto-mail and placeholders</h2>
|
||||
|
||||
<p>Template lookup (<code>NonEbTicketMasterModel::getTemplateDataByTicketID</code>):</p>
|
||||
<ul>
|
||||
<li>Join <code>ticket_claim_status</code> on ticket’s <code>claim_status_id</code> (or explicit <code>status_id</code> for tests).</li>
|
||||
<li>Join <code>ticket_mail_template</code> where <code>ticket_type = policy_type_id</code> AND <code>trigger_type = tcs.trigger_type</code>.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<code>sendAutoMailTrigger($ticket_id)</code> sends only when the matched template has
|
||||
<code>is_auto_mail = 1</code>. Mail goes to <code>insured_contact_email</code> from
|
||||
<code>constructMailContent()</code>; from address <code>claims@nhanceindia.in</code> via <code>MailHelper::send_email()</code>.
|
||||
Successful sends insert a <code>ticket_messages</code> row via <code>autoMessageInsertBasedOnMailResponse()</code>.
|
||||
</p>
|
||||
|
||||
<p>Placeholders (subject/body):</p>
|
||||
|
||||
<div class="docs-table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Token</th><th>Ticket field</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>((ACM))</code></td><td><code>acm</code></td></tr>
|
||||
<tr><td><code>((ACM_CONTACT))</code></td><td><code>acm_mobile</code></td></tr>
|
||||
<tr><td><code>((INSURED_NAME))</code></td><td><code>insured_contact_name</code></td></tr>
|
||||
<tr><td><code>((CORPORATE_NAME))</code></td><td><code>client_name</code></td></tr>
|
||||
<tr><td><code>((CLAIM_NO))</code></td><td><code>claim_number</code></td></tr>
|
||||
<tr><td><code>((POLICY_TYPE))</code></td><td><code>policy_type_name</code></td></tr>
|
||||
<tr><td><code>((NHANCE_REF_NO))</code></td><td><code>nhance_claim_ref_no</code></td></tr>
|
||||
<tr><td><code>((LOSS_DATE))</code></td><td><code>loss_date</code></td></tr>
|
||||
<tr><td><code>((LOSS_LOCATION))</code></td><td><code>loss_location</code></td></tr>
|
||||
<tr><td><code>((NATURE_OF_LOSS))</code></td><td><code>nature_of_loss</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Edit screen also supports <strong>manual reply</strong>: <code>POST /non-eb-claim/reply</code> validates To/Subject,
|
||||
inserts <code>ticket_messages</code>, sends via <code>sendReplyMessage()</code> with placeholder replacement.
|
||||
</p>
|
||||
|
||||
<h2 id="mail-template-crud">Mail template CRUD</h2>
|
||||
|
||||
<p>
|
||||
Page: <code>GET /non-eb-claim/mail_template</code>. DataTable lists rows from
|
||||
<code>ticket_mail_template</code> (<code>is_active = 1</code>). Tooltip on each row shows the
|
||||
matching <code>ticket_claim_status.claim_status</code> for that policy type + trigger type.
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
UI["mail_template UI"] --> S1["POST crud_mail_template/1 Save"]
|
||||
UI --> S2["POST crud_mail_template/2 Fetch"]
|
||||
UI --> S3["POST crud_mail_template/3 Delete"]
|
||||
S1 --> DB["ticket_mail_template"]
|
||||
S2 --> DB
|
||||
S3 --> DB
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="template-fields">Template fields</h3>
|
||||
|
||||
<ul>
|
||||
<li><code>template_name</code>, <code>ticket_type</code> (policy type id), <code>trigger_type</code> (1–9)</li>
|
||||
<li><code>subject</code>, <code>mail_content</code> (Jodit HTML)</li>
|
||||
<li><code>is_auto_mail</code> — checkbox “Auto Mail” (1 = send on create / status change when matched)</li>
|
||||
<li>Optional <code>id</code> on save for update</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<strong>Trigger ↔ status:</strong> Each <code>ticket_claim_status</code> row for a Non-EB/Marine
|
||||
<code>ticket_type</code> has a <code>trigger_type</code>. The template’s <code>trigger_type</code> must match
|
||||
that column for auto-mail and for the “Claim Status” readonly hint (<code>tool_tip</code> from server on fetch).
|
||||
</p>
|
||||
|
||||
<h3 id="template-actions">UI actions</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Action</th><th>Endpoint</th><th>Body</th><th>Result</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Add / Save</td><td><code>POST …/crud_mail_template/1</code></td><td>Form fields + <code>mail_content</code> + <code>is_auto_mail</code></td><td><code>{ status: bool }</code> → reload</td></tr>
|
||||
<tr><td>Edit load</td><td><code>POST …/crud_mail_template/2</code></td><td><code>id</code></td><td><code>{ status, data }</code> opens modal</td></tr>
|
||||
<tr><td>Delete</td><td><code>POST …/crud_mail_template/3</code></td><td><code>id</code></td><td>Soft delete (<code>is_active = 0</code>)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Placeholder dropdown in the modal inserts tokens into subject (focused input) or Jodit body.
|
||||
Validation errors return HTTP 400 with <code>errors</code> map (shown via toastr).
|
||||
</p>
|
||||
|
||||
<h2 id="notes">Notes</h2>
|
||||
|
||||
<p>On the edit screen:</p>
|
||||
<ul>
|
||||
<li><code>POST /non-eb-claim/note/1</code> — <code>id</code> (ticket), optional <code>is_auto_query</code> → fetch active note.</li>
|
||||
<li><code>POST /non-eb-claim/note/2</code> — save note (required 3–1000 chars) via <code>TicketNoteModel</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="documents">Documents and IR checklist</h2>
|
||||
|
||||
<ul>
|
||||
<li><code>uploadFile</code> — multi upload to <code>writable/uploads/claim_files/</code> or Google Drive URLs (<code>file_type</code> 1 vs 2).</li>
|
||||
<li><code>getClaimFiles</code> — lists rows; local files expose download URL <code>downloadClaimFile/{id}</code>.</li>
|
||||
<li><code>removeFile</code> — soft delete by file id.</li>
|
||||
<li><code>saveIRDocs</code> — stores JSON in <code>non_eb_ticket_master.required_docs</code>.</li>
|
||||
<li>Asset spreadsheet on ticket: <code>asset_file</code> under <code>writable/uploads/non_eb_asset_files/</code>; loss description required when file uploaded.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="reports">Reports</h2>
|
||||
|
||||
<p>
|
||||
<code>GET /non-eb-claim/reports</code> renders filters: policy type, ACM name, date range (default last 60 days).
|
||||
<code>generateReport()</code> in the view POSTs to the same URL and expects
|
||||
<code>{ status: true, data: [ rows ] }</code> for DataTable columns (status, policy type, claim/ref, client, insurer, loss fields, surveyor, settlement, ACM, created date).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Gap:</strong> <code>NonEbClaimController::claimReports()</code> only handles GET (layout load).
|
||||
There is no POST handler to return report data — Generate Report will fail until POST logic is added
|
||||
(mirror EB <code>ticket_reports</code> or reuse <code>claimSearch</code> with report-specific selects).
|
||||
</p>
|
||||
|
||||
<h2 id="data-model">Data model (summary)</h2>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Table</th><th>Role</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>non_eb_ticket_master</code></td><td>Main claim ticket</td></tr>
|
||||
<tr><td><code>non_eb_claim_asset</code></td><td>Repeating asset lines per ticket</td></tr>
|
||||
<tr><td><code>ticket_claim_status</code></td><td>Statuses per <code>ticket_type</code> (policy type id); <code>trigger_type</code>, <code>allowed_status</code></td></tr>
|
||||
<tr><td><code>ticket_mail_template</code></td><td>Templates; <code>ticket_type</code> = policy type id</td></tr>
|
||||
<tr><td><code>ticket_history</code></td><td>Field-level audit (status, ACM, priority, …)</td></tr>
|
||||
<tr><td><code>ticket_messages</code></td><td>Outbound mail log</td></tr>
|
||||
<tr><td><code>ticket_notes</code></td><td>User notes per ticket</td></tr>
|
||||
<tr><td><code>claim_files</code></td><td>Attachments; <code>ticket_type = 2</code> for Non-EB</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="controller-reference">Controller reference</h2>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead><tr><th>Method</th><th>Used for</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>claimList</code> / <code>claimSearch</code></td><td>List UI and filtered HTML</td></tr>
|
||||
<tr><td><code>claimForm</code> / <code>getFormData</code></td><td>New claim form bootstrap</td></tr>
|
||||
<tr><td><code>view_claim</code></td><td>Edit view</td></tr>
|
||||
<tr><td><code>createClaim</code> / <code>updateClaim</code></td><td>Persist ticket</td></tr>
|
||||
<tr><td><code>getClaimStatusForPolicyType</code> / <code>getVisibleSections*</code></td><td>Status dropdown + sections</td></tr>
|
||||
<tr><td><code>mailTemplate</code> / <code>crudTemplate</code></td><td>Template admin</td></tr>
|
||||
<tr><td><code>sendAutoMailTrigger</code> / <code>constructMailContent</code></td><td>Automated email</td></tr>
|
||||
<tr><td><code>saveReply</code> / <code>getTicketMessage</code></td><td>Manual email thread</td></tr>
|
||||
<tr><td><code>crudNote</code></td><td>Notes</td></tr>
|
||||
<tr><td><code>uploadFile</code> / <code>getClaimFiles</code> / <code>removeFile</code> / <code>saveIRDocs</code></td><td>Documents</td></tr>
|
||||
<tr><td><code>saveAssets</code> / <code>getAssets</code></td><td>Asset grid</td></tr>
|
||||
<tr><td><code>claimHistory</code> / <code>putHistoryAfterInsert</code></td><td>Audit trail</td></tr>
|
||||
<tr><td><code>getBranchAndPolicyByClientID</code></td><td>Client cascade</td></tr>
|
||||
<tr><td><code>claimReports</code></td><td>Reports page (GET only today)</td></tr>
|
||||
<tr><td><code>testAutoMailTrigger</code></td><td>Dev preview/send test</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="developer-steps">Developer checklist</h2>
|
||||
|
||||
<ol>
|
||||
<li>Ensure <code>ticket_claim_status</code> rows exist per Non-EB/Marine <code>policy_type.id</code> with correct <code>trigger_type</code> and <code>allowed_status</code>.</li>
|
||||
<li>Create mail templates at <code>/non-eb-claim/mail_template</code> with matching <code>ticket_type</code> + <code>trigger_type</code>; enable Auto Mail only where intended.</li>
|
||||
<li>Verify insured email is present before relying on auto-mail.</li>
|
||||
<li>Before changing Add/new URLs: read <a href="#policy-type-50-default">Policy type 50</a> — clone statuses + templates in DB and update every hardcoded <code>50</code> if a product needs its own workflow.</li>
|
||||
<li>Implement POST branch in <code>claimReports()</code> if reports Generate must work.</li>
|
||||
<li>Claim files: always set <code>ticket_type = 2</code> in new file-related code paths.</li>
|
||||
<li>ACL: extend <code>#^/non-eb-claim#</code> if new roles need access.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Template mismatch:</strong> No row in <code>ticket_mail_template</code> for policy type + status <code>trigger_type</code> → no auto-mail and empty reply preview.</li>
|
||||
<li><strong>Reports POST missing:</strong> UI POSTs to <code>/non-eb-claim/reports</code> but controller only loads view on GET.</li>
|
||||
<li><strong>Duplicate claims:</strong> Same client + loss date + policy number blocked on create.</li>
|
||||
<li><strong>Policy type 50 assumption:</strong> Shared status catalog under DB <code>ticket_type = 50</code>; Add/new routes and hidden form field use <code>50</code> — not the policy picked on the form. Different per-product statuses require full backend + frontend changes — <a href="#policy-type-50-future">checklist</a>.</li>
|
||||
<li><strong>Menu vs Non-EB templates:</strong> Sidebar “Mail Template” points to EB <code>/ticket/mail_template</code>, not Non-EB.</li>
|
||||
<li><strong>Soft deletes:</strong> Remove claim/file/template sets <code>is_active = 0</code>; list queries filter active only.</li>
|
||||
<li><strong>Asset file:</strong> Upload without loss description fails validation on create/update.</li>
|
||||
<li><strong>REST API separate:</strong> Mobile create/list under <code>employeeRest</code> / <code>Api\NonEbClaimApiController</code> — different validation and flows.</li>
|
||||
</ul>
|
||||
638
app/Views/docs/non-eb-opportunities.php
Normal file
638
app/Views/docs/non-eb-opportunities.php
Normal file
@ -0,0 +1,638 @@
|
||||
<?php
|
||||
/**
|
||||
* Non-EB Opportunities — content only
|
||||
* app/Views/docs/non-eb-opportunities.php
|
||||
*
|
||||
* Scope: list-view flow only — create/edit form, then table actions on /leads/list.
|
||||
* Sources:
|
||||
* - app/Views/leads_list.php (Non-EB action buttons)
|
||||
* - app/Views/leads_non_eb.php (add/edit form)
|
||||
* - app/Views/leads_form_handler.php
|
||||
* - app/Controllers/LeadsController.php (line ~4793+ and createLead / sendMail paths)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
This page documents the <strong>Non-EB Opportunities</strong> workflow from the
|
||||
<strong>Opportunities list</strong> (<code>/leads/list</code>). A user creates a Non-EB opportunity,
|
||||
then progresses through RFQ → QCR → mail actions → placement — all from the list row action menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Non-EB rows are identified by <code>leads.lead_form_type = 2</code> (EB = <code>1</code>).
|
||||
RFQ and QCR are managed in <strong>Google Sheets</strong>; sheet IDs are stored in
|
||||
<code>leads.misc</code> JSON.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">End-to-end flow</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["/leads/list — Add → Non-EB"] --> B["Add form leads_non_eb.php"]
|
||||
B --> C["POST /leads/create"]
|
||||
C --> D["status: queued"]
|
||||
D --> E["Action: RFQ"]
|
||||
E --> F["GET /leads/createRfqSheet"]
|
||||
F --> G["status: rfq_created"]
|
||||
G --> H["Action: QCR"]
|
||||
H --> I["GET /leads/createQcrSheet"]
|
||||
I --> J["status: qcr_created"]
|
||||
J --> K["Action: Send Internal Mail"]
|
||||
K --> L["POST /leads/sendMail internal"]
|
||||
L --> M["Action: Send Insurer Mail"]
|
||||
M --> N["POST /leads/sendMail insurer → rfq_sent"]
|
||||
N --> O["Action: Send Client Mail"]
|
||||
O --> P["POST /leads/sendMail client → qcr_sent"]
|
||||
P --> Q["Action: Placement"]
|
||||
Q --> R["POST /leads/sendMail placement → won"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Typical sequence from the list:</strong></p>
|
||||
<ol>
|
||||
<li><strong>Create</strong> opportunity (form submit) → <code>queued</code></li>
|
||||
<li><strong>RFQ</strong> — create/open Google Sheet → <code>rfq_created</code></li>
|
||||
<li><strong>QCR</strong> — copy RFQ sheet → <code>qcr_created</code></li>
|
||||
<li><strong>Send Internal Mail</strong> — team mail with RFQ or QCR attachment (by current status)</li>
|
||||
<li><strong>Send Insurer Mail</strong> — RFQ sheet attached → <code>rfq_sent</code></li>
|
||||
<li><strong>Send Client Mail</strong> — QCR sheet attached → <code>qcr_sent</code></li>
|
||||
<li><strong>Placement</strong> — placement sheet + policy/payment fields → <code>won</code></li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
<strong>Edit</strong> is available at any stage from the same action menu and reuses the add form
|
||||
with pre-filled data (<code>getLeadNonEB</code> + <code>POST /leads/create</code> with lead id).
|
||||
</p>
|
||||
|
||||
<h2 id="key-files">Key files</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>File</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>List + action dropdown</td><td><code>app/Views/leads_list.php</code></td></tr>
|
||||
<tr><td>Non-EB add/edit form</td><td><code>app/Views/leads_non_eb.php</code></td></tr>
|
||||
<tr><td>EB vs Non-EB form include</td><td><code>app/Views/leads_form_handler.php</code></td></tr>
|
||||
<tr><td>All backend logic</td><td><code>app/Controllers/LeadsController.php</code></td></tr>
|
||||
<tr><td>Google Sheets</td><td><code>GoogleSheetLib</code>, <code>Config\RfqConfig</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="google-sheet-config">Google Sheet config</h2>
|
||||
|
||||
<p>
|
||||
Non-EB RFQ, QCR, and Placement sheets are <strong>Google Drive files</strong> created at runtime by
|
||||
<code>GoogleSheetLib</code> using a service account. Only <strong>RFQ</strong> needs a pre-configured
|
||||
template per product; QCR and Placement are copies of the lead’s RFQ/QCR sheets.
|
||||
</p>
|
||||
|
||||
<h3 id="gsheet-app-drive-flow">App ↔ Google Drive flow</h3>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TB
|
||||
subgraph setup ["One-time / per product setup"]
|
||||
T["Drive: master RFQ template per product"]
|
||||
PT["policy_type.misc.rfq_template_sheet_id"]
|
||||
ENV[".env RFQ_PARENT_FOLDER_ID"]
|
||||
SA["Service account JSON + share folder/templates"]
|
||||
end
|
||||
|
||||
subgraph rfq ["RFQ — createRfqSheet"]
|
||||
R1["Read template ID from policy_type.misc"]
|
||||
R1 --> C1["Drive copyTemplate → parent folder"]
|
||||
C1 --> F1["Sheets find/replace placeholders"]
|
||||
F1 --> P1["Drive applyPermissions viewers"]
|
||||
P1 --> PR1["Sheets applyProtections"]
|
||||
PR1 --> S1["Save leads.misc.rfq_sheet_id"]
|
||||
end
|
||||
|
||||
subgraph qcr ["QCR — createQcrSheet"]
|
||||
S1 --> C2["Drive copyTemplate from rfq_sheet_id"]
|
||||
C2 --> P2["applyPermissions viewers"]
|
||||
P2 --> S2["Save leads.misc.qcr_sheet_id"]
|
||||
end
|
||||
|
||||
subgraph placement ["Placement — createAndDownloadPlacementSheet"]
|
||||
S2 --> C3["Drive copyTemplate from qcr_sheet_id"]
|
||||
C3 --> P3["applyPermissions viewers"]
|
||||
P3 --> S3["Save leads.misc.placement_sheet_id"]
|
||||
S3 --> X1["Drive export .xlsx → mail attachment"]
|
||||
end
|
||||
|
||||
subgraph mail ["Mail — downloadFileFromGoogleSheet"]
|
||||
S1 --> X2["Export RFQ or QCR as Excel"]
|
||||
S2 --> X2
|
||||
end
|
||||
|
||||
PT --> R1
|
||||
ENV --> C1
|
||||
T --> PT
|
||||
SA --> C1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Library:</strong> <code>app/Libraries/GoogleSheetLib.php</code> — auth via service account JSON at
|
||||
<code>{project-root}/nhance-ee8d1-e3c5269b1ec7.json</code>, scopes <code>DRIVE</code> + <code>SPREADSHEETS</code>.</p>
|
||||
|
||||
<h3 id="gsheet-prerequisites">What must be done before RFQ / QCR</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>Before</th><th>Why</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Google service account JSON present; account email shared on template folder + each template sheet</td>
|
||||
<td><code>GoogleSheetLib</code> cannot copy or edit without Drive access</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><code>RFQ_PARENT_FOLDER_ID</code> set in <code>.env</code></td>
|
||||
<td>Target folder for every copied RFQ/QCR/Placement file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Master RFQ Google Sheet template created per product (Fire, Marine, GPA, etc.) with placeholder tokens</td>
|
||||
<td>RFQ copy source — one template per <code>policy_type</code> row</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><code>policy_type.misc</code> JSON updated with <code>rfq_template_sheet_id</code> for that product</td>
|
||||
<td><code>createRfqSheet()</code> reads this; fails with “RFQ template not found” if missing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Non-EB opportunity created with correct <code>policy_type_id</code> and <code>rfq_qcr_viewers</code> emails</td>
|
||||
<td>Lead must exist; viewers become sheet editors after copy</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Before QCR:</strong> RFQ action completed (<code>leads.misc.rfq_sheet_id</code> set)</td>
|
||||
<td>QCR copies the lead’s RFQ sheet, not the policy template</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><strong>Before Placement mail:</strong> QCR action completed (<code>leads.misc.qcr_sheet_id</code> set)</td>
|
||||
<td>Placement copies the QCR sheet</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="gsheet-product-config">Where to configure sheet ID per product</h3>
|
||||
|
||||
<p>
|
||||
Template sheet IDs are stored <strong>per product</strong> on the <code>policy_type</code> table —
|
||||
one row per product (Fire, Marine, Burglary, etc.). The lead’s selected
|
||||
<code>policy_type_id</code> determines which template is copied when RFQ is clicked.
|
||||
</p>
|
||||
|
||||
<p><strong>Database column:</strong> <code>policy_type.misc</code> (JSON text)</p>
|
||||
|
||||
<pre><code>{
|
||||
"rfq_template_sheet_id": "1GXNDNXoWriClb5HCqPYd2GAY1T8aie_Hos0yAOoTaC0"
|
||||
}</code></pre>
|
||||
|
||||
<p>Example — set or update for policy type id <code>12</code>:</p>
|
||||
|
||||
<pre><code>UPDATE policy_type
|
||||
SET misc = JSON_SET(COALESCE(misc, '{}'), '$.rfq_template_sheet_id', 'YOUR_GOOGLE_DRIVE_FILE_ID')
|
||||
WHERE id = 12;</code></pre>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
There is no admin UI field for <code>rfq_template_sheet_id</code> today — configure via DB (or extend
|
||||
<code>MasterController::editPolicyType</code> / <code>policy_type_onboarding</code> if you add a form field).
|
||||
Model allow-list: <code>app/Models/PolicyTypeModel.php</code> includes <code>misc</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>How to find a template file ID:</strong></p>
|
||||
<ul>
|
||||
<li>From the Google Sheet URL: <code>https://docs.google.com/spreadsheets/d/<strong>{FILE_ID}</strong>/edit</code></li>
|
||||
<li>CLI — list all sheets in the RFQ parent folder:
|
||||
<code>php public/index.php cli/list-sheet-folder-files {RFQ_PARENT_FOLDER_ID}</code>
|
||||
→ writes <code>sheetid.json</code> with <code>name</code> + <code>sheetId</code> pairs
|
||||
(<code>GoogleSheetController::listFolderSheetFilesCli</code>)</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>QCR and Placement:</strong> no separate template ID per product. They always copy from the lead’s existing sheets:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Stage</th><th>Copy source</th><th>Stored on lead</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RFQ</td><td><code>policy_type.misc.rfq_template_sheet_id</code></td><td><code>leads.misc.rfq_sheet_id</code></td></tr>
|
||||
<tr><td>QCR</td><td><code>leads.misc.rfq_sheet_id</code></td><td><code>leads.misc.qcr_sheet_id</code></td></tr>
|
||||
<tr><td>Placement</td><td><code>leads.misc.qcr_sheet_id</code> (filename: QCR → Placement)</td><td><code>leads.misc.placement_sheet_id</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="gsheet-app-config">App-level config files</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Setting</th><th>Location</th><th>Purpose</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>RFQ_PARENT_FOLDER_ID</code></td>
|
||||
<td><code>.env</code> → <code>Config\RfqConfig::$rfqParentFolderId</code></td>
|
||||
<td>Google Drive folder where copied RFQ/QCR/Placement files are created</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rfqPlaceholders</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Maps template tokens like <code>{{INSURED_NAME}}</code> to lead field keys filled on RFQ create</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rfqClaimsPlaceholder</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Default <code>{{CLAIMS_DETAILS}}</code> — multi-row claims table from <code>fin_years_claims</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>protections</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Locked cell ranges on new RFQ sheets only (e.g. <code>RFQ Page!B12:C12</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Service account key</td>
|
||||
<td><code>nhance-ee8d1-e3c5269b1ec7.json</code> (project root)</td>
|
||||
<td>Google API authentication for all sheet operations</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Placeholder tokens to embed in each product’s RFQ master template:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Token in template</th><th>Filled from</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>{{INSURED_NAME}}</code></td><td>Client name / short name</td></tr>
|
||||
<tr><td><code>{{COMMUNICATION_ADDRESS}}</code></td><td>Client or custom field address</td></tr>
|
||||
<tr><td><code>{{GST}}</code> / <code>{{PAN}}</code></td><td>Lead GST / PAN</td></tr>
|
||||
<tr><td><code>{{POLICY_PERIOD}}</code></td><td>Policy start – end dates</td></tr>
|
||||
<tr><td><code>{{OPPORTUNITY_TYPE}}</code></td><td>Fresh / Renewal label</td></tr>
|
||||
<tr><td><code>{{RISK_LOCATION}}</code> / <code>{{OCCUPANCY}}</code></td><td>Custom policy-type fields</td></tr>
|
||||
<tr><td><code>{{CLAIMS_DETAILS}}</code></td><td>Claim history table (renewal leads)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
After RFQ copy, editors are granted from the lead’s <code>rfq_qcr_viewers</code> JSON email list (selected on the create/edit form).
|
||||
The same list is applied to QCR and Placement copies.
|
||||
</p>
|
||||
|
||||
<h2 id="create-edit">Step 1 — Create opportunity</h2>
|
||||
|
||||
<h3 id="create-ui">From the list</h3>
|
||||
<ol>
|
||||
<li>Open <code>/leads/list</code> → click <strong>Add</strong>.</li>
|
||||
<li>Modal <strong>Select Opportunity Type</strong> → choose <strong>Non-EB</strong> (<code>lead_form_type=2</code>).</li>
|
||||
<li>Redirect: <code>GET /util/getLeadNonEB/2/0</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="create-form">Form (<code>leads_non_eb.php</code>)</h3>
|
||||
<ul>
|
||||
<li>Sections: Opportunity Details, Client Information, Branch Information, Policy Details, Sales & Assignment.</li>
|
||||
<li>Policy types limited to <code>allocg</code> = <strong>Non-EB</strong> or <strong>Marine</strong>.</li>
|
||||
<li>Policy type change → <code>GET /util/getPolicyTypeFields</code> → dynamic fields in <code>#appendArea</code>.</li>
|
||||
<li>Renewal types may show claim history rows → stored as <code>fin_years_claims</code> JSON.</li>
|
||||
<li><code>rfq_qcr_viewers</code> (emails) become Google Sheet editors later.</li>
|
||||
<li>Submit → AJAX <code>POST /leads/create</code> with <code>lead_form_type=2</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="create-backend">Controller: <code>createLead()</code></h3>
|
||||
<ul>
|
||||
<li><code>prepareLeadData()</code> → <code>prepareSingleLeadData()</code> (one lead row; EB uses multi-row).</li>
|
||||
<li>Non-EB validation: policy type, DOC/DOE, claim-history rows when <code>claim_history=1</code>.</li>
|
||||
<li><code>insertNewLead()</code> — no demography background job (EB-only).</li>
|
||||
<li>Initial status: <code>queued</code> (In-Queued).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="edit-flow">Edit flow</h2>
|
||||
|
||||
<p>From the list action menu → <strong>Edit</strong> (available for both EB and Non-EB):</p>
|
||||
|
||||
<ol>
|
||||
<li>JS: <code>getLeadsDataForEdit(lead_id, lead_form_type, actual_lead_id)</code></li>
|
||||
<li>Redirect: <code>GET /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code></li>
|
||||
<li>Controller: <code>getLeadNonEB($type, $actual_lead_id, $id)</code> (~line 4818)
|
||||
<ul>
|
||||
<li>Loads master data (issuer, policy types, sales team, etc.)</li>
|
||||
<li>When <code>$id</code> present: fetches <code>lead_edit_data</code>, files, custom fields, date formatting</li>
|
||||
<li>Builds dynamic policy HTML via <code>generateViewPageHtml()</code></li>
|
||||
<li>Renewal Non-EB: renders <code>rfq/claims_details_non_eb</code> into <code>claims_details_html</code></li>
|
||||
<li>Returns layout with <code>leads_form_handler</code> → includes <code>leads_non_eb.php</code> when <code>selected_lead_type != 1</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Form pre-fills client, branch, policy, files, viewers, status, lost reason, etc.</li>
|
||||
<li>Submit same endpoint: <code>POST /leads/create</code> with hidden <code>id</code> → <code>updateOldLead()</code></li>
|
||||
</ol>
|
||||
|
||||
<h2 id="list-actions">Steps 2–7 — List table actions (Non-EB)</h2>
|
||||
|
||||
<p>
|
||||
When <code>lead_form_type === 2</code>, the row action menu in
|
||||
<code>app/Views/leads_list.php</code> (lines ~297–343) uses modal/AJAX flows instead of
|
||||
navigating to <code>/rfq/list/{id}/1|2</code> (EB behaviour).
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong>Menu order on screen</strong> (after opportunity is created): Edit → RFQ → QCR →
|
||||
Send Internal Mail → Send Insurer Mail → Send Client Mail → Placement → Email History.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="action-map">Action → view handler → controller endpoint</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table class="docs-action-map">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Action</th>
|
||||
<th>Visible when</th>
|
||||
<th>View (JS) / handler class</th>
|
||||
<th>Controller endpoint(s)</th>
|
||||
<th>Status after</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>—</td>
|
||||
<td><strong>Edit</strong></td>
|
||||
<td>Always (EB + Non-EB)</td>
|
||||
<td><code>getLeadsDataForEdit()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/create</code> (update when <code>id</code> posted)
|
||||
</td>
|
||||
<td>User-selected on form</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><strong>RFQ</strong></td>
|
||||
<td><code>lead_form_type === 2</code> only</td>
|
||||
<td><code>.btnRfqSheetList</code> → <code>createRfqSheetFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/createRfqSheet?lead_id={id}</code><br>
|
||||
<code>LeadsController::createRfqSheet()</code> — opens Google Sheet URL in new tab
|
||||
</td>
|
||||
<td><code>rfq_created</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><strong>QCR</strong></td>
|
||||
<td>
|
||||
Non-EB; status not <code>queued</code> or <code>rfq_created</code>;
|
||||
role <code>1, 5, 2, 3</code> or Business Support team
|
||||
</td>
|
||||
<td><code>.btnQcrSheetList</code> → <code>createQcrSheetFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/createQcrSheet?lead_id={id}</code><br>
|
||||
<code>LeadsController::createQcrSheet()</code>
|
||||
</td>
|
||||
<td><code>qcr_created</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><strong>Send Internal Mail</strong></td>
|
||||
<td>Non-EB; <code>status != queued</code></td>
|
||||
<td><code>.btnInternalMailList</code> → <code>openInternalMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=rfq</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=internal</code>
|
||||
</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><strong>Send Insurer Mail</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnInsurerMailList</code> → <code>openInsurerMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=rfq</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=insurer</code> (RFQ sheet attach)
|
||||
</td>
|
||||
<td><code>rfq_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><strong>Send Client Mail</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnClientMailList</code> → <code>openClientMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=qcr</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=client</code> (QCR sheet attach)
|
||||
</td>
|
||||
<td><code>qcr_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Placement</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnPlacementList</code> → <code>openPlacementFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/rfq/placementData/{id}</code><br>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=placement</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=placement</code>
|
||||
</td>
|
||||
<td><code>won</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>—</td>
|
||||
<td><strong>Email History</strong></td>
|
||||
<td>Always (EB + Non-EB)</td>
|
||||
<td><code>.btnHistory</code> → <code>getLeadsDataForMailHistory()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/util/getLeadEmailHistory/{id}</code> → <code>getLeadEmailHistory()</code>
|
||||
</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><strong>EB contrast</strong> (same menu, <code>lead_form_type === 1</code>): RFQ/QCR are links to
|
||||
<code>/rfq/list/{id}/1</code> and <code>/rfq/list/{id}/2</code>; internal/insurer/client/placement mail items are not shown.</p>
|
||||
|
||||
<p><strong>Shared mail modal helpers</strong> (all in <code>leads_list.php</code>):</p>
|
||||
<ul>
|
||||
<li><code>POST /leads/uploadLeadAttachment</code> — optional extra files (<code>lead_id</code>, <code>docs_name</code>, file)</li>
|
||||
<li><code>constructURL_ForInternalMailSend()</code>, insurer/client via <code>constructURL_ForInsurerOrClientMailSend()</code>, placement via <code>constructURL_ForPlacementMailSend()</code></li>
|
||||
<li>External CC disclaimer modal (<code>#external_cc_disclaimer_modal</code>) before client/placement send to non-user emails</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="step-rfq">Step 2 — RFQ (<code>createRfqSheet()</code>)</h2>
|
||||
|
||||
<ol>
|
||||
<li>Load lead + <code>policy_type.misc.rfq_template_sheet_id</code>.</li>
|
||||
<li>If <code>misc.rfq_sheet_id</code> exists → return existing Google Sheet URL.</li>
|
||||
<li>Copy template via <code>GoogleSheetLib::copyTemplate()</code> into <code>RfqConfig::rfqParentFolderId</code>.</li>
|
||||
<li>Fill placeholders (<code>buildRfqPlaceholderData()</code>): client, GST, PAN, policy period, claims table.</li>
|
||||
<li>Grant editors from <code>leads.rfq_qcr_viewers</code> email JSON.</li>
|
||||
<li>Save <code>misc.rfq_sheet_id</code>; set <code>status = rfq_created</code>.</li>
|
||||
<li>UI opens sheet in a new browser tab.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="step-qcr">Step 3 — QCR (<code>createQcrSheet()</code>)</h2>
|
||||
|
||||
<ol>
|
||||
<li>Requires <code>misc.rfq_sheet_id</code> — returns 400 if RFQ not created yet.</li>
|
||||
<li>If <code>misc.qcr_sheet_id</code> exists → return existing URL.</li>
|
||||
<li>Copy the RFQ sheet (not the policy template) with a QCR filename.</li>
|
||||
<li>Same editor permissions from <code>rfq_qcr_viewers</code>.</li>
|
||||
<li>Save <code>misc.qcr_sheet_id</code>; set <code>status = qcr_created</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="step-mails">Steps 4–6 — Mail actions</h2>
|
||||
|
||||
<h3 id="mail-template">Load template — <code>getLeadMailTemplate()</code></h3>
|
||||
<p>
|
||||
Called before each mail modal opens. Query params:
|
||||
<code>lead_id</code> + <code>template_type</code> (<code>rfq</code> | <code>qcr</code> | <code>placement</code>).
|
||||
Returns subject, HTML body, and attachment checkbox HTML from <code>lead_files</code>.
|
||||
</p>
|
||||
|
||||
<h3 id="send-mail">Send — <code>sendMailWithAttachement()</code></h3>
|
||||
|
||||
<p>For Non-EB (<code>lead_form_type == 2</code>), the Excel attachment comes from Google Sheets:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>recipient_type</th><th>Sheet used</th><th>Status after send</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>internal</code></td>
|
||||
<td>QCR if status is <code>qcr_created</code>/<code>qcr_sent</code>, else RFQ</td>
|
||||
<td>Unchanged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>insurer</code></td>
|
||||
<td><code>misc.rfq_sheet_id</code></td>
|
||||
<td><code>rfq_sent</code> (unless already past that stage)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>client</code></td>
|
||||
<td><code>misc.qcr_sheet_id</code></td>
|
||||
<td><code>qcr_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>placement</code></td>
|
||||
<td><code>createAndDownloadPlacementSheet()</code> — copies QCR sheet</td>
|
||||
<td><code>won</code> + saves placement/payment/installment fields</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Implementation detail: <code>downloadFileFromGoogleSheet()</code> exports the chosen sheet to a temp
|
||||
<code>.xlsx</code> under <code>writable/tmp/</code> before <code>MailHelper::send_email()</code>.</p>
|
||||
|
||||
<h2 id="step-placement">Step 7 — Placement</h2>
|
||||
|
||||
<p>Opened from list action → <code>openPlacementFromList(leadId)</code>:</p>
|
||||
<ol>
|
||||
<li><code>GET /rfq/placementData/{id}</code> — pre-fills policy dates, premium, CD, installments, contacts.</li>
|
||||
<li><code>GET /leads/mailTemplate?template_type=placement</code> — subject/body/attachments.</li>
|
||||
<li>User fills placement modal: dates, premium/CD/total, installment rows, To/CC, optional external CC.</li>
|
||||
<li><code>POST /leads/sendMail</code> with <code>recipient_type=placement</code>.</li>
|
||||
<li>Backend copies QCR → placement Google Sheet, attaches Excel, updates lead to <code>won</code>,
|
||||
persists <code>placement_date</code>, <code>premium_amount</code>, <code>cd_amount</code>, installments, etc.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="status-lifecycle">Status lifecycle</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Status</th><th>Label</th><th>Set by</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>queued</code></td><td>In-Queued</td><td>Create form (default)</td></tr>
|
||||
<tr><td><code>rfq_created</code></td><td>RFQ Created</td><td><code>createRfqSheet()</code></td></tr>
|
||||
<tr><td><code>rfq_sent</code></td><td>RFQ Sent</td><td>Insurer mail</td></tr>
|
||||
<tr><td><code>qcr_created</code></td><td>QCR Created</td><td><code>createQcrSheet()</code></td></tr>
|
||||
<tr><td><code>qcr_sent</code></td><td>QCR Sent</td><td>Client mail</td></tr>
|
||||
<tr><td><code>won</code></td><td>Won</td><td>Placement mail</td></tr>
|
||||
<tr><td><code>lost</code></td><td>Lost</td><td>User sets on edit form + lost reason</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="misc-json">leads.misc JSON</h2>
|
||||
|
||||
<pre><code>{
|
||||
"rfq_sheet_id": "…",
|
||||
"qcr_sheet_id": "…",
|
||||
"placement_sheet_id": "…"
|
||||
}</code></pre>
|
||||
|
||||
<h2 id="controller-reference">Controller reference (list flow)</h2>
|
||||
|
||||
<p>Methods in <code>LeadsController.php</code> used by the list Non-EB flow:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Triggered from</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>createLead()</code></td><td>Form submit (create + edit)</td></tr>
|
||||
<tr><td><code>getLeadNonEB()</code></td><td>Add / Edit navigation</td></tr>
|
||||
<tr><td><code>getPolicyTypeFields()</code></td><td>Policy type change on form</td></tr>
|
||||
<tr><td><code>createRfqSheet()</code></td><td>RFQ action</td></tr>
|
||||
<tr><td><code>createQcrSheet()</code></td><td>QCR action</td></tr>
|
||||
<tr><td><code>getLeadMailTemplate()</code></td><td>All mail modals</td></tr>
|
||||
<tr><td><code>getPlacementData()</code></td><td>Placement modal pre-fill</td></tr>
|
||||
<tr><td><code>uploadLeadAttachment()</code></td><td>Attachment upload in mail modals</td></tr>
|
||||
<tr><td><code>sendMailWithAttachement()</code></td><td>All mail sends + placement</td></tr>
|
||||
<tr><td><code>downloadFileFromGoogleSheet()</code></td><td>Mail attachment (internal/insurer/client)</td></tr>
|
||||
<tr><td><code>createAndDownloadPlacementSheet()</code></td><td>Placement mail attachment</td></tr>
|
||||
<tr><td><code>getLeadEmailHistory()</code></td><td>Email History modal</td></tr>
|
||||
<tr><td><code>buildRfqPlaceholderData()</code></td><td>RFQ sheet placeholder fill (helper)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer checklist</h2>
|
||||
|
||||
<ol>
|
||||
<li>Complete <a href="#google-sheet-config">Google Sheet config</a> for each Non-EB/Marine <code>policy_type</code> before first RFQ.</li>
|
||||
<li>Set <code>policy_type.misc.rfq_template_sheet_id</code> per product (see <a href="#gsheet-product-config">sheet ID per product</a>).</li>
|
||||
<li>Configure <code>RFQ_PARENT_FOLDER_ID</code> in <code>.env</code> and share folder/templates with the service account.</li>
|
||||
<li>Ensure placeholders in master templates match <code>Config\RfqConfig::$rfqPlaceholders</code>.</li>
|
||||
<li>Gate new list actions with <code>$isNonEb</code> in <code>leads_list.php</code>.</li>
|
||||
<li>Respect sheet order: RFQ → QCR → mails → placement.</li>
|
||||
<li>QCR button: roles <code>[1, 5, 2, 3]</code> or <code>BUSINESS_SUPPORT_TEAM_ID</code> in user team.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>QCR before RFQ:</strong> <code>createQcrSheet</code> fails if <code>rfq_sheet_id</code> is missing.</li>
|
||||
<li><strong>Queued status:</strong> Mail and placement actions are hidden until status moves past <code>queued</code>.</li>
|
||||
<li><strong>Insurer mail needs RFQ sheet;</strong> client mail needs QCR sheet — create sheets before sending.</li>
|
||||
<li><strong>Internal mail attachment</strong> picks RFQ or QCR based on current lead status.</li>
|
||||
<li><strong>Edit vs create:</strong> same <code>POST /leads/create</code>; presence of hidden <code>id</code> triggers update.</li>
|
||||
</ul>
|
||||
@ -39,6 +39,7 @@ $base = base_url();
|
||||
/* ─── TOKENS ─────────────────────────────── */
|
||||
:root {
|
||||
--sidebar-w : 260px;
|
||||
--toc-w : 200px;
|
||||
--topbar-h : 56px;
|
||||
--bg : #ffffff;
|
||||
--bg2 : #f7f8fa;
|
||||
@ -143,6 +144,8 @@ $base = base_url();
|
||||
/* ─── LAYOUT WRAPPER ─────────────────────── */
|
||||
.docs-layout {
|
||||
display : flex;
|
||||
width : 100%;
|
||||
align-items: flex-start;
|
||||
margin-top: var(--topbar-h);
|
||||
min-height: calc(100vh - var(--topbar-h));
|
||||
}
|
||||
@ -208,6 +211,15 @@ $base = base_url();
|
||||
.callout.success { background: #f0fdf4; border-color: #4ade80; color: #15803d; }
|
||||
|
||||
/* ─── TABLES ─────────────────────────────── */
|
||||
.docs-table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.docs-table-wrap table { margin: 0; min-width: 640px; }
|
||||
.docs-table-wrap--fluid table { min-width: 0; width: 100%; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13.5px; }
|
||||
th {
|
||||
background : var(--bg2);
|
||||
|
||||
@ -27,10 +27,11 @@ $toc = $toc ?? [];
|
||||
<style>
|
||||
/* ─── MAIN CONTENT ───────────────────────── */
|
||||
.docs-main {
|
||||
flex : 1;
|
||||
max-width: 760px;
|
||||
padding : 48px 56px 80px;
|
||||
min-width: 0;
|
||||
flex : 1 1 auto;
|
||||
min-width : 0;
|
||||
max-width : none;
|
||||
width : 0; /* grow to fill space between sidebar and right TOC */
|
||||
padding : 48px 40px 80px 48px;
|
||||
}
|
||||
|
||||
.docs-main__breadcrumb {
|
||||
@ -62,13 +63,18 @@ $toc = $toc ?? [];
|
||||
|
||||
/* ─── RIGHT TOC ──────────────────────────── */
|
||||
.docs-toc {
|
||||
width : 200px;
|
||||
min-width: 200px;
|
||||
padding : 56px 20px 0;
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
align-self: flex-start;
|
||||
width : var(--toc-w);
|
||||
min-width : var(--toc-w);
|
||||
max-width : var(--toc-w);
|
||||
padding : 56px 20px 48px 16px;
|
||||
border-left: 1px solid var(--border);
|
||||
background : var(--bg);
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
max-height : calc(100vh - var(--topbar-h) - 48px);
|
||||
overflow-y : auto;
|
||||
align-self : flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -109,6 +115,14 @@ $toc = $toc ?? [];
|
||||
border-radius : 999px;
|
||||
padding : 3px 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.docs-toc { display: none; }
|
||||
.docs-main {
|
||||
width: auto;
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
|
||||
259
app/Views/docs/si-enhancement.php
Normal file
259
app/Views/docs/si-enhancement.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* SI Enhancement (employee Excel upload) — content only
|
||||
* app/Views/docs/si-enhancement.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesSIEnhanceProcess)
|
||||
* - app/Controllers/EmployeeController.php (employeesUplodWithEvents)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>SI Enhancement</strong> increases sum insured for active members on a policy via Excel upload
|
||||
(<code>files.action = si_enhancement</code>). The process recalculates premium from slab/rack rates and
|
||||
creates <strong>pending SI endorsements</strong> on <code>employee_polices</code> — SI and premium are not
|
||||
updated in place until endorsements are applied downstream.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Core logic: <code>EmployeeServiceController::employeesSIEnhanceProcess</code>.
|
||||
Premium math uses <code>transform_si_excel_row_to_calculatable_format</code> and
|
||||
<code>premium_calculation_manager</code> (see
|
||||
<a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a>).
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action si_enhancement"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesSIEnhanceProcess job"]
|
||||
D --> E["Recalc premium plus pending SI endorsements"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|loop done| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 5 columns (A–E); <strong>Augmented SI</strong> validated against slabs (<code>check_si</code>).</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist on active policy; code <strong>10</strong> if not found.</li>
|
||||
<li><strong>Step 3 — SI enhance:</strong> Picks rack rate by relationship, recalculates premium, inserts 3 pending endorsements per member.</li>
|
||||
<li>One Excel row = one member SI change (not whole-family in one row).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>SI Enhancement</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SI process</td>
|
||||
<td><code>employeesSIEnhanceProcess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Onboard SI path</td>
|
||||
<td><code>employeesSIEnhanceProcessWhileOnbboard</code> — SI during dependent addition onboard (not Excel)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeesSIEnhanceProcess</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Helpers</td>
|
||||
<td><code>excel_util_helper.php</code> — <code>transform_si_excel_row_to_calculatable_format</code>, <code>premium_calculation_manager</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=si_enhancement</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — validation errors</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the client policy id. Slab rates load via
|
||||
<code>getPolicySlabRatesForEmpOnboard(policy_id, client_id)</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>SI enhance</td><td>Job <code>employeesSIEnhanceProcess</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$si_enhance_excel_columns</code> — <strong>5 columns (A–E)</strong>.</li>
|
||||
<li>Action code <code>SI</code> for mandatory rules.</li>
|
||||
<li><strong>Augmented SI</strong> (D): custom <code>check_si</code> against policy slabs (same helper as inception SI column).</li>
|
||||
<li><strong>Date of SI Enhancement</strong> (E): <code>d-M-Y</code>.</li>
|
||||
<li>Policy must have slab/rack configuration or format step fails early (code 5).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed (e.g. invalid Augmented SI for slab)</td></tr>
|
||||
<tr><td>5</td><td>File / policy / slab problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p><code>name_and_empid_check_in_db</code> for <code>si_enhancement</code>:</p>
|
||||
<ul>
|
||||
<li>Row must match active employee + active <code>employee_polices</code> on the policy.</li>
|
||||
<li>Not found → error code <strong>10</strong> (“Record Not found”).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeesSIEnhanceProcess</code>.</p>
|
||||
|
||||
<h2 id="si-process">Step 3: employeesSIEnhanceProcess</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read row Augmented SI and date"] --> B["Match active employee and policy"]
|
||||
B --> C{"Pending SI on basic_cover_si?"}
|
||||
C -->|Yes| D["Skip row log error"]
|
||||
C -->|No| E["Resolve rack rate by relationship"]
|
||||
E --> F["premium_calculation_manager"]
|
||||
F --> G["Insert 3 pending endorsements actions si"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per row:</strong></p>
|
||||
<ol>
|
||||
<li>Strip number formatting from Augmented SI (<code>removeNumberFormatting</code>).</li>
|
||||
<li>Load employee (emp code + name + branch) and active <code>employee_polices</code> row.</li>
|
||||
<li>Find applicable slab/rack rate from member <strong>relationship</strong> (Son/Daughter → childrens, parents, etc.).</li>
|
||||
<li>Build calculatable member payload via <code>transform_si_excel_row_to_calculatable_format</code> (uses family max age/count).</li>
|
||||
<li><code>premium_calculation_manager</code> → new premium for the augmented SI.</li>
|
||||
<li>Insert pending endorsements on <code>employee_polices</code> (<code>actions = si</code>):</li>
|
||||
</ol>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>field_name</th><th>new_value source</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>basic_cover_si</code></td><td>Excel Augmented SI (column D)</td></tr>
|
||||
<tr><td><code>premium</code></td><td>Recalculated premium from rack logic</td></tr>
|
||||
<tr><td><code>si_enhancement_date</code></td><td>Excel Date of SI Enhancement (column E)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Sets <code>files.status = success</code> when the loop completes. Skipped rows (duplicate pending SI, missing member)
|
||||
are logged only — the file still succeeds.
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">SI Enhancement Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB</td></tr>
|
||||
<tr><td>D</td><td>Augmented SI</td><td>Yes</td><td>New SI; validated via <code>check_si</code></td></tr>
|
||||
<tr><td>E</td><td>Date of SI Enhancement</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Enhance Self SI</strong> — one row:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Augmented SI</th><th>Date of SI Enhancement</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Raj Kumar</td><td>1000000</td><td>19-May-2026</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Enhance spouse only</strong> — separate row (premium uses that member’s relationship for rack selection):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Augmented SI</th><th>Date of SI Enhancement</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Priya Kumar</td><td>500000</td><td>19-May-2026</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Confirm slab/rack rates exist for the client policy (same as inception).</li>
|
||||
<li>Upload with <code>upload-action-type=si_enhancement</code>; note <code>file_id</code>.</li>
|
||||
<li>On validation failure, check <code>/employee/excel_error/{file_id}</code> — codes <strong>4</strong> (SI/slab) or <strong>10</strong> (member missing).</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'si'</code>, <code>status = 'pending'</code> — expect up to 3 rows per member (same <code>group_key</code>).</li>
|
||||
<li>Compare <code>new_value</code> on <code>basic_cover_si</code> and <code>premium</code> to Excel and rack expectations.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Slab / rack not configured</strong> — format step or premium calc fails; relationship must map to a non-zero rack key.</li>
|
||||
<li><strong>Augmented SI vs slab</strong> — <code>check_si</code> in step 1 must pass before the job runs.</li>
|
||||
<li><strong>Pending SI already exists</strong> — duplicate upload for same member skipped until prior endorsement cleared.</li>
|
||||
<li><strong>Per-member rows</strong> — enhancing whole family requires one row per member (unlike deletion Self = whole family).</li>
|
||||
<li><strong>File success vs rows</strong> — <code>files.status = success</code> does not guarantee every row created endorsements.</li>
|
||||
<li><strong>Not live SI update</strong> — <code>employee_polices.basic_cover_si</code> changes after endorsement processing.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Related:
|
||||
<a href="<?= base_url('docs/inception') ?>">Inception</a>,
|
||||
<a href="<?= base_url('docs/correction') ?>">Correction</a>,
|
||||
<a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a>.
|
||||
</p>
|
||||
@ -163,7 +163,8 @@
|
||||
</h4> -->
|
||||
</div>
|
||||
<form role="form" class="parsley-examples" method="post" id="leads_form_id"
|
||||
enctype="multipart/form-data">
|
||||
enctype="multipart/form-data"
|
||||
data-parsley-excluded="input[type=button], input[type=submit], input[type=reset], input[type=hidden], [disabled], :hidden, .select2-search__field">
|
||||
|
||||
<input type="hidden" name="id" id="leads_primarykey">
|
||||
<input type="hidden" name="lead_form_type" id="lead_form_type_id" value="<?= isset($selected_lead_type) ? $selected_lead_type : 1 ?>">
|
||||
@ -487,7 +488,7 @@
|
||||
|
||||
function restoreActualLeadGstNumber() {
|
||||
if ($('#actual_lead_id').val() && $('#actual_lead_id').val() != 0 && actualLeadGstNumber) {
|
||||
$('#gst').val(actualLeadGstNumber);
|
||||
$('#gst').val(String(actualLeadGstNumber).trim().toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
@ -770,18 +771,7 @@
|
||||
leadTypeBsedHideAndShow(lead_type, false, policy_type_id)
|
||||
|
||||
|
||||
if (lead_type == 1) {
|
||||
$('.claim-row').hide();
|
||||
} else {
|
||||
$('.claim-row').show();
|
||||
if (policy_type_id == 1) {
|
||||
$('.gpaClaimFileds').show();
|
||||
$('.lifeClaimFields').hide();
|
||||
} else if (policy_type_id == 6 || policy_type_id == 7) {
|
||||
$('.gpaClaimFileds').hide();
|
||||
$('.lifeClaimFields').show();
|
||||
}
|
||||
}
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
|
||||
if (lead_type != 1 && policy_type_id != 1 && policy_type_id != 6 && policy_type_id != 7) {
|
||||
// updateRenewalFields(dataIncrement);
|
||||
@ -1160,11 +1150,9 @@
|
||||
leadTypeBsedHideAndShow(lead_type)
|
||||
|
||||
|
||||
if (lead_type == 1) {
|
||||
$('.claim-row').hide();
|
||||
} else if (!shouldShowClaimHistorySwitch(lead_type, policy_type_id)) {
|
||||
$('.claim-row').show();
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
|
||||
if (!shouldShowClaimHistorySwitch(lead_type, policy_type_id)) {
|
||||
if (policy_type_id == 1) {
|
||||
$('.gpaClaimFileds').show();
|
||||
$('.lifeClaimFields').hide();
|
||||
@ -1774,15 +1762,56 @@
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$('.claim-row').each(function() {
|
||||
const isNil = $(this).find('.nil-claims-checkbox').prop('checked');
|
||||
$(this).find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
});
|
||||
|
||||
if (typeof window.prepareLeadsFormForSubmit === 'function') {
|
||||
window.prepareLeadsFormForSubmit();
|
||||
}
|
||||
|
||||
var isValid = $('#leads_form_id').parsley().validate();
|
||||
|
||||
if (!isValid) {
|
||||
$('#leads_form_id').find('input, select, textarea').each(function() {
|
||||
if ($(this).parsley().isValid() === false && !$(this).val()) {
|
||||
console.log('Empty field ID:', this.id);
|
||||
var invalidFields = typeof window.getLeadsFormInvalidFields === 'function'
|
||||
? window.getLeadsFormInvalidFields()
|
||||
: [];
|
||||
|
||||
if (invalidFields.length) {
|
||||
console.table(invalidFields);
|
||||
invalidFields.forEach(function(f) {
|
||||
console.log('Invalid field:', f.label || f.name || f.id, f);
|
||||
});
|
||||
|
||||
var firstInvalid = invalidFields[0];
|
||||
var fieldLabel = firstInvalid.label || firstInvalid.name || firstInvalid.id || 'A required field';
|
||||
var fieldMessage = (firstInvalid.messages && firstInvalid.messages.length)
|
||||
? firstInvalid.messages[0]
|
||||
: (firstInvalid.isEmpty ? 'This field is required.' : 'Please check this field.');
|
||||
|
||||
if (typeof toastr !== 'undefined') {
|
||||
toastr.warning(fieldLabel + ': ' + fieldMessage, 'Validation');
|
||||
}
|
||||
});
|
||||
console.log('Form is Empty', 'Warning');
|
||||
|
||||
if (firstInvalid.id) {
|
||||
var $invalidField = $('#' + firstInvalid.id);
|
||||
if ($invalidField.length) {
|
||||
var scrollTarget = $invalidField.closest('.form-group, .card, .dynamic-form-row');
|
||||
if (scrollTarget.length) {
|
||||
$('html, body').animate({
|
||||
scrollTop: scrollTarget.offset().top - 100
|
||||
}, 300);
|
||||
}
|
||||
$invalidField.focus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('Form validation failed');
|
||||
if (typeof toastr !== 'undefined') {
|
||||
toastr.warning('Please correct the highlighted fields.', 'Validation');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1803,12 +1832,11 @@
|
||||
const jsonString = JSON.stringify(salse_person_id);
|
||||
console.log('jsonString', jsonString);
|
||||
|
||||
// Append the JSON string to the FormData object
|
||||
formData.append('salse_person_id', jsonString);
|
||||
// Convert the claim experience array to JSON
|
||||
const finyearJsonString = gatherClaimExperienceData(policy_type_ids);
|
||||
console.log('finyearJsonString', finyearJsonString);
|
||||
formData.append('finyear', finyearJsonString);
|
||||
syncClaimHistoryFieldsToFormData(formData);
|
||||
let claimHistoryStatus = $("#claim_history").length > 0 && $("#claim_history").prop("checked") ? 1 : 0;
|
||||
formData.set('claim_history', claimHistoryStatus);
|
||||
|
||||
@ -2065,6 +2093,25 @@
|
||||
|
||||
$(".claim-row").each(function() {
|
||||
|
||||
let isNilClaims = $(this).find(".nil-claims-checkbox").prop("checked");
|
||||
|
||||
if (isNilClaims) {
|
||||
let year = $(this).find("[name='first_year[]']").val();
|
||||
claimData.push({
|
||||
"year": year,
|
||||
"emp_id": "Nil",
|
||||
"emp_name": "Nil",
|
||||
"gender": "Nil",
|
||||
"designation": "Nil",
|
||||
"sum_insured": "Nil",
|
||||
"death_date": "Nil",
|
||||
"cause_of_death": "Nil",
|
||||
"settled": "Nil",
|
||||
"nil_claims": 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let year = $(this).find("[name='first_year[]']").val();
|
||||
let claimAmount = $(this).find("[name='first_claim_amount[]']").val();
|
||||
let claimStatus = $(this).find("[name='first_claim_status[]']").val();
|
||||
@ -2088,6 +2135,7 @@
|
||||
"death_date": deathDate,
|
||||
"cause_of_death": causeOfDeath,
|
||||
"settled": claimAmount,
|
||||
"nil_claims": 0,
|
||||
});
|
||||
});
|
||||
|
||||
@ -2196,8 +2244,37 @@
|
||||
return ['1', '6', '7'].includes(String(policyTypeId));
|
||||
}
|
||||
|
||||
function getClaimHistoryLabel(leadType) {
|
||||
return String(leadType) === '1' ? 'Mortality Claims' : 'Claims History';
|
||||
}
|
||||
|
||||
function shouldShowClaimHistorySwitch(leadType, policyTypeId) {
|
||||
return (leadType == 2 || leadType == 3) && isClaimHistoryPolicyType(policyTypeId);
|
||||
return ['1', '2', '3'].includes(String(leadType)) && isClaimHistoryPolicyType(policyTypeId);
|
||||
}
|
||||
|
||||
function updateClaimRowVisibility(fallbackPolicyTypeId = null) {
|
||||
const leadType = $('#lead_type').val();
|
||||
let policyTypeId = fallbackPolicyTypeId;
|
||||
|
||||
if (policyTypeId == null || policyTypeId === '') {
|
||||
const $policySelect = $('[id^="policy_type_id_"]').first();
|
||||
policyTypeId = $policySelect.length ? $policySelect.val() : '';
|
||||
}
|
||||
|
||||
if (!shouldShowClaimHistorySwitch(leadType, policyTypeId)) {
|
||||
$('.claim-row').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('#claim_history').length) {
|
||||
if ($('#claim_history').prop('checked')) {
|
||||
$('.claim-row').show();
|
||||
} else {
|
||||
$('.claim-row').hide();
|
||||
}
|
||||
} else {
|
||||
$('.claim-row').show();
|
||||
}
|
||||
}
|
||||
|
||||
function appendThreeYearsClaims(count) {
|
||||
@ -2210,20 +2287,21 @@
|
||||
|
||||
let lead_type = $('#lead_type').val();
|
||||
let showClaimHistorySwitch = shouldShowClaimHistorySwitch(lead_type, policy_type_id);
|
||||
let claimHistoryLabel = getClaimHistoryLabel(lead_type);
|
||||
|
||||
console.log('claimIndex from parent', claimIndex);
|
||||
let increment = claimIndex;
|
||||
|
||||
let claimsFields = `${showClaimHistorySwitch && !document.querySelector('#claim_history') ? `<div class="custom-control custom-switch">
|
||||
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" checked>
|
||||
<label class="custom-control-label" for="claim_history">Claims History</label>
|
||||
<label class="custom-control-label" for="claim_history">${claimHistoryLabel}</label>
|
||||
</div><br>` : ``}`;
|
||||
|
||||
claimsFields += `
|
||||
<div class="row claim-row">
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_year_${increment}">Year<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-year-group">
|
||||
<label for="first_year_${increment}">Year<span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control first_year_ claim-input" id="first_year_${increment}" name="first_year[]">
|
||||
<option value="">Select Year</option>
|
||||
<?php foreach ($lastFiveYears as $year) {
|
||||
@ -2233,18 +2311,27 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="emp_id_${increment}">Emp ID<span class="text-danger">*</span></label>
|
||||
<label>Nil Claims</label>
|
||||
<input type="hidden" class="nil-claims-value" name="nil_claims[]" value="0">
|
||||
<div class="custom-control custom-checkbox" style="padding-top: 8px;">
|
||||
<input type="checkbox" class="custom-control-input nil-claims-checkbox" id="nil_claims_${increment}" onchange="nilClaimsToggle(this)">
|
||||
<label class="custom-control-label" for="nil_claims_${increment}">Nil Claims</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="emp_id_${increment}">Emp ID<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_id_${increment}" name="emp_id[]">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="emp_name_${increment}">Employee Name<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="emp_name_${increment}">Employee Name<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_name_${increment}" name="emp_name[]">
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="gender_${increment}">Gender<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="gender_${increment}">Gender<span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control claim-input" id="gender_${increment}" name="gender[]">
|
||||
<option value="">Select Gender</option>
|
||||
<option value="Female">Female</option>
|
||||
@ -2252,26 +2339,26 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="designation_${increment}">Designation <span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="designation_${increment}">Designation <span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="designation_${increment}" name="designation[]">
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="sum_insured_${increment}">Sum Insured <span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="sum_insured_${increment}">Sum Insured <span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="sum_insured_${increment}" name="sum_insured[]">
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_death_date_${increment}">Date of Death<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_death_date_${increment}">Date of Death<span class="text-danger claim-required-star">*</span></label>
|
||||
<div class="input-icon">
|
||||
<input type="text" class="form-control death_date flatpickr-date claim-input" id="first_death_date_${increment}" name="first_death_date[]" autocomplete="off">
|
||||
<i class="mdi mdi-calendar-blank-outline additional-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_cause_of_death_${increment}">Nature/Cause Of Death <span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_cause_of_death_${increment}">Nature/Cause Of Death <span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control claim-input" id="first_cause_of_death_${increment}" name="first_cause_of_death[]">
|
||||
<option value="">Select Cause of Death</option>
|
||||
<?php foreach ($causeOfDeath as $cause => $death_value) {
|
||||
@ -2280,8 +2367,8 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount_${increment}">Claim/Settled Amount<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_amount_${increment}">Claim/Settled Amount<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_amount_${increment}" name="first_claim_amount[]">
|
||||
</div>
|
||||
|
||||
@ -2345,6 +2432,8 @@
|
||||
|
||||
if (showClaimHistorySwitch) {
|
||||
claimHistoryToggle();
|
||||
} else {
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
}
|
||||
|
||||
toggleRequiredFields();
|
||||
@ -2363,15 +2452,198 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isClaimRowNilClaims($row) {
|
||||
return $row.find('.nil-claims-checkbox').prop('checked');
|
||||
}
|
||||
|
||||
function setClaimSelectValue($select, value) {
|
||||
if (!$select.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($select.find('option[value="' + value + '"]').length === 0) {
|
||||
$select.append($('<option>', { value: value, text: value }));
|
||||
}
|
||||
|
||||
$select.val(value);
|
||||
|
||||
if ($select.hasClass('select2-hidden-accessible')) {
|
||||
$select.trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
function resetClaimYearField($yearField) {
|
||||
if (!$yearField.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$yearField.find('option[value="Nil"]').remove();
|
||||
|
||||
if ($yearField.val() === 'Nil') {
|
||||
$yearField.val('').trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
function nilClaimsToggle(checkbox) {
|
||||
const $row = $(checkbox).closest('.claim-row');
|
||||
const isNil = $(checkbox).prop('checked');
|
||||
const $yearField = $row.find('[name="first_year[]"]');
|
||||
|
||||
resetClaimYearField($yearField);
|
||||
|
||||
$row.find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
$row.toggleClass('claim-row-nil', isNil);
|
||||
$row.find('.claim-field-group .claim-required-star').toggle(!isNil);
|
||||
|
||||
$row.find('.claim-input').not('[name="first_year[]"]').each(function() {
|
||||
const $field = $(this);
|
||||
|
||||
if (isNil) {
|
||||
if ($field.is('select')) {
|
||||
setClaimSelectValue($field, 'Nil');
|
||||
} else {
|
||||
$field.val('Nil');
|
||||
}
|
||||
|
||||
$field.prop('disabled', true).removeAttr('required');
|
||||
|
||||
if ($field.hasClass('select2-hidden-accessible')) {
|
||||
$field.next('.select2-container').addClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if ($field.parsley) {
|
||||
$field.parsley().reset();
|
||||
}
|
||||
} else {
|
||||
$field.prop('disabled', false);
|
||||
|
||||
if ($field.hasClass('select2-hidden-accessible')) {
|
||||
$field.next('.select2-container').removeClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if ($field.is('select')) {
|
||||
$field.find('option[value="Nil"]').remove();
|
||||
$field.val('').trigger('change');
|
||||
} else {
|
||||
$field.val('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const claimHistoryOn = !$('#claim_history').length || $('#claim_history').prop('checked');
|
||||
|
||||
if (claimHistoryOn) {
|
||||
$yearField.prop('required', true).prop('disabled', false);
|
||||
|
||||
if ($yearField.hasClass('select2-hidden-accessible')) {
|
||||
$yearField.next('.select2-container').removeClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if (!isNil) {
|
||||
$row.find('.claim-input').not('[name="first_year[]"]').prop('required', true);
|
||||
}
|
||||
}
|
||||
|
||||
toggleRequiredFields();
|
||||
|
||||
if (typeof window.refreshLeadsFormValidation === 'function') {
|
||||
window.refreshLeadsFormValidation();
|
||||
}
|
||||
}
|
||||
|
||||
function initNilClaimsRows() {
|
||||
$('.claim-row .nil-claims-checkbox:checked').each(function() {
|
||||
nilClaimsToggle(this);
|
||||
});
|
||||
}
|
||||
|
||||
function syncClaimHistoryFieldsToFormData(formData) {
|
||||
const $rows = $('.claim-row');
|
||||
|
||||
if (!$rows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isEbRow = $rows.first().find('[name="emp_id[]"]').length > 0;
|
||||
const fieldNames = isEbRow
|
||||
? ['first_year[]', 'nil_claims[]', 'emp_id[]', 'emp_name[]', 'gender[]', 'designation[]', 'sum_insured[]', 'first_death_date[]', 'first_cause_of_death[]', 'first_claim_amount[]']
|
||||
: ['first_year[]', 'nil_claims[]', 'first_policy_type_[]', 'first_date_of_loss_[]', 'first_cause_of_loss[]', 'first_claim_amount[]', 'first_settled_amount[]', 'first_claim_status[]'];
|
||||
|
||||
fieldNames.forEach(function(name) {
|
||||
if (typeof formData.delete === 'function') {
|
||||
formData.delete(name);
|
||||
}
|
||||
});
|
||||
|
||||
$rows.each(function() {
|
||||
const $row = $(this);
|
||||
const isNil = $row.find('.nil-claims-checkbox').prop('checked');
|
||||
|
||||
$row.find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
formData.append('first_year[]', $row.find('[name="first_year[]"]').val() || '');
|
||||
formData.append('nil_claims[]', isNil ? '1' : '0');
|
||||
|
||||
if (isEbRow) {
|
||||
if (isNil) {
|
||||
formData.append('emp_id[]', 'Nil');
|
||||
formData.append('emp_name[]', 'Nil');
|
||||
formData.append('gender[]', 'Nil');
|
||||
formData.append('designation[]', 'Nil');
|
||||
formData.append('sum_insured[]', 'Nil');
|
||||
formData.append('first_death_date[]', 'Nil');
|
||||
formData.append('first_cause_of_death[]', 'Nil');
|
||||
formData.append('first_claim_amount[]', 'Nil');
|
||||
} else {
|
||||
formData.append('emp_id[]', $row.find('[name="emp_id[]"]').val() || '');
|
||||
formData.append('emp_name[]', $row.find('[name="emp_name[]"]').val() || '');
|
||||
formData.append('gender[]', $row.find('[name="gender[]"]').val() || '');
|
||||
formData.append('designation[]', $row.find('[name="designation[]"]').val() || '');
|
||||
formData.append('sum_insured[]', $row.find('[name="sum_insured[]"]').val() || '');
|
||||
formData.append('first_death_date[]', $row.find('[name="first_death_date[]"]').val() || '');
|
||||
formData.append('first_cause_of_death[]', $row.find('[name="first_cause_of_death[]"]').val() || '');
|
||||
formData.append('first_claim_amount[]', $row.find('[name="first_claim_amount[]"]').val() || '');
|
||||
}
|
||||
} else if (isNil) {
|
||||
formData.append('first_policy_type_[]', 'Nil');
|
||||
formData.append('first_date_of_loss_[]', 'Nil');
|
||||
formData.append('first_cause_of_loss[]', 'Nil');
|
||||
formData.append('first_claim_amount[]', 'Nil');
|
||||
formData.append('first_settled_amount[]', 'Nil');
|
||||
formData.append('first_claim_status[]', 'Nil');
|
||||
} else {
|
||||
formData.append('first_policy_type_[]', $row.find('[name="first_policy_type_[]"]').val() || '');
|
||||
formData.append('first_date_of_loss_[]', $row.find('[name="first_date_of_loss_[]"]').val() || '');
|
||||
formData.append('first_cause_of_loss[]', $row.find('[name="first_cause_of_loss[]"]').val() || '');
|
||||
formData.append('first_claim_amount[]', $row.find('[name="first_claim_amount[]"]').val() || '');
|
||||
formData.append('first_settled_amount[]', $row.find('[name="first_settled_amount[]"]').val() || '');
|
||||
formData.append('first_claim_status[]', $row.find('[name="first_claim_status[]"]').val() || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function claimHistoryToggle() {
|
||||
let claimHistoryStatus = $("#claim_history").prop("checked") ? 1 : 0;
|
||||
|
||||
if (claimHistoryStatus == 1) {
|
||||
$(".claim-row").show();
|
||||
$(".claim-input").attr("required", true);
|
||||
$(".claim-row").each(function() {
|
||||
const $row = $(this);
|
||||
$row.find('[name="first_year[]"]').prop("required", true);
|
||||
|
||||
if (!isClaimRowNilClaims($row)) {
|
||||
$row.find(".claim-input").not('[name="first_year[]"]').prop("required", true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$(".claim-row").hide();
|
||||
$(".claim-input").removeAttr("required").val("");
|
||||
$(".claim-row").each(function() {
|
||||
const $row = $(this);
|
||||
$row.find('.nil-claims-checkbox').prop('checked', false);
|
||||
$row.find('.nil-claims-value').val('0');
|
||||
$row.removeClass('claim-row-nil');
|
||||
$row.find('.claim-required-star').show();
|
||||
});
|
||||
$(".claim-input").removeAttr("required").prop('disabled', false).val("");
|
||||
$(".claim-input").each(function() {
|
||||
if ($(this).hasClass('select2-hidden-accessible')) {
|
||||
$(this).val(null).trigger('change');
|
||||
@ -2408,7 +2680,7 @@
|
||||
|
||||
if (value == 1) {
|
||||
$('.btnDiv').show();
|
||||
$('.claim-row').hide();
|
||||
updateClaimRowVisibility(fallbackPolicyTypeId);
|
||||
|
||||
$('.emp_title_text').text('No of Employees')
|
||||
$('.depnd_title_text').text('No of Dependents')
|
||||
@ -2497,7 +2769,7 @@
|
||||
} else {
|
||||
|
||||
$('.btnDiv').hide();
|
||||
$('.claim-row').show();
|
||||
updateClaimRowVisibility(fallbackPolicyTypeId);
|
||||
|
||||
$('.emp_title_text').text('No of Employees at Inception')
|
||||
$('.depnd_title_text').text(' No of Dependents at Inception')
|
||||
@ -2564,27 +2836,30 @@
|
||||
toggleRequiredFields();
|
||||
restoreActualLeadGstNumber();
|
||||
restoreActualLeadContactDetails();
|
||||
|
||||
if (typeof window.refreshLeadsFormValidation === 'function') {
|
||||
window.refreshLeadsFormValidation();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRequiredFields() {
|
||||
try {
|
||||
const claimHistoryOn = !$('#claim_history').length || $('#claim_history').prop('checked');
|
||||
|
||||
$('.claim-row').each(function() {
|
||||
var isRowHidden = $(this).css('display') === 'none';
|
||||
var isNilClaims = isClaimRowNilClaims($(this));
|
||||
|
||||
$(this).find('.form-group').each(function() {
|
||||
var input = $(this).find('input, select');
|
||||
$(this).find('.claim-year-group .claim-input').prop('required', claimHistoryOn && !isRowHidden);
|
||||
|
||||
if (input.attr('name') === "gender[]") {
|
||||
return;
|
||||
}
|
||||
$(this).find('.claim-field-group').each(function() {
|
||||
var input = $(this).find('input.claim-input, select.claim-input');
|
||||
|
||||
if (input.length === 0) {
|
||||
console.warn('No input/select fields found in:', this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove required if row is hidden, otherwise check field visibility
|
||||
input.prop('required', !isRowHidden && $(this).css('display') !== 'none');
|
||||
input.prop('required', claimHistoryOn && !isRowHidden && !isNilClaims && $(this).css('display') !== 'none');
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@ -2694,7 +2969,7 @@
|
||||
actualLeadClientName = actual_lead_client_details.company_name || '';
|
||||
$('#client_name').val(actualLeadClientName);
|
||||
|
||||
actualLeadGstNumber = actual_lead_client_details.gst_number || actual_lead_client_details.gst || '';
|
||||
actualLeadGstNumber = (actual_lead_client_details.gst_number || actual_lead_client_details.gst || '').toString().trim().toUpperCase();
|
||||
$('#gst').val(actualLeadGstNumber);
|
||||
if (actual_lead_client_details.client_type !== undefined && actual_lead_client_details.client_type !== null && actual_lead_client_details.client_type !== '') {
|
||||
$('#client_type').val(String(actual_lead_client_details.client_type));
|
||||
|
||||
@ -846,12 +846,22 @@ if (isset($selected_lead_type)) {
|
||||
claimHistoryToggle();
|
||||
}
|
||||
|
||||
if (typeof initNilClaimsRows === 'function') {
|
||||
initNilClaimsRows();
|
||||
}
|
||||
|
||||
if (typeof updateClaimRowVisibility === 'function') {
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
}
|
||||
|
||||
$('.loader').fadeIn();
|
||||
$('.loader-mask').fadeIn();
|
||||
|
||||
|
||||
if (lead_type == 1 || lead_type == 3) {
|
||||
if (lead_type == 1 || !shouldShowClaimHistorySwitch(lead_type, policy_type_id)) {
|
||||
if (typeof updateClaimRowVisibility === 'function') {
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
} else if (!shouldShowClaimHistorySwitch(lead_type, policy_type_id)) {
|
||||
$('.claim-row').hide();
|
||||
}
|
||||
|
||||
@ -948,6 +958,14 @@ if (isset($selected_lead_type)) {
|
||||
claimHistoryToggle();
|
||||
}
|
||||
|
||||
if (typeof initNilClaimsRows === 'function') {
|
||||
initNilClaimsRows();
|
||||
}
|
||||
|
||||
if (typeof updateClaimRowVisibility === 'function') {
|
||||
updateClaimRowVisibility(policy_type_id);
|
||||
}
|
||||
|
||||
if(lead_type == 3){
|
||||
let incurred_claim_date_id = 'incurred_claim_date';
|
||||
|
||||
@ -1121,7 +1139,7 @@ if (isset($selected_lead_type)) {
|
||||
let policy_type_id = data.policy_type_id || null;
|
||||
let lead_type = data.lead_type || null;
|
||||
|
||||
leadTypeBsedHideAndShow(lead_type);
|
||||
$('#lead_type').val(lead_type || '');
|
||||
|
||||
const isExistingClient = data.client_id !== undefined
|
||||
&& data.client_id !== null
|
||||
@ -1192,7 +1210,6 @@ if (isset($selected_lead_type)) {
|
||||
$('#leads_primarykey').val(data.id || '');
|
||||
$('#actual_lead_id').val(data.actual_lead_id || 0);
|
||||
$('#policy_start_date').val(data.policy_end_date || '');
|
||||
$('#lead_type').val(data.lead_type || '');
|
||||
$('#issuer').val(data.issuer || '');
|
||||
$('#client_type').val(data.client_type || '');
|
||||
$('#client_name').val(data.client_name || '');
|
||||
@ -1253,9 +1270,23 @@ if (isset($selected_lead_type)) {
|
||||
selecSalsePerson(data.salse_person_id);
|
||||
}
|
||||
|
||||
leadTypeBsedHideAndShow(lead_type, false, Boolean(data.claims_details_html));
|
||||
|
||||
let referenceDiv = document.getElementById('appendAreaForClaim');
|
||||
referenceDiv.innerHTML = '';
|
||||
referenceDiv.insertAdjacentHTML('beforeend', data.claims_details_html);
|
||||
referenceDiv.insertAdjacentHTML('beforeend', data.claims_details_html || '');
|
||||
|
||||
if (typeof initNilClaimsRows === 'function') {
|
||||
initNilClaimsRows();
|
||||
}
|
||||
|
||||
if (typeof updateNonEbClaimRowVisibility === 'function') {
|
||||
updateNonEbClaimRowVisibility(lead_type);
|
||||
} else if (typeof updateClaimRowVisibility === 'function') {
|
||||
updateClaimRowVisibility(data.policy_type_id);
|
||||
} else if (!data.claims_details_html && typeof setupNonEbClaimDetailsSection === 'function' && shouldShowNonEbClaimHistory(lead_type)) {
|
||||
setupNonEbClaimDetailsSection(lead_type);
|
||||
}
|
||||
|
||||
console.log("wdesfgefwregfefwregffeegf",data.lead_type);
|
||||
var lead_type_value = data.lead_type;
|
||||
|
||||
@ -647,14 +647,7 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
console.log(response.message, 'WARNING');
|
||||
}
|
||||
|
||||
if (lead_type != 1 && lead_type != 3) {
|
||||
|
||||
let html = `<hr><div class="form-row"> <div class="form-group col-md-3"><h4> Claim Details </h4></div></div>`
|
||||
let referenceDiv = document.getElementById('appendAreaForClaim');
|
||||
referenceDiv.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
appendThreeYearsClaims();
|
||||
}
|
||||
setupNonEbClaimDetailsSection();
|
||||
|
||||
// Hide loader
|
||||
$('.loader').fadeOut();
|
||||
@ -675,6 +668,11 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$('.claim-row').each(function() {
|
||||
const isNil = $(this).find('.nil-claims-checkbox').prop('checked');
|
||||
$(this).find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
});
|
||||
|
||||
var isValid = $('#leads_non_eb_form_id').parsley().validate();
|
||||
console.log('isValid', isValid)
|
||||
|
||||
@ -728,6 +726,7 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
const finyearJsonString = gatherClaimExperienceData();
|
||||
console.log('finyearJsonString', finyearJsonString);
|
||||
formData.append('fin_years_claims', finyearJsonString);
|
||||
syncClaimHistoryFieldsToFormData(formData);
|
||||
|
||||
cliam_history_status = $("#claim_history").prop("checked") ? 1 : 0;
|
||||
|
||||
@ -799,7 +798,7 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
});
|
||||
|
||||
$('#lead_type').change(function() {
|
||||
let value = $(this).val()
|
||||
let value = $(this).val();
|
||||
// alert(value);
|
||||
if (value == 1 || value == 3) {
|
||||
// alert("Function Called");
|
||||
@ -840,7 +839,7 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
}
|
||||
|
||||
leadTypeBsedHideAndShow(value, true)
|
||||
leadTypeBsedHideAndShow(value, true);
|
||||
|
||||
$('#contact_person_summary').empty();
|
||||
$('#contact_person_summary').append($('<option>', {
|
||||
@ -906,13 +905,72 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
})
|
||||
|
||||
function leadTypeBsedHideAndShow(value, resetValues = false) {
|
||||
function getClaimHistoryLabel(leadType) {
|
||||
return String(leadType) === '1' ? 'Mortality Claims' : 'Claims History';
|
||||
}
|
||||
|
||||
function getClaimDetailsSectionTitle(leadType) {
|
||||
return String(leadType) === '1' ? 'Mortality Claims' : 'Claim Details';
|
||||
}
|
||||
|
||||
function shouldShowNonEbClaimHistory(leadType) {
|
||||
return ['1', '2', '3'].includes(String(leadType));
|
||||
}
|
||||
|
||||
function updateNonEbClaimRowVisibility(leadTypeOverride) {
|
||||
const leadType = leadTypeOverride ?? $('#lead_type').val();
|
||||
|
||||
if (!shouldShowNonEbClaimHistory(leadType)) {
|
||||
$('.claim-row').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('#claim_history').length) {
|
||||
if ($('#claim_history').prop('checked')) {
|
||||
$('.claim-row').show();
|
||||
} else {
|
||||
$('.claim-row').hide();
|
||||
}
|
||||
} else {
|
||||
$('.claim-row').show();
|
||||
}
|
||||
}
|
||||
|
||||
function setupNonEbClaimDetailsSection(leadTypeOverride) {
|
||||
const leadType = leadTypeOverride ?? $('#lead_type').val();
|
||||
const referenceDiv = document.getElementById('appendAreaForClaim');
|
||||
|
||||
if (!referenceDiv) {
|
||||
return;
|
||||
}
|
||||
|
||||
referenceDiv.innerHTML = '';
|
||||
|
||||
if (!shouldShowNonEbClaimHistory(leadType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionTitle = getClaimDetailsSectionTitle(leadType);
|
||||
const html = `<hr><div class="form-row claim-details-section-header"><div class="form-group col-md-3"><h4>${sectionTitle}</h4></div></div>`;
|
||||
|
||||
referenceDiv.insertAdjacentHTML('beforeend', html);
|
||||
appendThreeYearsClaims();
|
||||
updateNonEbClaimRowVisibility(leadType);
|
||||
}
|
||||
|
||||
function leadTypeBsedHideAndShow(value, resetValues = false, skipClaimSetup = false) {
|
||||
|
||||
var actual_lead_id = $('#actual_lead_id').val();
|
||||
|
||||
if (value == 1 || value == 3) {
|
||||
$('.btnDiv').show();
|
||||
$('.claim-row').hide();
|
||||
|
||||
if (!skipClaimSetup && shouldShowNonEbClaimHistory(value)) {
|
||||
setupNonEbClaimDetailsSection(value);
|
||||
} else if (!shouldShowNonEbClaimHistory(value)) {
|
||||
$('#appendAreaForClaim').empty();
|
||||
$('.claim-row').hide();
|
||||
}
|
||||
|
||||
|
||||
$('.emp_title_text').text('No of Employees')
|
||||
@ -950,7 +1008,10 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
} else {
|
||||
|
||||
$('.btnDiv').hide();
|
||||
$('.claim-row').show();
|
||||
|
||||
if (!skipClaimSetup && shouldShowNonEbClaimHistory(value)) {
|
||||
setupNonEbClaimDetailsSection(value);
|
||||
}
|
||||
|
||||
$('.emp_title_text').text('No of Employees at Inception')
|
||||
$('.depnd_title_text').text(' No of Dependents at Inception')
|
||||
@ -1178,11 +1239,158 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
var increment = 1;
|
||||
|
||||
function isClaimRowNilClaims($row) {
|
||||
return $row.find('.nil-claims-checkbox').prop('checked');
|
||||
}
|
||||
|
||||
function setClaimSelectValue($select, value) {
|
||||
if (!$select.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($select.find('option[value="' + value + '"]').length === 0) {
|
||||
$select.append($('<option>', { value: value, text: value }));
|
||||
}
|
||||
|
||||
$select.val(value);
|
||||
|
||||
if ($select.hasClass('select2-hidden-accessible')) {
|
||||
$select.trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
function resetClaimYearField($yearField) {
|
||||
if (!$yearField.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$yearField.find('option[value="Nil"]').remove();
|
||||
|
||||
if ($yearField.val() === 'Nil') {
|
||||
$yearField.val('').trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
function nilClaimsToggle(checkbox) {
|
||||
const $row = $(checkbox).closest('.claim-row');
|
||||
const isNil = $(checkbox).prop('checked');
|
||||
const $yearField = $row.find('[name="first_year[]"]');
|
||||
|
||||
resetClaimYearField($yearField);
|
||||
|
||||
$row.find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
$row.toggleClass('claim-row-nil', isNil);
|
||||
$row.find('.claim-field-group .claim-required-star').toggle(!isNil);
|
||||
|
||||
$row.find('.claim-input').not('[name="first_year[]"]').each(function() {
|
||||
const $field = $(this);
|
||||
|
||||
if (isNil) {
|
||||
if ($field.is('select')) {
|
||||
setClaimSelectValue($field, 'Nil');
|
||||
} else {
|
||||
$field.val('Nil');
|
||||
}
|
||||
|
||||
$field.prop('disabled', true).removeAttr('required');
|
||||
|
||||
if ($field.hasClass('select2-hidden-accessible')) {
|
||||
$field.next('.select2-container').addClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if ($field.parsley) {
|
||||
$field.parsley().reset();
|
||||
}
|
||||
} else {
|
||||
$field.prop('disabled', false);
|
||||
|
||||
if ($field.hasClass('select2-hidden-accessible')) {
|
||||
$field.next('.select2-container').removeClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if ($field.is('select')) {
|
||||
$field.find('option[value="Nil"]').remove();
|
||||
$field.val('').trigger('change');
|
||||
} else {
|
||||
$field.val('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const claimHistoryOn = !$('#claim_history').length || $('#claim_history').prop('checked');
|
||||
|
||||
if (claimHistoryOn) {
|
||||
$yearField.prop('required', true).prop('disabled', false);
|
||||
|
||||
if ($yearField.hasClass('select2-hidden-accessible')) {
|
||||
$yearField.next('.select2-container').removeClass('select2-container--disabled');
|
||||
}
|
||||
|
||||
if (!isNil) {
|
||||
$row.find('.claim-input').not('[name="first_year[]"]').prop('required', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.refreshLeadsFormValidation === 'function') {
|
||||
window.refreshLeadsFormValidation();
|
||||
}
|
||||
}
|
||||
|
||||
function initNilClaimsRows() {
|
||||
$('.claim-row .nil-claims-checkbox:checked').each(function() {
|
||||
nilClaimsToggle(this);
|
||||
});
|
||||
}
|
||||
|
||||
function syncClaimHistoryFieldsToFormData(formData) {
|
||||
const $rows = $('.claim-row');
|
||||
|
||||
if (!$rows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldNames = ['first_year[]', 'nil_claims[]', 'first_policy_type_[]', 'first_date_of_loss_[]', 'first_cause_of_loss[]', 'first_claim_amount[]', 'first_settled_amount[]', 'first_claim_status[]'];
|
||||
|
||||
fieldNames.forEach(function(name) {
|
||||
if (typeof formData.delete === 'function') {
|
||||
formData.delete(name);
|
||||
}
|
||||
});
|
||||
|
||||
$rows.each(function() {
|
||||
const $row = $(this);
|
||||
const isNil = $row.find('.nil-claims-checkbox').prop('checked');
|
||||
|
||||
$row.find('.nil-claims-value').val(isNil ? '1' : '0');
|
||||
formData.append('first_year[]', $row.find('[name="first_year[]"]').val() || '');
|
||||
formData.append('nil_claims[]', isNil ? '1' : '0');
|
||||
|
||||
if (isNil) {
|
||||
formData.append('first_policy_type_[]', 'Nil');
|
||||
formData.append('first_date_of_loss_[]', 'Nil');
|
||||
formData.append('first_cause_of_loss[]', 'Nil');
|
||||
formData.append('first_claim_amount[]', 'Nil');
|
||||
formData.append('first_settled_amount[]', 'Nil');
|
||||
formData.append('first_claim_status[]', 'Nil');
|
||||
} else {
|
||||
formData.append('first_policy_type_[]', $row.find('[name="first_policy_type_[]"]').val() || '');
|
||||
formData.append('first_date_of_loss_[]', $row.find('[name="first_date_of_loss_[]"]').val() || '');
|
||||
formData.append('first_cause_of_loss[]', $row.find('[name="first_cause_of_loss[]"]').val() || '');
|
||||
formData.append('first_claim_amount[]', $row.find('[name="first_claim_amount[]"]').val() || '');
|
||||
formData.append('first_settled_amount[]', $row.find('[name="first_settled_amount[]"]').val() || '');
|
||||
formData.append('first_claim_status[]', $row.find('[name="first_claim_status[]"]').val() || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function appendThreeYearsClaims() {
|
||||
|
||||
const leadType = $('#lead_type').val();
|
||||
const claimHistoryLabel = getClaimHistoryLabel(leadType);
|
||||
|
||||
let claimsFields = `${!document.querySelector('#claim_history') ? `<div class="custom-control custom-switch">
|
||||
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" checked />
|
||||
<label class="custom-control-label" for="claim_history">Claims History</label>
|
||||
<label class="custom-control-label" for="claim_history">${claimHistoryLabel}</label>
|
||||
</div><br>`: ``}`
|
||||
|
||||
claimsFields += `
|
||||
@ -1190,8 +1398,8 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
<div id = "claimHistoryRow" class="row claim-row">
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_year_${increment}">Year<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-year-group">
|
||||
<label for="first_year_${increment}">Year<span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control claim-input first_year_" id="first_year_${increment}" name="first_year[]">
|
||||
<option value="">Select Year</option>
|
||||
<?php foreach ($lastFiveYears as $year) {
|
||||
@ -1199,28 +1407,38 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
} ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_policy_type_${increment}">Policy Type<span class="text-danger">*</span></label>
|
||||
<label>Nil Claims</label>
|
||||
<input type="hidden" class="nil-claims-value" name="nil_claims[]" value="0">
|
||||
<div class="custom-control custom-checkbox" style="padding-top: 8px;">
|
||||
<input type="checkbox" class="custom-control-input nil-claims-checkbox" id="nil_claims_${increment}" onchange="nilClaimsToggle(this)">
|
||||
<label class="custom-control-label" for="nil_claims_${increment}">Nil Claims</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_policy_type_${increment}">Policy Type<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_policy_type_${increment}" name="first_policy_type_[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_date_of_loss_${increment}">Date of Loss<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_date_of_loss_${increment}">Date of Loss<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control loss_date claim-input" id="first_date_of_loss_${increment}" name="first_date_of_loss_[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_cause_of_death_${increment}">Cause Of Loss <span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_cause_of_loss_${increment}">Cause Of Loss <span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_cause_of_loss_${increment}" name="first_cause_of_loss[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount_${increment}">Claim Amount<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_amount_${increment}">Claim Amount<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_amount_${increment}" name="first_claim_amount[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount_${increment}">Settled Amount<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_settled_amount_${increment}">Settled Amount<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_settled_amount_${increment}" name="first_settled_amount[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_status_${increment}">Claim Status<span class="text-danger">*</span></label>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_status_${increment}">Claim Status<span class="text-danger claim-required-star">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_status_${increment}" name="first_claim_status[]">
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
@ -1241,7 +1459,10 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
let lastRow = $(".claim-row").last();
|
||||
if (isChecked) {
|
||||
lastRow.show();
|
||||
lastRow.find(".claim-input").attr("required", true);
|
||||
lastRow.find('[name="first_year[]"]').prop("required", true);
|
||||
if (!isClaimRowNilClaims(lastRow)) {
|
||||
lastRow.find(".claim-input").not('[name="first_year[]"]').prop("required", true);
|
||||
}
|
||||
} else {
|
||||
lastRow.hide();
|
||||
lastRow.find(".claim-input").removeAttr("required");
|
||||
@ -1274,8 +1495,31 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
let claimData = [];
|
||||
|
||||
if ($("#claim_history").length > 0 && !$("#claim_history").prop("checked")) {
|
||||
return JSON.stringify({
|
||||
"finyear": claimData
|
||||
});
|
||||
}
|
||||
|
||||
$(".claim-row").each(function() {
|
||||
|
||||
let isNilClaims = $(this).find(".nil-claims-checkbox").prop("checked");
|
||||
|
||||
if (isNilClaims) {
|
||||
let year = $(this).find("[name='first_year[]']").val();
|
||||
claimData.push({
|
||||
"year": year,
|
||||
"policy_type": "Nil",
|
||||
"date_of_loss": "Nil",
|
||||
"claim_amount": "Nil",
|
||||
"settled_amount": "Nil",
|
||||
"cause_of_loss": "Nil",
|
||||
"status": "Nil",
|
||||
"nil_claims": 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let year = $(this).find("[name='first_year[]']").val();
|
||||
let policyType = $(this).find("[name='first_policy_type_[]']").val();
|
||||
let dateOfLoss = $(this).find("[name='first_date_of_loss_[]']").val();
|
||||
@ -1292,6 +1536,7 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
"settled_amount": settledAmount,
|
||||
"cause_of_loss": causeOfLoss,
|
||||
"status": claimStatus,
|
||||
"nil_claims": 0,
|
||||
});
|
||||
});
|
||||
|
||||
@ -1309,26 +1554,41 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
|
||||
function claimHistoryToggle() {
|
||||
cliam_history_status = $("#claim_history").prop("checked") ? 1 : 0;
|
||||
// alert(cliam_history_status);
|
||||
|
||||
if (cliam_history_status == 1) {
|
||||
if ($(".claim-row").length > 0) {
|
||||
// Rows already exist, just show them
|
||||
$(".claim-row").show();
|
||||
$(".claim-row").show();
|
||||
} else {
|
||||
// No rows yet, so create them
|
||||
appendThreeYearsClaims();
|
||||
}
|
||||
$(".claim-input").attr("required", true);
|
||||
} else {
|
||||
|
||||
$(".claim-row").each(function() {
|
||||
const $row = $(this);
|
||||
$row.find('[name="first_year[]"]').prop("required", true);
|
||||
|
||||
if (!isClaimRowNilClaims($row)) {
|
||||
$row.find(".claim-input").not('[name="first_year[]"]').prop("required", true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$(".claim-row").hide();
|
||||
$(".claim-input").removeAttr("required"); // Required Attributes Remove
|
||||
$(".claim-input").val(""); // Value Reset
|
||||
$(".claim-row").each(function() {
|
||||
const $row = $(this);
|
||||
$row.find('.nil-claims-checkbox').prop('checked', false);
|
||||
$row.find('.nil-claims-value').val('0');
|
||||
$row.removeClass('claim-row-nil');
|
||||
$row.find('.claim-required-star').show();
|
||||
});
|
||||
$(".claim-input").removeAttr("required").prop('disabled', false).val("");
|
||||
$(".claim-input").each(function() {
|
||||
$(this).parsley().reset(); // Validation Reset
|
||||
if ($(this).hasClass('select2-hidden-accessible')) {
|
||||
$(this).val(null).trigger('change');
|
||||
}
|
||||
if ($(this).parsley) {
|
||||
$(this).parsley().reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$('#exixting_client').change(function () {
|
||||
@ -1476,5 +1736,12 @@ var pageBackButton = '<a href="<?= base_url('leads/list') ?>" class="topbar-icon
|
||||
$('#lost_reason').val("");
|
||||
$('#lost_reason').removeAttr('required');
|
||||
}
|
||||
|
||||
const initialLeadType = $('#lead_type').val();
|
||||
const isEditLead = String($('#actual_lead_id').val() || '0') !== '0';
|
||||
|
||||
if (initialLeadType && !isEditLead) {
|
||||
leadTypeBsedHideAndShow(initialLeadType, false);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -1,55 +1,85 @@
|
||||
<?php if(isset($lead_edit_data)) {
|
||||
$claims = !empty($lead_edit_data['fin_years_claims_array']) ? $lead_edit_data['fin_years_claims_array'] : [ ['year' => '', 'claim_amount' => '', 'status' => '', 'claim_type' => '', 'cause_of_death' => '', 'death_date' => ''] ];
|
||||
<?php if (isset($lead_edit_data)) {
|
||||
if (! isset($lastFiveYears) || ! is_array($lastFiveYears)) {
|
||||
$year = (int) date('Y');
|
||||
$month = (int) date('m');
|
||||
$currentFYStart = ($month >= 4) ? $year : $year - 1;
|
||||
$lastFiveYears = [];
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$startYear = $currentFYStart - $i;
|
||||
$lastFiveYears[] = "{$startYear}-" . ($startYear + 1);
|
||||
}
|
||||
}
|
||||
|
||||
$claims = ! empty($lead_edit_data['fin_years_claims_array'])
|
||||
? $lead_edit_data['fin_years_claims_array']
|
||||
: [['year' => '', 'claim_amount' => '', 'status' => '', 'policy_type' => '', 'date_of_loss' => '', 'cause_of_loss' => '']];
|
||||
$leadType = (int) ($lead_edit_data['lead_type'] ?? 0);
|
||||
$showClaimsSection = in_array($leadType, [1, 2, 3], true);
|
||||
$claimHistoryLabel = $leadType === 1 ? 'Mortality Claims' : 'Claims History';
|
||||
?>
|
||||
<?php if ($showClaimsSection) { ?>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" <?= $lead_edit_data['claim_history'] == 1 ? "checked" : "" ?> />
|
||||
<label class="custom-control-label" for="claim_history">Claims History</label>
|
||||
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" <?= $lead_edit_data['claim_history'] == 1 ? 'checked' : '' ?> />
|
||||
<label class="custom-control-label" for="claim_history"><?= $claimHistoryLabel ?></label>
|
||||
</div><br>
|
||||
<?php
|
||||
|
||||
if ($lead_edit_data['claim_history'] == 1) {
|
||||
foreach ($claims as $key => $value) { ?>
|
||||
foreach ($claims as $key => $value) {
|
||||
$isNilClaimRow = ! empty($value['nil_claims']);
|
||||
?>
|
||||
|
||||
<div class="row claim-row">
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_year">Year<span class="text-danger">*</span></label>
|
||||
<select class="form-control first_year_" id="first_year" name="first_year[]">
|
||||
<div class="row claim-row<?= $isNilClaimRow ? ' claim-row-nil' : '' ?>">
|
||||
<div class="form-group col-md-2 claim-year-group">
|
||||
<label for="first_year_<?= $key ?>">Year<span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control first_year_ claim-input" id="first_year_<?= $key ?>" name="first_year[]">
|
||||
<option value="">Select Year</option>
|
||||
<?php foreach ($lastFiveYears as $year) {
|
||||
$selected = ($year == $value['year']) ? 'selected' : '';
|
||||
echo "<option value='$year' $selected>$year</option>";
|
||||
} ?>
|
||||
$selected = ($year == $value['year']) ? 'selected' : '';
|
||||
echo "<option value='$year' $selected>$year</option>";
|
||||
} ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_policy_type_${increment}">Policy Type<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="first_policy_type_${increment}" name="first_policy_type_[]"
|
||||
value="<?= htmlspecialchars($value['policy_type']) ?>">
|
||||
<label>Nil Claims</label>
|
||||
<input type="hidden" class="nil-claims-value" name="nil_claims[]" value="<?= $isNilClaimRow ? '1' : '0' ?>">
|
||||
<div class="custom-control custom-checkbox" style="padding-top: 8px;">
|
||||
<input type="checkbox" class="custom-control-input nil-claims-checkbox" id="nil_claims_<?= $key ?>" onchange="nilClaimsToggle(this)"<?= $isNilClaimRow ? ' checked' : '' ?>>
|
||||
<label class="custom-control-label" for="nil_claims_<?= $key ?>">Nil Claims</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_date_of_loss_${increment}">Date of Loss<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control loss_date" id="first_date_of_loss_${increment}" name="first_date_of_loss_[]"
|
||||
value="<?= htmlspecialchars($value['date_of_loss']) ?>">
|
||||
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_policy_type_<?= $key ?>">Policy Type<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_policy_type_<?= $key ?>" name="first_policy_type_[]"
|
||||
value="<?= htmlspecialchars($value['policy_type'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_cause_of_death_${increment}">Cause Of Loss <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="first_cause_of_loss_${increment}" name="first_cause_of_loss[]"
|
||||
value="<?= htmlspecialchars($value['cause_of_loss']) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_date_of_loss_<?= $key ?>">Date of Loss<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control loss_date claim-input" id="first_date_of_loss_<?= $key ?>" name="first_date_of_loss_[]"
|
||||
value="<?= htmlspecialchars($value['date_of_loss'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount_${increment}">Claim Amount<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="first_claim_amount_${increment}" name="first_claim_amount[]"
|
||||
value="<?= htmlspecialchars($value['claim_amount']) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_cause_of_loss_<?= $key ?>">Cause Of Loss <span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_cause_of_loss_<?= $key ?>" name="first_cause_of_loss[]"
|
||||
value="<?= htmlspecialchars($value['cause_of_loss'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount_${increment}">Settled Amount<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="first_settled_amount_${increment}" name="first_settled_amount[]"
|
||||
value="<?= htmlspecialchars($value['settled_amount']) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_amount_<?= $key ?>">Claim Amount<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_amount_<?= $key ?>" name="first_claim_amount[]"
|
||||
value="<?= htmlspecialchars($value['claim_amount'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_status_${increment}">Claim Status<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="first_claim_status_${increment}" name="first_claim_status[]"
|
||||
value="<?= htmlspecialchars($value['status']) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_settled_amount_<?= $key ?>">Settled Amount<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_settled_amount_<?= $key ?>" name="first_settled_amount[]"
|
||||
value="<?= htmlspecialchars($value['settled_amount'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_status_<?= $key ?>">Claim Status<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_status_<?= $key ?>" name="first_claim_status[]"
|
||||
value="<?= htmlspecialchars($value['status'] ?? '') ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
<div class="form-group col-md-2">
|
||||
<div class="" style="position: relative; top: 28px; float: right; text-align: end;">
|
||||
@ -59,5 +89,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php } } ?>
|
||||
<?php } ?>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
$(".loss_date").each(function () {
|
||||
flatpickr(this, {
|
||||
dateFormat: "d-m-Y",
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof initNilClaimsRows === 'function') {
|
||||
initNilClaimsRows();
|
||||
}
|
||||
|
||||
if (typeof updateNonEbClaimRowVisibility === 'function') {
|
||||
updateNonEbClaimRowVisibility();
|
||||
}
|
||||
</script>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
|
||||
@ -42,16 +42,35 @@
|
||||
|
||||
<?php if(isset($lead_edit_data)) { ?>
|
||||
<?php
|
||||
if (! isset($lastFiveYears) || ! is_array($lastFiveYears)) {
|
||||
$year = (int) date('Y');
|
||||
$month = (int) date('m');
|
||||
$currentFYStart = ($month >= 4) ? $year : $year - 1;
|
||||
$lastFiveYears = [];
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$startYear = $currentFYStart - $i;
|
||||
$lastFiveYears[] = "{$startYear}-" . ($startYear + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($causeOfDeath) || ! is_array($causeOfDeath)) {
|
||||
$causeOfDeath = [];
|
||||
}
|
||||
|
||||
$savedClaims = !empty($lead_edit_data['fin_years_claims_array']) ? $lead_edit_data['fin_years_claims_array'] : [];
|
||||
$isClaimHistoryChecked = (isset($lead_edit_data['claim_history']) && (int) $lead_edit_data['claim_history'] === 1) || !empty($savedClaims);
|
||||
$showClaimHistorySwitch = isset($lead_edit_data['lead_type'], $lead_edit_data['policy_type_id'])
|
||||
&& in_array((int) $lead_edit_data['lead_type'], [2, 3], true)
|
||||
&& in_array((int) $lead_edit_data['lead_type'], [1, 2, 3], true)
|
||||
&& in_array((int) $lead_edit_data['policy_type_id'], [1, 6, 7], true);
|
||||
$claimHistoryLabel = (isset($lead_edit_data['lead_type']) && (int) $lead_edit_data['lead_type'] === 1)
|
||||
? 'Mortality Claims'
|
||||
: 'Claims History';
|
||||
?>
|
||||
<?php if ($showClaimHistorySwitch) { ?>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" onchange="claimHistoryToggle()" class="custom-control-input" id="claim_history" name="claim_history" <?= $isClaimHistoryChecked ? 'checked' : '' ?>>
|
||||
<label class="custom-control-label" for="claim_history">Claims History</label>
|
||||
<label class="custom-control-label" for="claim_history"><?= $claimHistoryLabel ?></label>
|
||||
</div><br>
|
||||
<?php } ?>
|
||||
<div class="form-row" id="appendAreaForClaim_1">
|
||||
@ -59,13 +78,15 @@
|
||||
|
||||
$claims = !empty($savedClaims) ? $savedClaims : [ ['year' => '', 'claim_amount' => '', 'status' => '', 'claim_type' => '', 'cause_of_death' => '', 'death_date' => ''] ];
|
||||
|
||||
foreach ($claims as $key => $value) { ?>
|
||||
foreach ($claims as $key => $value) {
|
||||
$isNilClaimRow = !empty($value['nil_claims']);
|
||||
?>
|
||||
|
||||
<div class="row claim-row">
|
||||
<div class="row claim-row<?= $isNilClaimRow ? ' claim-row-nil' : '' ?>">
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_year">Year<span class="text-danger">*</span></label>
|
||||
<select class="form-control first_year_ claim-input" id="first_year" name="first_year[]">
|
||||
<div class="form-group col-md-2 claim-year-group">
|
||||
<label for="first_year_<?= $key ?>">Year<span class="text-danger claim-required-star">*</span></label>
|
||||
<select class="form-control first_year_ claim-input" id="first_year_<?= $key ?>" name="first_year[]">
|
||||
<option value="">Select Year</option>
|
||||
<?php foreach ($lastFiveYears as $year) {
|
||||
$selected = ($year == $value['year']) ? 'selected' : '';
|
||||
@ -75,54 +96,69 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="emp_id">Emp ID<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_id" name="emp_id[]" value="<?= htmlspecialchars(isset($value['emp_id']) ? $value['emp_id'] : '-' ) ?>">
|
||||
<label>Nil Claims</label>
|
||||
<input type="hidden" class="nil-claims-value" name="nil_claims[]" value="<?= $isNilClaimRow ? '1' : '0' ?>">
|
||||
<div class="custom-control custom-checkbox" style="padding-top: 8px;">
|
||||
<input type="checkbox" class="custom-control-input nil-claims-checkbox" id="nil_claims_<?= $key ?>" onchange="nilClaimsToggle(this)"<?= $isNilClaimRow ? ' checked' : '' ?>>
|
||||
<label class="custom-control-label" for="nil_claims_<?= $key ?>">Nil Claims</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="emp_id_<?= $key ?>">Emp ID<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_id_<?= $key ?>" name="emp_id[]" value="<?= htmlspecialchars(isset($value['emp_id']) ? $value['emp_id'] : '-' ) ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="emp_name">Employee Name<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_name" name="emp_name[]" value="<?= htmlspecialchars(isset($value['emp_name']) ? $value['emp_name'] : '-' ) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="emp_name_<?= $key ?>">Employee Name<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="emp_name_<?= $key ?>" name="emp_name[]" value="<?= htmlspecialchars(isset($value['emp_name']) ? $value['emp_name'] : '-' ) ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="gender">Gender<span class="text-danger">*</span></label>
|
||||
<select class="form-control claim-input" id="gender" name="gender[]">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="gender_<?= $key ?>">Gender<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<select class="form-control claim-input" id="gender_<?= $key ?>" name="gender[]"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
<option value="">Select Gender</option>
|
||||
<option value="Female" <?= (isset($value['gender']) && $value['gender'] == 'Female') ? 'selected' : '' ?>>Female</option>
|
||||
<option value="Male" <?= (isset($value['gender']) && $value['gender'] == 'Male') ? 'selected' : '' ?>>Male</option>
|
||||
<option value="Male" <?= (isset($value['gender']) && $value['gender'] == 'Male') ? 'selected' : '' ?>>Male</option>
|
||||
<?php if ($isNilClaimRow) { ?>
|
||||
<option value="Nil" selected>Nil</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="designation">Designation <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="designation" name="designation[]" value="<?= htmlspecialchars(isset($value['designation']) ? $value['designation'] : '-' ) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="designation_<?= $key ?>">Designation <span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="designation_<?= $key ?>" name="designation[]" value="<?= htmlspecialchars(isset($value['designation']) ? $value['designation'] : '-' ) ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="sum_insured">Sum Insured <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="sum_insured" name="sum_insured[]" value="<?= htmlspecialchars(isset($value['sum_insured']) ? $value['sum_insured'] : '-' ) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="sum_insured_<?= $key ?>">Sum Insured <span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="sum_insured_<?= $key ?>" name="sum_insured[]" value="<?= htmlspecialchars(isset($value['sum_insured']) ? $value['sum_insured'] : '-' ) ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_death_date">Date of Death<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control flatpickr-date claim-input" id="first_death_date" name="first_death_date[]" value="<?= htmlspecialchars($value['death_date']) ?>" autocomplete="off">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_death_date_<?= $key ?>">Date of Death<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control flatpickr-date claim-input" id="first_death_date_<?= $key ?>" name="first_death_date[]" value="<?= htmlspecialchars($value['death_date'] ?? '') ?>" autocomplete="off"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_cause_of_death">Nature/Cause Of Death <span class="text-danger">*</span></label>
|
||||
<select class="form-control claim-input" id="first_cause_of_death" name="first_cause_of_death[]">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_cause_of_death_<?= $key ?>">Nature/Cause Of Death <span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<select class="form-control claim-input" id="first_cause_of_death_<?= $key ?>" name="first_cause_of_death[]"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
<option value="">Select Cause of Death</option>
|
||||
<?php foreach ($causeOfDeath as $cause => $death_value) {
|
||||
$selected = ($cause == $value['cause_of_death']) ? 'selected' : '';
|
||||
echo "<option value='$cause' $selected>$death_value</option>";
|
||||
} ?>
|
||||
<?php if ($isNilClaimRow) { ?>
|
||||
<option value="Nil" selected>Nil</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label for="first_claim_amount">Claim/Settled Amount<span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_amount" name="first_claim_amount[]" value="<?= htmlspecialchars(isset($value['claim_amount']) ? $value['claim_amount'] : ( isset($value['settled']) ? $value['settled'] : '-' )) ?>">
|
||||
<div class="form-group col-md-2 claim-field-group">
|
||||
<label for="first_claim_amount_<?= $key ?>">Claim/Settled Amount<span class="text-danger claim-required-star"<?= $isNilClaimRow ? ' style="display:none;"' : '' ?>>*</span></label>
|
||||
<input type="text" class="form-control claim-input" id="first_claim_amount_<?= $key ?>" name="first_claim_amount[]" value="<?= htmlspecialchars(isset($value['claim_amount']) ? $value['claim_amount'] : ( isset($value['settled']) ? $value['settled'] : '-' )) ?>"<?= $isNilClaimRow ? ' disabled' : '' ?>>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-group col-md-2">
|
||||
@ -165,5 +201,9 @@ flatpickr('.flatpickr-date', {
|
||||
maxDate: 'today', // Optional: disable future dates
|
||||
});
|
||||
|
||||
if (typeof initNilClaimsRows === 'function') {
|
||||
initNilClaimsRows();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -39,12 +39,25 @@
|
||||
<div class="card" style="margin-right: 23px;">
|
||||
<div class="card-body">
|
||||
|
||||
<?php
|
||||
$tpaActionButtons = '';
|
||||
if (
|
||||
!empty($ticket_data['is_tpa_api_service_enabled']) &&
|
||||
empty($ticket_data['tpa_claim_push_reference_no']) &&
|
||||
($ticket_data['claim_created_by'] ?? '') !== 'TPA'
|
||||
) {
|
||||
$tpaActionButtons .= ' <a href="#" class="btn btn-success btn-sm mr-2" onclick="manualTpaClaimPush(); return false;">Manual TPA Claim Push</a>';
|
||||
}
|
||||
if (!empty($ticket_data['tpa_claim_push_reference_no'])) {
|
||||
$tpaActionButtons .= ' <a href="#" class="btn btn-success btn-sm mr-2" onclick="fetchTpaClaimStatus(); return false;">Fetch Claim Status</a>';
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
var pageHideMainNavTitle = true;
|
||||
var pageTitle = 'Claims<?php if(isset($ticket_data["tpa_claim_push_reference_no"]) && !empty($ticket_data["tpa_claim_push_reference_no"])) { echo " <a href=\"#\" class=\"btn btn-success mr-2\" onclick=\"fetchTpaClaimStatus()\">Fetch Claim Status</a>"; } ?>';
|
||||
var pageTitle = 'Claims <?= $tpaActionButtons ?>';
|
||||
var pageBackButton = '<a href="<?= base_url("ticket/list"); ?>" class="topbar-icon-btn" data-toggle="tooltip" data-placement="top" title="Back" aria-label="Back to list"><i class="ri-arrow-left-s-line"></i></a>';
|
||||
</script>
|
||||
<!-- Header Section -->
|
||||
<!-- Header Section (TPA actions rendered in top navbar via pageTitle) -->
|
||||
<!-- <div class="row mb-3 align-items-center justify-content-between">
|
||||
<div class="col-auto">
|
||||
<h4 class="mb-0">Claims</h4>
|
||||
@ -56,7 +69,7 @@
|
||||
<?php if (
|
||||
$ticket_data['is_tpa_api_service_enabled'] == true &&
|
||||
empty($ticket_data['tpa_claim_push_reference_no']) &&
|
||||
empty($ticket_data['claim_number'])
|
||||
$ticket_data['claim_created_by'] != 'TPA'
|
||||
) : ?>
|
||||
<a href="#" class="btn btn-success mr-2" onclick="manualTpaClaimPush(); return false;">
|
||||
Manual TPA Claim Push
|
||||
|
||||
@ -577,6 +577,104 @@
|
||||
<script>
|
||||
let GlobelExtraFields = [];
|
||||
var doa;
|
||||
var STATUS_SECTION_CLASSES = ['cda_ir', 'settled', 'approved', 'rejected', 'up_qdr', 'up_cnu', 'canceled', 'returned', 'payment', 'non_id'];
|
||||
var statusSectionInitialValues = {};
|
||||
|
||||
function captureSectionInitialValues(sectionClass) {
|
||||
if (!sectionClass) {
|
||||
return;
|
||||
}
|
||||
var values = {};
|
||||
$('.' + sectionClass).find('input, textarea, select').each(function() {
|
||||
var el = this;
|
||||
var key = el.id || el.name;
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
values[key] = el.type === 'file' ? '' : String($(el).val() || '');
|
||||
});
|
||||
statusSectionInitialValues[sectionClass] = values;
|
||||
}
|
||||
|
||||
function captureAllStatusSectionInitialValues() {
|
||||
STATUS_SECTION_CLASSES.forEach(captureSectionInitialValues);
|
||||
}
|
||||
|
||||
function hasSectionChanged(sectionClass) {
|
||||
if (!sectionClass) {
|
||||
return false;
|
||||
}
|
||||
var initial = statusSectionInitialValues[sectionClass];
|
||||
if (!initial) {
|
||||
return false;
|
||||
}
|
||||
var changed = false;
|
||||
$('.' + sectionClass).find('input, textarea, select').each(function() {
|
||||
var el = this;
|
||||
var key = el.id || el.name;
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (el.type === 'file') {
|
||||
if (el.files && el.files.length > 0) {
|
||||
changed = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
var current = String($(el).val() || '');
|
||||
var original = String(initial[key] !== undefined ? initial[key] : '');
|
||||
if (current !== original) {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
function updateSectionRequiredLabels(sectionClass, isRequired) {
|
||||
if (!sectionClass) {
|
||||
return;
|
||||
}
|
||||
$('.' + sectionClass).find('input, textarea, select').each(function() {
|
||||
var fieldId = $(this).attr('id');
|
||||
if (!fieldId || fieldId === 'approved_description') {
|
||||
return;
|
||||
}
|
||||
var $label = $("label[for='" + fieldId + "']");
|
||||
if (!$label.length) {
|
||||
return;
|
||||
}
|
||||
if (isRequired && !$label.find('.text-danger').length) {
|
||||
$label.append(' <span class="text-danger">*</span>');
|
||||
} else if (!isRequired) {
|
||||
$label.find('.text-danger').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyConditionalRequiredForSection(sectionClass) {
|
||||
if (!sectionClass) {
|
||||
return;
|
||||
}
|
||||
if (sectionClass === 'approved' || sectionClass === 'settled') {
|
||||
enforceLetterPairRules();
|
||||
updateSectionRequiredLabels(sectionClass, hasSectionChanged(sectionClass));
|
||||
return;
|
||||
}
|
||||
var changed = hasSectionChanged(sectionClass);
|
||||
$('.' + sectionClass).find('input, textarea, select').each(function() {
|
||||
if (this.id === 'approved_description') {
|
||||
return;
|
||||
}
|
||||
$(this).prop('required', changed);
|
||||
});
|
||||
updateSectionRequiredLabels(sectionClass, changed);
|
||||
}
|
||||
|
||||
function refreshAllStatusSectionRequired() {
|
||||
STATUS_SECTION_CLASSES.forEach(applyConditionalRequiredForSection);
|
||||
$('#approved_description').prop('required', false);
|
||||
$("label[for='approved_description'] .text-danger").remove();
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
@ -645,6 +743,8 @@
|
||||
GlobelExtraFields = extraFields;
|
||||
console.log("extraFields", extraFields);
|
||||
claimStatusFieldChanges(extraFields);
|
||||
captureAllStatusSectionInitialValues();
|
||||
refreshAllStatusSectionRequired();
|
||||
handleTPARequired(tpa_id_for_hid_filed);
|
||||
// syncApprovedLetterInputs();
|
||||
// syncSettleLetterInputs();
|
||||
@ -736,24 +836,16 @@
|
||||
showClass = '.payment';
|
||||
}
|
||||
|
||||
// Show the relevant section and enable required attributes
|
||||
if (showClass) {
|
||||
// $(showClass).show().find('input, textarea').attr('required', true);
|
||||
$(showClass).show().find('input, textarea').attr('required', true).each(function() {
|
||||
var $label = $("label[for='" + $(this).attr('id') + "']");
|
||||
if ($label.length && !$label.find('.text-danger').length) {
|
||||
$label.append(' <span class="text-danger">*</span>');
|
||||
}
|
||||
});
|
||||
|
||||
$(showClass).show();
|
||||
captureSectionInitialValues(showClass.replace('.', ''));
|
||||
}
|
||||
|
||||
$field = $('#approved_description')
|
||||
$field.prop('required', false);
|
||||
var $parentDiv = $field.closest("div");
|
||||
$parentDiv.find("label[for='approved_description'] .text-danger").remove();
|
||||
syncApprovedLetterInputs();
|
||||
syncSettleLetterInputs();
|
||||
refreshAllStatusSectionRequired();
|
||||
}
|
||||
|
||||
$('#claim_status_id').on('change', function() {
|
||||
@ -837,13 +929,13 @@
|
||||
const hasUrl = $.trim($urlInput.val()) !== '';
|
||||
const hasFile = $fileInput[0].files && $fileInput[0].files.length > 0;
|
||||
const isApprovedSectionVisible = $urlInput.closest('.approved').is(':visible');
|
||||
const approvedSectionChanged = hasSectionChanged('approved');
|
||||
|
||||
$fileInput.prop('disabled', hasUrl);
|
||||
$urlInput.prop('disabled', hasFile);
|
||||
|
||||
// Keep the file optional; URL remains conditionally required for approved state unless file is present.
|
||||
$fileInput.prop('required', false);
|
||||
$urlInput.prop('required', isApprovedSectionVisible && !hasFile);
|
||||
$urlInput.prop('required', isApprovedSectionVisible && approvedSectionChanged && !hasFile);
|
||||
}
|
||||
|
||||
function syncSettleLetterInputs() {
|
||||
@ -857,12 +949,13 @@
|
||||
const hasUrl = $.trim($urlInput.val()) !== '';
|
||||
const hasFile = $fileInput[0].files && $fileInput[0].files.length > 0;
|
||||
const isSettledSectionVisible = $urlInput.closest('.settled').is(':visible');
|
||||
const settledSectionChanged = hasSectionChanged('settled');
|
||||
|
||||
$fileInput.prop('disabled', hasUrl);
|
||||
$urlInput.prop('disabled', hasFile);
|
||||
|
||||
$fileInput.prop('required', false);
|
||||
$urlInput.prop('required', isSettledSectionVisible && !hasFile);
|
||||
$urlInput.prop('required', isSettledSectionVisible && settledSectionChanged && !hasFile);
|
||||
}
|
||||
|
||||
function enforceLetterPairRules() {
|
||||
@ -956,10 +1049,14 @@
|
||||
}
|
||||
|
||||
var selectedClaimStatus = $('#claim_status_id').val();
|
||||
|
||||
if (thirdClass === 'approved' || thirdClass === 'settled') {
|
||||
applyConditionalRequiredForSection(thirdClass);
|
||||
return;
|
||||
}
|
||||
|
||||
var sectionChanged = hasSectionChanged(thirdClass);
|
||||
var $thirdClass = $(`.${thirdClass}`);
|
||||
var hasValue = $thirdClass.find('input, textarea, select').filter(function() {
|
||||
return $(this).val().trim() !== ''; // Check if at least one field is not empty
|
||||
}).length > 0;
|
||||
|
||||
$thirdClass.each(function() {
|
||||
var id = $(this).find('[id]').first().attr('id');
|
||||
@ -967,27 +1064,19 @@
|
||||
var $thisDiv = $(this);
|
||||
var $inputs = $thisDiv.find('input, textarea, select');
|
||||
|
||||
|
||||
// ✅ Condition 1: Check if this ID is in extraFieldsArray (required by status)
|
||||
var shouldAddRequired = extrafieldsArray[selectedClaimStatus] && extrafieldsArray[selectedClaimStatus].includes(id);
|
||||
|
||||
// ✅ Condition 2: If any field in the class has a value, make all required
|
||||
if (shouldAddRequired || hasValue) {
|
||||
if (shouldAddRequired || sectionChanged) {
|
||||
if ($label.length && !$label.find('.text-danger').length) {
|
||||
$label.append(' <span class="text-danger">*</span>');
|
||||
}
|
||||
console.log("input from if ", $inputs);
|
||||
// $inputs.attr('required', true);
|
||||
$('.' + thirdClass).find('input, textarea,select').attr('required', true);
|
||||
} else {
|
||||
$label.find('.text-danger').remove();
|
||||
console.log("input ", $inputs);
|
||||
// $inputs.prop('required', false);
|
||||
$('.' + thirdClass).find('input, textarea,select').attr('required', false);
|
||||
}
|
||||
});
|
||||
|
||||
// Re-apply URL/file pair rules after bulk required toggles.
|
||||
enforceLetterPairRules();
|
||||
|
||||
}
|
||||
@ -1034,12 +1123,12 @@
|
||||
$field.prop('required', false);
|
||||
var $parentDiv = $field.closest("div");
|
||||
$parentDiv.find("label[for='approved_description'] .text-danger").remove();
|
||||
enforceLetterPairRules();
|
||||
refreshAllStatusSectionRequired();
|
||||
|
||||
});
|
||||
|
||||
$('#ticket_form_data').on('submit', function() {
|
||||
enforceLetterPairRules();
|
||||
refreshAllStatusSectionRequired();
|
||||
});
|
||||
|
||||
// function addRequiredFieldSymbol(field_name) {
|
||||
|
||||
@ -269,12 +269,12 @@ table.dataTable tbody td {
|
||||
<td style="display: none;"><?php echo $row['insurer_name']; ?></td>
|
||||
<td style="display: none;"><?php echo $row['tpa_name']; ?></td>
|
||||
<td style="display: none;"><?php echo $row['policy_no']; ?></td>
|
||||
<td style="display: none;"><?php echo isset($priorityType[$row['priority'] ?? 0]) ? $priorityType[$row['priority'] ?? 0] : ''; ?></td>
|
||||
<td style="display: none;"><?php echo ($priorityType ?? [])[$row['priority'] ?? 0] ?? ''; ?></td>
|
||||
<td style="display: none;"><?php echo $row['relationship']; ?></td>
|
||||
<td style="display: none;"><?php echo $row['emp_mobile']; ?></td>
|
||||
<td style="display: none;"><?php echo $row['emp_mail']; ?></td>
|
||||
<td style="display: none;"><?php echo $row['emp_personal_mail']; ?></td>
|
||||
<td style="display: none;"><?php echo isset($modeOFIntimate[$row['mode_of_intimation'] ?? '']) ? $modeOFIntimate[$row['mode_of_intimation']] : ''; ?></td>
|
||||
<td style="display: none;"><?php echo ($modeOFIntimate ?? [])[$row['mode_of_intimation'] ?? ''] ?? ''; ?></td>
|
||||
<td style="display: none;">
|
||||
<?php
|
||||
$typeId = $row['ticket_type_id'] ?? 0;
|
||||
@ -393,7 +393,8 @@ function getClaimSourceBadgeHtml(claimCreatedBy) {
|
||||
if (!cls) {
|
||||
return '';
|
||||
}
|
||||
return '<span class="claim-source-badge ' + cls + '">' + escapeHtml(key) + '</span>';
|
||||
var label = key === 'CRM' ? 'STAFF' : key;
|
||||
return '<span class="claim-source-badge ' + cls + '">' + escapeHtml(label) + '</span>';
|
||||
}
|
||||
|
||||
function getPolicyTypeIconHtml(tpaClaimType) {
|
||||
|
||||
@ -29,7 +29,44 @@
|
||||
}
|
||||
|
||||
function isSkippableField(el) {
|
||||
return !el || el.disabled || el.type === 'hidden';
|
||||
if (!el || el.disabled || el.type === 'hidden') {
|
||||
return true;
|
||||
}
|
||||
if (el.classList && el.classList.contains('select2-search__field')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isVisibleForValidation(el) {
|
||||
if (isSkippableField(el)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var jq = getJq();
|
||||
if (!jq) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var $el = jq(el);
|
||||
if (!$el.length || !$el.is(':visible')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $el.closest('.form-group, .dynamic-form-row, .claim-row, .col-md-4, .col-md-12').filter(function () {
|
||||
var display = jq(this).css('display');
|
||||
return display === 'none';
|
||||
}).length === 0;
|
||||
}
|
||||
|
||||
function isEmptyValue(val) {
|
||||
if (val == null || val === '') {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(val)) {
|
||||
return val.length === 0;
|
||||
}
|
||||
return String(val).trim() === '';
|
||||
}
|
||||
|
||||
function isPanOrGstField(el) {
|
||||
@ -95,7 +132,7 @@
|
||||
return true;
|
||||
}
|
||||
if (instance.$element.prop('required') && v.length === 0) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return v.length === 10;
|
||||
},
|
||||
@ -181,7 +218,7 @@
|
||||
}
|
||||
|
||||
function validateField(field) {
|
||||
if (!field || isSkippableField(field) || !isParsleyReady()) {
|
||||
if (!field || !isVisibleForValidation(field) || !isParsleyReady()) {
|
||||
return;
|
||||
}
|
||||
var $field = $(field);
|
||||
@ -200,6 +237,70 @@
|
||||
}
|
||||
}
|
||||
|
||||
function prepareHiddenFieldsForSubmit($form) {
|
||||
if (!$form || !$form.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form.find('input, textarea, select').each(function () {
|
||||
var el = this;
|
||||
var $el = $(el);
|
||||
|
||||
if (isVisibleForValidation(el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$el.removeAttr('required').prop('required', false);
|
||||
clearAttrs($el);
|
||||
|
||||
try {
|
||||
if (typeof $el.parsley === 'function') {
|
||||
var fieldInstance = $el.parsley();
|
||||
if (fieldInstance && typeof fieldInstance.reset === 'function') {
|
||||
fieldInstance.reset();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// no-op
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getInvalidFields($form) {
|
||||
var invalid = [];
|
||||
|
||||
if (!$form || !$form.length || !isParsleyReady()) {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
var instance = $form.parsley();
|
||||
if (!instance) {
|
||||
return invalid;
|
||||
}
|
||||
|
||||
instance.fields.forEach(function (field) {
|
||||
if (field.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var el = field.$element[0];
|
||||
if (!isVisibleForValidation(el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = field.$element.val();
|
||||
invalid.push({
|
||||
id: field.$element.attr('id') || '',
|
||||
name: field.$element.attr('name') || '',
|
||||
label: (field.$element.closest('.form-group, .mb-3, .col-md-4, .col-lg-4').find('label').first().text() || '').trim(),
|
||||
messages: field.getErrorsMessages ? field.getErrorsMessages() : [],
|
||||
isEmpty: isEmptyValue(val)
|
||||
});
|
||||
});
|
||||
|
||||
return invalid;
|
||||
}
|
||||
|
||||
function init() {
|
||||
var $form = $(FORM_SELECTOR);
|
||||
if (!$form.length || !isParsleyReady()) {
|
||||
@ -207,6 +308,7 @@
|
||||
}
|
||||
|
||||
registerValidators();
|
||||
prepareHiddenFieldsForSubmit($form);
|
||||
applyConstraints($form);
|
||||
refreshParsley($form);
|
||||
|
||||
@ -226,6 +328,22 @@
|
||||
window.refreshLeadsFormValidation = function () {
|
||||
init();
|
||||
};
|
||||
|
||||
window.prepareLeadsFormForSubmit = function () {
|
||||
var $form = $(FORM_SELECTOR);
|
||||
if (!$form.length || !isParsleyReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
registerValidators();
|
||||
prepareHiddenFieldsForSubmit($form);
|
||||
applyConstraints($form);
|
||||
refreshParsley($form);
|
||||
};
|
||||
|
||||
window.getLeadsFormInvalidFields = function () {
|
||||
return getInvalidFields($(FORM_SELECTOR));
|
||||
};
|
||||
})(function () {
|
||||
return window.jQuery;
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user