From aac694ca3db52feecff92a498b4c97b377da02c8 Mon Sep 17 00:00:00 2001 From: velz Date: Fri, 22 May 2026 15:01:14 +0530 Subject: [PATCH] FEAT_DOCS_CONTD --- app/Controllers/Docs/DocsController.php | 228 +++++++ app/Controllers/EmployeeController.php | 1 + app/Views/docs/bds-commission.php | 292 +++++++++ app/Views/docs/bds-insurer-statement.php | 309 ++++++++++ app/Views/docs/correction.php | 265 ++++++++ app/Views/docs/deletion.php | 259 ++++++++ app/Views/docs/docs_header.php | 12 + app/Views/docs/docs_main_open.php | 36 +- app/Views/docs/eb-rack-rate-calculation.php | 290 +++++++-- app/Views/docs/inception.php | 389 ++++++++++++ app/Views/docs/non-eb-claims.php | 488 +++++++++++++++ app/Views/docs/non-eb-opportunities.php | 638 ++++++++++++++++++++ app/Views/docs/partials/docs_header.php | 12 + app/Views/docs/partials/docs_main_open.php | 36 +- app/Views/docs/si-enhancement.php | 259 ++++++++ 15 files changed, 3449 insertions(+), 65 deletions(-) create mode 100644 app/Views/docs/bds-commission.php create mode 100644 app/Views/docs/bds-insurer-statement.php create mode 100644 app/Views/docs/correction.php create mode 100644 app/Views/docs/deletion.php create mode 100644 app/Views/docs/inception.php create mode 100644 app/Views/docs/non-eb-claims.php create mode 100644 app/Views/docs/non-eb-opportunities.php create mode 100644 app/Views/docs/si-enhancement.php diff --git a/app/Controllers/Docs/DocsController.php b/app/Controllers/Docs/DocsController.php index ee117868..d85d77b0 100644 --- a/app/Controllers/Docs/DocsController.php +++ b/app/Controllers/Docs/DocsController.php @@ -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'], ], diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php index 05873250..1539620c 100755 --- a/app/Controllers/EmployeeController.php +++ b/app/Controllers/EmployeeController.php @@ -4175,6 +4175,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'); diff --git a/app/Views/docs/bds-commission.php b/app/Views/docs/bds-commission.php new file mode 100644 index 00000000..55f83aed --- /dev/null +++ b/app/Views/docs/bds-commission.php @@ -0,0 +1,292 @@ + + +

+ BDS commission is a two-part flow: admins upload commission rules + (Excel) per insurer, month, and department; partner/BDS systems then call an API to + calculate payout for a policy using those rules. +

+ +

Overview

+ +
+
+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"] +
+
+ +

In short:

+ + +

Key files

+ + + + + + + + + + + + + +
PartFile
Upload list UIapp/Views/commission_file_upload.php
Rules editor UIapp/Views/commission_rules_list.php
Upload and editor APIapp/Controllers/RuleImportController.php
Excel parsingruleImportService (via Config\Services::ruleImportService())
Payout calculation APIapp/Controllers/InsuranceCommissionController.php
Upload metadata DBcommission_files (CommissionFilesModel)
+ +

Routes

+ +

Admin (commission group):

+ + + + + + + + + + + + + + + + +
RouteHandler
GET/POST commission/listcommissionFileUploadList
POST commission/uploadupload
GET commission/sample_fileSample CSV download
GET commission/downloadErrorFileAnnotated error Excel
GET commission/checkSameEntryDuplicate insurer + month + department check
GET commission/rules/list/(:id)ruleList — rules editor page
POST commission/rules/save/saveRule
POST commission/rules/remove/removeRule
GET commission/checkRuleUsageWhether rule is used on partner policies
GET commission/deleteCommissionData/(:id)Soft-delete file + mark rules deleted
+ +

Runtime API:

+
POST getCommission
+  → InsuranceCommissionController::initiateCommissionCalc
+  → filter: CommissionApiFilter
+ +

How to build the commission file

+ +

+ Use the template from the upload screen (Download sample file) or copy from + public/sample_excel/sample_commission.csv. Dev copies also live under + writable/uploads/commission/files/ (e.g. sample_commission.csv, + Sample_commission_file-New.xlsx). After a successful import, see the generated JSON under + writable/uploads/commission/rules/{MONYYYY}/ (example: NOV2025/1_motor.json). +

+ +
+ ! +
+ Use the current column layout + Some older CSVs in writable/uploads/commission/files/ use legacy headers + (Rule Name, Commission Params(TP:OD:PA)). The importer expects the + S.No layout below (RuleImportService). Wrong headers fail with + “Missing required columns”. +
+
+ +

Required columns (row 1 headers)

+ +

Header text must match exactly (one row per rule, starting row 2). Empty cells are allowed and simply skip that condition.

+ + + + + + + + + + + + + + + + + + + + + + + +
ColumnPurposeExample
S.NoSerial (not used in logic)1
Premium TypeMaps to policy_typeOD, TP, COM
Vehicle Typevehicle_type; comma = multiple (IN)Two Wheeler, Four Wheeler
Vehicle Sub Typevehicle_sub_typeCar, PCV
Make / ModelVehicle make and modelHonda, i10
CC Min / CC MaxCubic capacity range1000, 3000
Fuel TypeComma-separated fuelsPetrol, Diesel
Vehicle Age Min / MaxVehicle age range (years)1, 5
Vehicle Weight Min / MaxWeight range (kg)2000, 3000
RTO State / RTO Codegeo_rto_state, geo_rto_cityRJ, 41
Renewal Typerenewal_typeOnline, Cash, Card
Commission Typepercentage, composite, flat, tieredpercentage
Commission Value% or flat amount (required for percentage / flat)15 or 500
Commission Params (TP)Composite: % on TP premium18
Commission Params (OD)Composite: % on OD premium10
Commission Params (PA)Composite: % on PA premium0 or empty
+ +

Commission types (what to fill)

+ + + + + + + + + + + + + + + + + + + + + + +
TypeFill in sheetBecomes in JSON
percentageCommission Value = e.g. 1010% of premium
compositeLeave Value empty; set TP / OD / PA param columns (percent each)Split % on tp_premium, od_premium, pa_premium
flatCommission Value = fixed rupee amount e.g. 500Fixed payout on premium
+ +

Sample rows (from project files)

+ +

Template header + rows (public/sample_excel/sample_commission.csv):

+
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,
+ +

Example A — composite two-wheeler (from writable/.../rules/NOV2025/1_motor.json):

+ + +

Example B — percentage four-wheeler:

+ + +

Example C — composite TP-only:

+ + +

Example D — flat amount:

+ + +

Tips:

+ + +

Rule upload

+ +

From commission_file_upload.php the user picks insurer, commission month, department (motor / health), and an Excel/CSV file.

+ +
    +
  1. checkSameEntry — if a successful upload already exists for the same trio, SweetAlert offers Overwrite or Append (overwrite=1 or 0 on POST).
  2. +
  3. upload — stores file under writable/uploads/commission/files/, inserts commission_files row (pending).
  4. +
  5. ruleImportService->processUpload() — validates Excel rows; on success returns rules array; on failure returns annotated_file for download.
  6. +
  7. On success — writes JSON to rules/{MONYYYY}/{insurer_id}_{department}.json; sets file_status=success and rules_count.
  8. +
  9. On failure — file_status=failed; user downloads annotated_{filename} via downloadErrorFile.
  10. +
+ +

Rules editor

+ +

+ For successful uploads, action View Rules opens + commission/rules/list/{file_id} (commission_rules_list.php). +

+ + +

Rule JSON shape

+ +

Each rule is roughly:

+
{
+  "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"
+  }
+}
+ +

Calculation types in InsuranceCommissionController: percentage, composite, fixed. Conditions support ==, !=, >, >=, <, <=, between, in.

+ +

Commission calculation API

+ +

initiateCommissionCalc() expects POST/JSON including at least:

+ + +
+
+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"] +
+
+ +

Rules with is_deleted: false are loaded; the first matching rule wins (no priority field yet).

+ +

Developer steps

+ +
    +
  1. Open /commission/list (logged-in admin).
  2. +
  3. Download sample file, fill rules for insurer + month + department, upload.
  4. +
  5. If validation fails, download the annotated error file and fix the sheet.
  6. +
  7. Use View Rules to tweak conditions or calculation without re-uploading the whole file.
  8. +
  9. Test payout: POST getCommission with the same insurer, department, and a policy_issue_date in that commission month.
  10. +
+ +

Common pitfalls

+ + diff --git a/app/Views/docs/bds-insurer-statement.php b/app/Views/docs/bds-insurer-statement.php new file mode 100644 index 00000000..a504be59 --- /dev/null +++ b/app/Views/docs/bds-insurer-statement.php @@ -0,0 +1,309 @@ + + +

+ BDS Insurer statement lets finance users upload an insurer-provided Excel statement, + validate each row against NHance policy transactions (pt_co_share_details + + policy_transaction), and persist matched brokerage amounts into + co_share_stmt_details. The admin UI is + app/Views/insurer_statement_list.php; all server logic lives in + PolicyTransactionController under the policy_tranction/statement route group. +

+ +
+ i +
+ Auth + Statement routes use the authMVC filter. Upload and list are browser AJAX/form calls from an authenticated session, not public API endpoints. +
+
+ +

Overview

+ +
+
+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] +
+
+ +

Row validation logic

+ +

+ validateInsurerStatement() checks that every Excel line maps to a real NHance transaction + for the selected insurer branch. Each row is matched on policy number (column B) and + endorsement number (column C). +

+ +
+
+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"] +
+
+ +

In short:

+ + +

Key files and routes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaLocation
UIapp/Views/insurer_statement_list.php — DataTable list, upload modal, validation error modal, invoice modal
Controllerapp/Controllers/PolicyTransactionController.php
Statement header modelapp/Models/InsurerStatements.phpinsurer_statements
Line-item modelapp/Models/COShareStmtDetailsModel.phpco_share_stmt_details
NHance source rowsapp/Models/PTCOShareDetailsModel.phpgetNonReconcileredPolicyTransactionByPolicyAndEndorsement()
Sample Excelpublic/sample_excel/insurer_stament_sample.xlsx
+ +

Routes (prefix policy_tranction/statement, filter authMVC):

+ + + + + + + + + + + + + + + + +
MethodRouteHandler
GETliststatementList
POSTuploaduploadInsurerStatement
GETdownloadSampleInsurerStatementSample file download
GETdownloadInsurerStatement/(:num)Uploaded file download
GETgetFileErr/(:any)Validation failure JSON for modal
GETgetInsurerStatementMonthUsed to disable already-used statement numbers
GETdeleteStatement/(:any)Soft-delete statement + related rows
GETgetPaymentDetails/(:any)Invoice modal data
POSTsaveInvoicePaymentDetailsInvoice / payment save
+ +
$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');
+        // ...
+    });
+});
+ +

Statement list UI

+ +

+ statementList() loads insurers/branches via + insurerBranchModel::getInsurerBranchesWithInsurerNames(), invoice status labels, and + statements from the last 180 days (is_active = 1). The view shows: +

+ + + +

+ Upload form fields (#insurer_statement_upload_form): insurer + (insurer_id-branch_id), statement month (flatpickr), statement no (1–7), Excel file. + Submit is AJAX POST to relative upload. On success the page reloads; on validation failure + the API still returns HTTP 200 with dataStatus: false and error_data. +

+ +

uploadInsurerStatement()

+ +
    +
  1. Validates uploaded file: Excel MIME types, max 16 MB (max_size[statement,16384] KB).
  2. +
  3. Moves file to WRITEPATH . 'uploads/statements/' (see createStatementFolder() for folder creation).
  4. +
  5. Parses POST: insurer as {insurer_id}-{branch_id}, statement_month (converted to first-of-month Y-m-d), statement_nostmt_sno.
  6. +
  7. Inserts insurer_statements row via InsurerStatements model.
  8. +
  9. Calls validateInsurerStatement(['file_id' => $file_id]).
  10. +
  11. If validation passes, calls updateInsurerStatement(['file_id' => $file_id]).
  12. +
  13. On validation failure: responds with dataStatus: false, error_data, error_code (HTTP 200).
  14. +
  15. On success: sets invoice_status = 'pending' and returns dataStatus: true.
  16. +
+ +

validateInsurerStatement($params)

+ +

Runs immediately after upload (and can be re-run manually in dev with a hard-coded file_id in statementList() comment).

+ +

Steps

+ +
    +
  1. Load insurer_statements by file_id; fail if missing or physical file absent under writable/uploads/statements/.
  2. +
  3. Load active sheet via PhpSpreadsheet; drop header row; sanitize with ExcelSanitizeHelper::sanitizeArrayData().
  4. +
  5. Collect unique policy numbers from column B (index 1), skipping empty rows via check_row_is_empty_or_null().
  6. +
  7. Fetch NHance candidates: + PTCOShareDetailsModel::getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id, branch_id, policy_no[]) + — matches pt.policy_no for completed transactions on that insurer branch.
  8. +
  9. Build lookup keys policy_no|endorsement_no (sanitized) for source and Excel rows.
  10. +
  11. For each non-empty Excel row, match policy + endorsement; track duplicates in $matched_entry.
  12. +
  13. On mismatch, append row-wise HTML messages under error_data[row_index].
  14. +
  15. Update insurer_statements.line_items, file_status (success / failed), reason (JSON).
  16. +
+ +

sanitizeStatementLookupValue()

+ +

Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel.

+ +

Validation error codes

+ + + + + + + + + + +
error_codeMeaningUI
0DB row missing or physical file not foundPlain message in modal
1Legacy: list of row numbers (old format)Comma-separated row list
2Row-wise validation (current)Modal lists each row with policy / endorsement / duplicate messages
+ +

Row keys in error_data are the array index from the Excel loop (first data row is typically 1 after header removal), not necessarily the Excel row number on sheet.

+ +

updateInsurerStatement($params)

+ +

Runs only when validation returned status: true.

+ +
    +
  1. Same file load / sanitize path as validation.
  2. +
  3. Rebuild policy list and source lookup (policy + endorsement → array of pt_co_share_details rows).
  4. +
  5. For each Excel row with a matching source entry, compute brokerage totals and variance: +
      +
    • BP: amount col 3, brokerage col 5
    • +
    • TP: amount col 4, brokerage col 6
    • +
    • TEP: forced to 0 in current code
    • +
    • reward from col 7
    • +
    • variance = exp_amt - total_amt (from source exp_amt)
    • +
    +
  6. +
  7. COShareStmtDetailsModel::insertBatch($data_to_update) — one row per matched Excel line.
  8. +
  9. Sets file_status = success, invoice_status = pending, clears/sets reason.
  10. +
+ +

Excel column mapping (0-based index)

+ +

Header row is removed; data columns used by validation/update:

+ + + + + + + + + + + + + + +
IndexColumnUse
1BPolicy number (required for matching)
2CEndorsement number
3DActual BP amount
4EActual TP amount
5FActual BP brokerage
6GActual TP brokerage
7HReward
+ +

Download the canonical layout from the list page link → downloadSampleInsurerStatement.

+ +

NHance source query

+ +

getNonReconcileredPolicyTransactionByPolicyAndEndorsement() joins:

+ + + +

+ Matching is on sanitized policy_no + endorsement_no. The method name suggests + “non-reconciled” but the current query does not filter statement_id IS NULL; + be aware when re-uploading or debugging duplicate reconciliation. +

+ +

Invoice status and delete

+ +

After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):

+ + + +

+ getInsurerStatementMonth returns successful statements for insurer+month so the UI can + disable statement numbers already used (disableStatementNo() in the view). +

+ +

Developer steps

+ +
    +
  1. Ensure writable/uploads/statements/ exists and is writable (or call createStatementFolder() once).
  2. +
  3. Open policy_tranction/statement/list in a logged-in session.
  4. +
  5. Use sample Excel; pick insurer branch and month; choose an unused statement number (1–7 per insurer/month).
  6. +
  7. Confirm policy/endorsement exist on a completed BDS transaction for that insurer branch.
  8. +
  9. On failure, open the alert icon → modal calls getFileErr/{id} and renders reason JSON.
  10. +
  11. To debug validation only: temporarily uncomment the validateInsurerStatement / updateInsurerStatement one-liner in statementList() with a known file_id.
  12. +
+ +

Common pitfalls

+ + + + + + diff --git a/app/Views/docs/correction.php b/app/Views/docs/correction.php new file mode 100644 index 00000000..d8d68029 --- /dev/null +++ b/app/Views/docs/correction.php @@ -0,0 +1,265 @@ + + +

+ Correction updates existing member data on an active policy via Excel upload + (files.action = correction). The final step creates pending correction endorsements + on the employees table — data is not updated until those endorsements are applied downstream. +

+ +

+ Processing is in EmployeeServiceController::employeesCorrectionProcess, queued after the same + format and data validation steps used for inception and deletion. +

+ +

Overview

+ +
+
+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"] +
+
+ +

In short:

+ + +

Key files and routes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaLocation
UIapp/Views/employee_upload.php — action Correction
UploadEmployeeController::employeesUplodWithEvents
ValidationexcelFileFormatValidation, excelFileDataValidation
Correction processEmployeeServiceController::employeesCorrectionProcess
Column config APIgetCorrectionExcelColumns() — used when building correction Excel programmatically
JobemployeesCorrectionProcess in JobWorker.php
EndorsementsEmpEndorsementModelactions = c, table_name = employees, status = pending
+ +

Routes (group /employee, authMVC):

+ + +
+ i +
+ files.policy_id is the client policy id. Lookup requires active + employees + employee_polices on that policy and branch. +
+
+ +

Sync vs background jobs

+ + + + + + + + + + +
Step< 1 MB≥ 1 MB
Format validationInline on uploadJob excelFileFormatValidation
Data validationJob excelFileDataValidationSame
Correction processJob employeesCorrectionProcessSame
+ +

Step 1: excelFileFormatValidation

+ + + +

Format error codes

+ + + + + + + + + + + + + +
CodeMeaning
1Mandatory missing
2Wrong format
3Not in allowed list (e.g. invalid Field value)
4Custom validation failed
5File / policy problem
6Column headers wrong
+ +

Step 2: excelFileDataValidation

+ +

Rows grouped by EMP ID; for correction the critical check is name_and_empid_check_in_db:

+ + +

On success → queues employeesCorrectionProcess (with optional batch_file_id for TPA multi-file flows).

+ +

Step 3: employeesCorrectionProcess

+ +
+
+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"] +
+
+ +

Per row:

+ + +

+ Skips insert when a pending correction endorsement already exists for the same + emp_code, name, and field_name + (actions = c, endorsement_id IS NULL, status != truncated). +

+ +

+ Sets files.status = success 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). +

+ +

+ TPA batch: If batch_file_id is set, also queues + updateEmployeeDataFromTpa, reconTpaApiDataWithEmployeepolicies, and + initializeDeletionProcessForTpaApiData. +

+ +

Correction Excel columns

+ + + + + + + + + + + + + + + +
ColHeaderRequiredNotes
AS.NoYes
BEMP IDYes
CNAME OF EMP/DEPYesMust match DB before correction
DFieldYesname, dob, relationship, email_corporate
EValueYesNew value; DOB as d-M-Y
FDate of CorrectionYesd-M-Y
GChange eventYese.g. correction
HRemarksNoStored on endorsement
+ +

Row layout examples

+ +

Fix DOB — one row, one field:

+ + + + + + + + + + + + + + + + +
EMP IDNAMEFieldValueDate of CorrectionChange eventRemarks
EMP001Raj Kumardob15-Jan-198519-May-2026correctionTypo in upload
+ +

Multiple fixes — use separate rows (same or different members):

+ + + + + + + + + +
EMP IDNAMEFieldValue
EMP001Raj Kumaremail_corporateraj.kumar@company.com
EMP001Priya KumarrelationshipSpouse
+ +

Developer steps

+ +
    +
  1. Upload with upload-action-type=correction; note file_id.
  2. +
  3. On validation failure, check /employee/excel_error/{file_id} for code 10.
  4. +
  5. After success, query emp_endorsement where file_id = upload id, actions = 'c', status = 'pending'.
  6. +
  7. Compare field_name, old_value, new_value per row to the Excel.
  8. +
  9. To generate correction Excel in code, use getCorrectionExcelColumns() for header layout.
  10. +
+ +

Common pitfalls

+ + + +

+ Related: + Inception, + Deletion. +

diff --git a/app/Views/docs/deletion.php b/app/Views/docs/deletion.php new file mode 100644 index 00000000..aff0fdba --- /dev/null +++ b/app/Views/docs/deletion.php @@ -0,0 +1,259 @@ + + +

+ Deletion removes active members from a client policy via Excel upload + (files.action = deletion). Unlike inception, the final step does not delete rows immediately — + it creates pending endorsement records on employee_polices for approval/processing later. +

+ +

+ Core processing lives in EmployeeServiceController::employeeDisembark. + EmployeeController::initializeDeletionProcessForTpaApiData is a separate TPA-reconcile path that + builds a deletion Excel file and calls the same disembark function. +

+ +

Overview

+ +
+
+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"] +
+
+ +

In short:

+ + +

Key files and routes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaLocation
UIapp/Views/employee_upload.php — action Deletion
UploadEmployeeController::employeesUplodWithEvents
ValidationEmployeeServiceController::excelFileFormatValidation, excelFileDataValidation
Deletion processEmployeeServiceController::employeeDisembark
TPA auto-deletionEmployeeController::initializeDeletionProcessForTpaApiData
JobemployeeDisembark in JobWorker.php
EndorsementsEmpEndorsementModelemp_endorsement (actions = d, status = pending)
+ +

Routes (group /employee, authMVC):

+ + +
+ i +
+ files.policy_id is the client policy id. Member lookup joins + employees + employee_polices on that policy and branch. +
+
+ +

Sync vs background jobs

+ + + + + + + + + + +
Step< 1 MB≥ 1 MB
Format validationInline on uploadJob excelFileFormatValidation
Data validationJob excelFileDataValidationSame
DisembarkJob employeeDisembarkSame
+ +

Step 1: excelFileFormatValidation

+ + + +

Format error codes

+ + + + + + + + + + + + + +
CodeMeaning
1Mandatory missing
2Wrong format
3Not in allowed list
4Custom validation failed
5File / policy problem
6Column headers wrong
+ +

Step 2: excelFileDataValidation

+ +

Rows are grouped by EMP ID. For deletion, the main check is name_and_empid_check_in_db:

+ + +

On success → queues employeeDisembark (not employeesOnboardPreprocess).

+ +

Step 3: employeeDisembark

+ +

Loads the Excel again and processes each non-empty row.

+ +
+
+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 +
+
+ +

Per matched member, four pending endorsement rows are inserted on employee_polices:

+ + +

+ Returns an array of processed employee.id values. Sets files.status = success when the loop finishes + (even if some rows were skipped — check logs for “not found” or “existing endorsement pending”). +

+ +

TPA auto-deletion (EmployeeController)

+ +

initializeDeletionProcessForTpaApiData($file_id):

+
    +
  1. Reads TPA reconcile file context (tpa_api_data, action_flag_status = D).
  2. +
  3. Builds candidate members and writes writable/uploads/excel/tpa_auto_deletion_{fileId}_{timestamp}.xls.
  4. +
  5. Inserts a new files row with action = deletion.
  6. +
  7. Calls employeeDisembark(['file_id' => $newFileId]) synchronously.
  8. +
  9. Exports endorsement data for TPA via getDeletionEmployeeDataForExportExcel.
  10. +
+ +

Deletion Excel columns

+ + + + + + + + + + + + + + +
ColHeaderRequiredNotes
AS.NoYes
BEMP IDYesEmployee / family code
CNAME OF EMP/DEPYesMust match DB name exactly
DChange eventYese.g. deletion
EDate of exitYesd-M-Y
FReason for exitYes
GClaim statusYes0 or 1
+ +

Row layout examples

+ +

Delete one dependent — only that name appears; Self row is not required in the file.

+ + + + + + + + +
RowEMP IDNAMEChange eventDate of exitReasonClaim
2EMP001Arjun Kumardeletion19-May-2026Resigned0
+

Result: endorsements for Arjun only (relationship ≠ Self).

+ +

Delete entire family — list the Self row; disembark loads all active family members for that EMP ID.

+ + + + + + + + +
RowEMP IDNAMEChange eventDate of exitReasonClaim
2EMP001Raj Kumardeletion19-May-2026Resigned0
+

Result: pending endorsements for Self + all active dependents on that policy (same exit date/reason/claim from the row).

+ +

Developer steps

+ +
    +
  1. Upload via /employee/upload with action deletion; note file_id.
  2. +
  3. If validation fails, use GET /employee/excel_error/{file_id} — look for code 10 (member not in DB).
  4. +
  5. After success, query emp_endorsement where file_id = upload id, actions = 'd', status = 'pending'.
  6. +
  7. If rows were skipped, search logs for not found or Existing endorsement pending.
  8. +
  9. TPA path: trace initializeDeletionProcessForTpaApiData and the generated tpa_auto_deletion_*.xls file.
  10. +
+ +

Common pitfalls

+ + + +

Related: Inception (onboard pipeline uses the same upload screen and first two validation steps).

diff --git a/app/Views/docs/docs_header.php b/app/Views/docs/docs_header.php index 2b8a8e6e..949ee16e 100644 --- a/app/Views/docs/docs_header.php +++ b/app/Views/docs/docs_header.php @@ -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); diff --git a/app/Views/docs/docs_main_open.php b/app/Views/docs/docs_main_open.php index e964f522..e9a75656 100644 --- a/app/Views/docs/docs_main_open.php +++ b/app/Views/docs/docs_main_open.php @@ -27,10 +27,11 @@ $toc = $toc ?? []; 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"] - - - -
- i -
- When maintaining this helper, inspect the implementation for duplicate pushes on the first row of a new rack name; - downstream code tolerates duplicate slab rows but it can confuse debugging of premium matches. -
-

get_familiy_composition

@@ -321,13 +294,122 @@ flowchart LR (already grouped to one employee). Keys align with the JSON used in rack configuration (additional_relationship), except either-parents-pil and elders_count which are stripped before comparison.

+

+ This function mainly: +

+

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"])
@@ -348,12 +430,87 @@ flowchart TD
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"]) +
@@ -378,13 +535,60 @@ flowchart TD
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"])
diff --git a/app/Views/docs/inception.php b/app/Views/docs/inception.php new file mode 100644 index 00000000..ad406a78 --- /dev/null +++ b/app/Views/docs/inception.php @@ -0,0 +1,389 @@ + + +

+ Inception here means onboarding employees and dependents from an Excel upload + (files.action = inception or related actions like missed_inception, + addition, dependent_addition). The same three-step pipeline runs for those + actions; this page focuses on the inception path. +

+ +

+ Manual policy inception (form UI under policy_tranction/inception) is a separate flow + in PolicyTransactionController — not covered by these three functions. +

+ +

Overview

+ +
+
+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"] +
+
+ +

In short:

+ + +

Key files and routes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaLocation
UIapp/Views/employee_upload.php — client, branch, policy, action Inception, file upload
Upload handlerEmployeeController::employeesUplodWithEvents
Pipeline logicEmployeeServiceController — the three functions on this page
Job dispatchapp/Controllers/JobWorker.php — maps job names to EmployeeServiceController
Column / Excel helpersapp/Helpers/excel_util_helper.phpcheck_columns_name, custom validators
Upload recordfiles table via FileModelstatus, action, reason
PremiumEB rack rate calculationcalculate_premium_new, employeesOnboardProcess
+ +

Routes (group /employee, filter authMVC):

+ + +

+ REST upload (mobile/HR): POST employeeRest/employeeUpload — same pipeline via + EmployeeRestController. +

+ +
+ i +
+ files.policy_id stores client policy id (client_policies.id), + not the insurer policy master id. Slab/rack lookups use this id with client_id. +
+
+ +

Sync vs background jobs

+ + + + + + + + + + + + + + + + + +
File sizeStep 1Steps 2–3
< 1 MBexcelFileFormatValidation runs inline in the upload requestAlways queued: excelFileDataValidationemployeesOnboardPreprocess
≥ 1 MBJob excelFileFormatValidationSame job chain after format passes
+ +

+ After upload the UI usually shows files.status = inprogress until jobs finish. + Poll notifications or refresh the upload list; use /employee/excel_error/{id} when status is failed. +

+ +

Entry points

+ +

Job chain for inception (and missed_inception / addition / dependent_addition):

+

excelFileFormatValidationexcelFileDataValidationemployeesOnboardPreprocess

+ +

Step 1: excelFileFormatValidation

+ +

Runs on the uploaded sheet before any DB business rules.

+ + + +

Common format error codes

+ + + + + + + + + + + + + +
CodeMeaning
1Mandatory value missing
2Wrong format (e.g. date, mobile)
3Value not in allowed list
4Custom validation failed (DOB, relationship, SI, etc.)
5File / policy / slab configuration problem
6Column headers wrong or out of order
+ +

On failure: files.status = failed, reason JSON with row/column errors; user notification via pull notification.

+ +

Lead-policy branch: If inception file is for a policy created from leads (policy_entry_from == 3 and is_from_lead set), format validation queues compareMemberDataAndInceptionData instead of going straight to data validation.

+ +

files.reason shape (debugging)

+ +

Stored as JSON string on files.reason. Typical failure payload:

+
{
+  "error_type": 1,
+  "error_summary": { "4": 2, "1": 1 },
+  "error_data": {
+    "3": {
+      "dob": { "error": ["Invalid date format"], "value": "01/01/1990" }
+    }
+  }
+}
+ + +

Step 2: excelFileDataValidation

+ +

Runs after format passes. Groups rows by EMP ID (family) and applies business rules.

+ +
+
+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"] +
+
+ +

Typical inception checks:

+ + +

On success for inception: queues job employeesOnboardPreprocess. Other actions queue different jobs (deletion, correction, etc.).

+ +

Common data-validation error codes

+ + + + + + + + + + + + +
CodeMeaning
7Duplicate name within same family in the Excel file
9Record already exists in DB (inception / addition)
10Record not found (used on deletion flows)
14Self row missing in family
26Duplicate employee code in file or DB
+ +

Step 3: employeesOnboardPreprocess

+ +

Calculates premium and writes members to the database.

+ +
    +
  1. Reload Excel; group by emp_id.
  2. +
  3. For each family: calculate_premium_new() using policy terms + slab rates + rack config.
  4. +
  5. employeesOnboardProcess() — insert/update employees, employee_polices, create policy_transaction (inception).
  6. +
  7. If at least one family inserted → files.status = success; else failed with rack-rate message.
  8. +
+ +

+ Optional second path: client_policy_id without file_id — converts enrolled DB members + to inception (enrollment → inception), not from Excel. +

+ +

+ TPA batch: When batch_file_id is present on the job payload, success also queues + updateEmployeeDataFromTpa, reconTpaApiDataWithEmployeepolicies, and + initializeDeletionProcessForTpaApiData (multi-file TPA reconcile flow). +

+ +

Inception Excel columns

+ +

+ Defined in EmployeeServiceController::$inception_excel_columns. + Header row must match exactly — 19 columns (A–S), row 1 only. + Dates use format d-M-Y (e.g. 4-Apr-1990). + One Self row per EMP ID; other rows are dependents. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ColHeaderRequired (inception)Notes
AS.NoYes
BEMP IDYesFamily key
CNAME OF EMP/DEPYes
DDOBYesd-M-Y; age vs relationship checked
EGenderYesM / F (several casings allowed)
FRELATIONSHIPYesSelf, Spouse, Son, Daughter, …
GBASIC COVER SIConditionalValidated against slab when applicable
HDate of CoverageNoMandatory for addition / DA only
IDOJNo
JBasic PayNoUsed when policy terms need it
KBand/GradeNo
LDesignationNo
MPhoneNoMobile format; duplicate check
NEmailNoDuplicate check in file
OPRE EXISTING AILMENTSYes0 or 1
PChange eventNoNot used for pure inception
QDate of exitNoDeletion only
RReason for exitNoDeletion only
SUnitNoMust match branch units when filled
+ +

Sample file: use the download link on the employee upload screen (environment-specific).

+ +

Family row layout example

+ +

+ All rows with the same EMP ID (column B) are treated as one family. + Step 2 requires exactly one Self row in that group; dependents share the same EMP ID. + Step 3 runs premium and DB insert once per family. +

+ +

Example: one employee (EMP001) with spouse and son — three data rows plus header.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RowS.NoEMP IDNAMEDOBGenderRELATIONSHIPBASIC COVER SIPED
1Header row (all 19 columns A–S required in file)
21EMP001Raj Kumar15-Jan-1985MSelf5000000
32EMP001Priya Kumar20-Mar-1988FSpouse5000000
43EMP001Arjun Kumar10-Jun-2015MSon5000000
+ +

Rules illustrated:

+ + +

+ A second employee in the same file uses a different EMP ID (e.g. EMP002) with its own Self row — each EMP ID is a separate family loop in preprocess. +

+ +

Developer steps

+ +
    +
  1. Confirm policy has policy_terms JSON and slab/rack rates for the client policy.
  2. +
  3. Upload via /employee/upload with action inception; note file_id in response or files table.
  4. +
  5. If status = failed, call GET /employee/excel_error/{file_id} or read files.reason.
  6. +
  7. Map error_data row keys back to Excel (row 1 = header).
  8. +
  9. If format passes but preprocess fails with rack message, debug calculate_premium_new (see rack-rate doc) and SI/slab config.
  10. +
  11. For stuck inprogress, check job queue / JobWorker logs for the three job names.
  12. +
+ +

Common pitfalls

+ + diff --git a/app/Views/docs/non-eb-claims.php b/app/Views/docs/non-eb-claims.php new file mode 100644 index 00000000..71aec5c5 --- /dev/null +++ b/app/Views/docs/non-eb-claims.php @@ -0,0 +1,488 @@ + + +

+ This page documents the Non-EB Claims web workflow: list and filter claims, + create and edit tickets, status-driven form sections, document uploads, manual email reply, + mail template CRUD, auto-mail on create/status change, and claim reports. +

+ +

+ Scope: authenticated MVC routes under /non-eb-claim/* only. + Mobile/REST endpoints in Api\NonEbClaimApiController are not covered here. +

+ +

+ Policy types are limited to policy_type.allocg IN ('Non-EB', 'Marine'). + Claim records live in non_eb_ticket_master; claim files use ticket_type = 2 + in claim_files. +

+ +

End-to-end flow

+ +
+
+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"] +
+
+ +

Typical user path:

+
    +
  1. Open claim list (default: open claims excluding settled/closed/rejected/withdrawn).
  2. +
  3. Add claim → form opens with canonical status policy type 50 — see why 50 is hardcoded.
  4. +
  5. On save, optional auto-mail fires if a matching template has is_auto_mail = 1.
  6. +
  7. Open claim from list → edit view with history, messages, files, notes.
  8. +
  9. Change status → allowed next statuses from ticket_claim_status.allowed_status; section visibility updates.
  10. +
  11. Configure templates at /non-eb-claim/mail_template (direct URL; not in main Claims sidebar today).
  12. +
+ +

Why policy type 50 is hardcoded (read this before changing Non-EB Claims)

+ +
+

+ Non-EB has many real policy products (Fire, Marine, Liability, etc. — each row in + policy_type with allocg Non-EB or Marine). Claim status workflows are + not stored separately per product today. The team configured one canonical policy type, + id 50, in the database as the single source of status definitions. The application + assumes every Non-EB/Marine claim uses that same status set unless you deliberately change + backend and frontend. +

+
+ +

Design assumption

+ +

+ Claim statuses live in ticket_claim_status, keyed by ticket_type (which equals + policy_type.id). Mail templates in ticket_mail_template also key off + ticket_type + trigger_type from that status row. +

+ +

Instead of maintaining duplicate status trees for every Non-EB product, developers assumed:

+ +
+

+ All Non-EB and Marine policy types share one identical claim status lifecycle. + That lifecycle is configured only under policy type 50 in + ticket_claim_status (and matching templates under ticket_type = 50). +

+
+ +

+ New claims opened via the list Add button or New Non EB Claim menu therefore + pass 50 into the form URL. Status dropdowns, section visibility, and initial status resolution in + getClaimStatusForPolicyType($policy_type_id) all use that id on create — not the id of the policy + the user later picks from client_policy. +

+ +

Policy type 50 vs policy selected on the form

+ +

On create (non_eb_claim_form.php):

+ + +

+ On edit (non_eb_claim_edit.php), policy_type_id comes from + non_eb_ticket_master as saved. Status changes and templates use that stored id. +

+ +

Where 50 appears in code (change all if this design changes)

+ +
+ + + + + + + + + + + + +
LocationUsage
app/Config/Routes.phpGET non-eb-claim/newclaimForm/50
app/Views/non_eb_claim_list.phpDataTable Add button → /non-eb-claim/new/50
app/Views/non_eb_claim_form.phpHidden policy_type_id from route; status/section AJAX uses this value
ticket_claim_status (DB)Master status rows maintained with ticket_type = 50
ticket_mail_template (DB)Non-EB auto-mail templates should use ticket_type = 50 if they follow the shared workflow
Api\NonEbClaimApiController::listClaimStatuses()API hardcodes where('ticket_type', 50) (out of scope for this page but same assumption)
+
+ +

When a Non-EB product needs a different status set

+ +

+ If a new (or existing) policy type must have its own statuses, triggers, or allowed transitions + (not the shared tree under 50), you cannot only change the product row in + policy_type. You must update both backend and frontend: +

+ +
    +
  1. Database — Add full ticket_claim_status rows with + ticket_type = <that policy_type.id> (claim_status, display_name, trigger_type, + allowed_status JSON). Add matching ticket_mail_template rows for that + ticket_type if auto-mail applies.
  2. +
  3. Routes / entry URL — Stop routing every new claim through 50: e.g. change + claimForm/50, restore policy-type picker modal (commented in + non_eb_claim_search.php), or pass the correct policy_type_id per product.
  4. +
  5. List Add button — Replace hardcoded new/50 in + non_eb_claim_list.php with the correct id or dynamic selection.
  6. +
  7. Create form — On policy selection, set hidden #policy_type_id to the real + policy_type_id from getBranchAndPolicy (field p.policy_type_id is + already returned) so create/update and getVisibleSections use the right status tree.
  8. +
  9. Controller logic — Ensure getClaimStatusForPolicyType, + getTemplateDataByTicketID, and filters that assume a single Non-EB status catalog are tested for + the new ticket_type.
  10. +
  11. API — Replace hardcoded 50 in listClaimStatuses() if mobile + clients need per-product statuses.
  12. +
+ +

+ Until those steps are done, pointing Add at another id without cloning the full status + template set under + that id will produce empty status lists, wrong section visibility, or + missing auto-mail. +

+ +

Key files and routes

+ + + + + + + + + + + + + + + + + +
AreaFile / route
Controllerapp/Controllers/NonEbClaimController.php
Modelapp/Models/NonEbTicketMasterModel.php
List + filtersapp/Views/non_eb_claim_search.php, non_eb_claim_list.php
New claimapp/Views/non_eb_claim_form.php
View / editapp/Views/non_eb_claim_edit.php
Mail templatesapp/Views/non_eb_claim_mail_template.php
Reportsapp/Views/non_eb_claim_reports.php
Routesapp/Config/Routes.php — group /non-eb-claim, filter authMVC
ACLapp/Config/Acl.php#^/non-eb-claim# (Claims team roles)
App menuapp/Views/layout/header.php — New / List only (EB mail template link is separate)
+ +

MVC route map

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodRouteControllerPurpose
GET/non-eb-claim/listclaimListSearch page + default open claims
POST/non-eb-claim/listclaimListFilter → HTML partial for DataTable
GET/non-eb-claim/newclaimForm/50New claim (default policy type 50)
GET/non-eb-claim/new/{policy_type_id}claimFormNew claim for selected product
POST/non-eb-claim/createcreateClaimCreate ticket + assets + history + auto-mail
GET/non-eb-claim/view/{id}view_claimEdit layout (non_eb_claim_edit)
POST/non-eb-claim/updateupdateClaimUpdate; auto-mail on status change
GET/non-eb-claim/remove?ticket_id=removeClaimSoft delete (is_active = 0)
GET/non-eb-claim/mail_templatemailTemplateTemplate list + modal CRUD UI
POST/non-eb-claim/crud_mail_template/1crudTemplateSave template
POST/non-eb-claim/crud_mail_template/2crudTemplateFetch one template (edit)
POST/non-eb-claim/crud_mail_template/3crudTemplateSoft delete template
POST/non-eb-claim/note/1crudNoteGet note
POST/non-eb-claim/note/2crudNoteSave note
POST/non-eb-claim/replysaveReplyManual outbound mail + message row
GET/non-eb-claim/reportsclaimReportsReports UI shell
POST/non-eb-claim/reportsclaimReportsNot implemented in controller — see Reports
POST/non-eb-claim/getBranchAndPolicygetBranchAndPolicyByClientIDBranches, policies, contacts for client
POST/non-eb-claim/getVisibleSectionsgetVisibleSectionsAjaxSection keys for status
POST/non-eb-claim/getMoreInfogetMoreInfoTicket row JSON
POST/non-eb-claim/uploadFileuploadFileClaim docs (file or Drive URL)
POST/non-eb-claim/getClaimFilesgetClaimFilesList files for ticket
GET/non-eb-claim/removeFile?id=removeFileSoft delete file
POST/non-eb-claim/saveIRDocssaveIRDocsPersist required-docs JSON on ticket
GET/non-eb-claim/testAutoMail/{id}testAutoMailTriggerDev/test auto-mail (optional)
+
+ +

Access control

+ +

+ All routes in the /non-eb-claim group use the authMVC filter. + Acl.php allows roles HEAD, ADMIN, MANAGER, + ACCOUNT_MANAGER on the Claims team. +

+ +

+ List row actions (view / delete) render only for roles 1, 2, 5 in + non_eb_claim_list.php. +

+ +

List and filter

+ +

GET /non-eb-claim/list loads non_eb_claim_search.php, which includes the table partial. Initial data comes from claimSearch(1): active tickets where status display name is not in Claim Settled, Claim Closed, Claim Rejected, Claim Withdrawn.

+ +

Filter sidebar posts the same URL with criteria (at least one required):

+ + +

+ Response is JSON { status: true, html: "…" }; JS replaces #claim_list_div + and re-initializes the DataTable. Row click navigates to /non-eb-claim/view/{id}. +

+ +

+ Add button: window.location.href = '/non-eb-claim/new/50'. + Header menu uses /non-eb-claim/new (routes to claimForm/50). + See Policy type 50 for why this id is fixed and what to change if a product needs its own statuses. +

+ +

Create and edit claim

+ +

Create

+ +
    +
  1. GET /non-eb-claim/new/{policy_type_id}getFormData(): ACMs (role 3), clients, insurers, initial claim status for product, visible sections.
  2. +
  3. User selects client → POST getBranchAndPolicy fills branch, policy (Non-EB/Marine only), branch contact.
  4. +
  5. Status change → POST getVisibleSections toggles accordion sections client-side.
  6. +
  7. POST /non-eb-claim/create — validation via getValidationRules(), sanitizeInputArrayAdvanced, date normalization, optional asset file upload.
  8. +
  9. Duplicate guard: same client_id + loss_date + policy_no (if policy set) → HTTP 409-style JSON.
  10. +
  11. On success: insert non_eb_ticket_master, saveAssets(), history row, sendAutoMailTrigger(), redirect to list.
  12. +
+ +

View / edit

+ +

+ view_claim($id) loads ticket via NonEbTicketMasterModel::getTicketDataByTicketID(), + merges mail template preview (getTemplateDataByTicketID + placeholder replace), + messages, history, assets, and reuses the edit view non_eb_claim_edit.php. +

+ +

+ POST /non-eb-claim/update mirrors create validation. If claim_status_id changes, + auto-mail runs again. remark_mode=append appends to closure_remark with a separator. +

+ +

Status-driven sections

+ +

+ $statusSectionVisibility in the controller maps each ticket_claim_status.claim_status + label to section keys: policy_account, loss_incident, intimation, + insured_contact, asset, documents, surveyor, settlement. +

+ +

+ Allowed next statuses come from getClaimStatusForPolicyType(): current status plus IDs in + allowed_status JSON on the status row. Both create and edit forms call + getVisibleSectionsAjax when the user changes status. +

+ +

Auto-mail and placeholders

+ +

Template lookup (NonEbTicketMasterModel::getTemplateDataByTicketID):

+ + +

+ sendAutoMailTrigger($ticket_id) sends only when the matched template has + is_auto_mail = 1. Mail goes to insured_contact_email from + constructMailContent(); from address claims@nhanceindia.in via MailHelper::send_email(). + Successful sends insert a ticket_messages row via autoMessageInsertBasedOnMailResponse(). +

+ +

Placeholders (subject/body):

+ +
+ + + + + + + + + + + + + + +
TokenTicket field
((ACM))acm
((ACM_CONTACT))acm_mobile
((INSURED_NAME))insured_contact_name
((CORPORATE_NAME))client_name
((CLAIM_NO))claim_number
((POLICY_TYPE))policy_type_name
((NHANCE_REF_NO))nhance_claim_ref_no
((LOSS_DATE))loss_date
((LOSS_LOCATION))loss_location
((NATURE_OF_LOSS))nature_of_loss
+
+ +

+ Edit screen also supports manual reply: POST /non-eb-claim/reply validates To/Subject, + inserts ticket_messages, sends via sendReplyMessage() with placeholder replacement. +

+ +

Mail template CRUD

+ +

+ Page: GET /non-eb-claim/mail_template. DataTable lists rows from + ticket_mail_template (is_active = 1). Tooltip on each row shows the + matching ticket_claim_status.claim_status for that policy type + trigger type. +

+ +
+
+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 +
+
+ +

Template fields

+ + + +

+ Trigger ↔ status: Each ticket_claim_status row for a Non-EB/Marine + ticket_type has a trigger_type. The template’s trigger_type must match + that column for auto-mail and for the “Claim Status” readonly hint (tool_tip from server on fetch). +

+ +

UI actions

+ +
+ + + + + + + + + +
ActionEndpointBodyResult
Add / SavePOST …/crud_mail_template/1Form fields + mail_content + is_auto_mail{ status: bool } → reload
Edit loadPOST …/crud_mail_template/2id{ status, data } opens modal
DeletePOST …/crud_mail_template/3idSoft delete (is_active = 0)
+
+ +

+ Placeholder dropdown in the modal inserts tokens into subject (focused input) or Jodit body. + Validation errors return HTTP 400 with errors map (shown via toastr). +

+ +

Notes

+ +

On the edit screen:

+ + +

Documents and IR checklist

+ + + +

Reports

+ +

+ GET /non-eb-claim/reports renders filters: policy type, ACM name, date range (default last 60 days). + generateReport() in the view POSTs to the same URL and expects + { status: true, data: [ rows ] } for DataTable columns (status, policy type, claim/ref, client, insurer, loss fields, surveyor, settlement, ACM, created date). +

+ +

+ Gap: NonEbClaimController::claimReports() 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 ticket_reports or reuse claimSearch with report-specific selects). +

+ +

Data model (summary)

+ + + + + + + + + + + + + +
TableRole
non_eb_ticket_masterMain claim ticket
non_eb_claim_assetRepeating asset lines per ticket
ticket_claim_statusStatuses per ticket_type (policy type id); trigger_type, allowed_status
ticket_mail_templateTemplates; ticket_type = policy type id
ticket_historyField-level audit (status, ACM, priority, …)
ticket_messagesOutbound mail log
ticket_notesUser notes per ticket
claim_filesAttachments; ticket_type = 2 for Non-EB
+ +

Controller reference

+ +
+ + + + + + + + + + + + + + + + + + + +
MethodUsed for
claimList / claimSearchList UI and filtered HTML
claimForm / getFormDataNew claim form bootstrap
view_claimEdit view
createClaim / updateClaimPersist ticket
getClaimStatusForPolicyType / getVisibleSections*Status dropdown + sections
mailTemplate / crudTemplateTemplate admin
sendAutoMailTrigger / constructMailContentAutomated email
saveReply / getTicketMessageManual email thread
crudNoteNotes
uploadFile / getClaimFiles / removeFile / saveIRDocsDocuments
saveAssets / getAssetsAsset grid
claimHistory / putHistoryAfterInsertAudit trail
getBranchAndPolicyByClientIDClient cascade
claimReportsReports page (GET only today)
testAutoMailTriggerDev preview/send test
+
+ +

Developer checklist

+ +
    +
  1. Ensure ticket_claim_status rows exist per Non-EB/Marine policy_type.id with correct trigger_type and allowed_status.
  2. +
  3. Create mail templates at /non-eb-claim/mail_template with matching ticket_type + trigger_type; enable Auto Mail only where intended.
  4. +
  5. Verify insured email is present before relying on auto-mail.
  6. +
  7. Before changing Add/new URLs: read Policy type 50 — clone statuses + templates in DB and update every hardcoded 50 if a product needs its own workflow.
  8. +
  9. Implement POST branch in claimReports() if reports Generate must work.
  10. +
  11. Claim files: always set ticket_type = 2 in new file-related code paths.
  12. +
  13. ACL: extend #^/non-eb-claim# if new roles need access.
  14. +
+ +

Common pitfalls

+ + diff --git a/app/Views/docs/non-eb-opportunities.php b/app/Views/docs/non-eb-opportunities.php new file mode 100644 index 00000000..eb4e7576 --- /dev/null +++ b/app/Views/docs/non-eb-opportunities.php @@ -0,0 +1,638 @@ + + +

+ This page documents the Non-EB Opportunities workflow from the + Opportunities list (/leads/list). A user creates a Non-EB opportunity, + then progresses through RFQ → QCR → mail actions → placement — all from the list row action menu. +

+ +

+ Non-EB rows are identified by leads.lead_form_type = 2 (EB = 1). + RFQ and QCR are managed in Google Sheets; sheet IDs are stored in + leads.misc JSON. +

+ +

End-to-end flow

+ +
+
+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"] +
+
+ +

Typical sequence from the list:

+
    +
  1. Create opportunity (form submit) → queued
  2. +
  3. RFQ — create/open Google Sheet → rfq_created
  4. +
  5. QCR — copy RFQ sheet → qcr_created
  6. +
  7. Send Internal Mail — team mail with RFQ or QCR attachment (by current status)
  8. +
  9. Send Insurer Mail — RFQ sheet attached → rfq_sent
  10. +
  11. Send Client Mail — QCR sheet attached → qcr_sent
  12. +
  13. Placement — placement sheet + policy/payment fields → won
  14. +
+ +

+ Edit is available at any stage from the same action menu and reuses the add form + with pre-filled data (getLeadNonEB + POST /leads/create with lead id). +

+ +

Key files

+ + + + + + + + + + + + +
AreaFile
List + action dropdownapp/Views/leads_list.php
Non-EB add/edit formapp/Views/leads_non_eb.php
EB vs Non-EB form includeapp/Views/leads_form_handler.php
All backend logicapp/Controllers/LeadsController.php
Google SheetsGoogleSheetLib, Config\RfqConfig
+ +

Google Sheet config

+ +

+ Non-EB RFQ, QCR, and Placement sheets are Google Drive files created at runtime by + GoogleSheetLib using a service account. Only RFQ needs a pre-configured + template per product; QCR and Placement are copies of the lead’s RFQ/QCR sheets. +

+ +

App ↔ Google Drive flow

+ +
+
+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 +
+
+ +

Library: app/Libraries/GoogleSheetLib.php — auth via service account JSON at +{project-root}/nhance-ee8d1-e3c5269b1ec7.json, scopes DRIVE + SPREADSHEETS.

+ +

What must be done before RFQ / QCR

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepBeforeWhy
1Google service account JSON present; account email shared on template folder + each template sheetGoogleSheetLib cannot copy or edit without Drive access
2RFQ_PARENT_FOLDER_ID set in .envTarget folder for every copied RFQ/QCR/Placement file
3Master RFQ Google Sheet template created per product (Fire, Marine, GPA, etc.) with placeholder tokensRFQ copy source — one template per policy_type row
4policy_type.misc JSON updated with rfq_template_sheet_id for that productcreateRfqSheet() reads this; fails with “RFQ template not found” if missing
5Non-EB opportunity created with correct policy_type_id and rfq_qcr_viewers emailsLead must exist; viewers become sheet editors after copy
6Before QCR: RFQ action completed (leads.misc.rfq_sheet_id set)QCR copies the lead’s RFQ sheet, not the policy template
7Before Placement mail: QCR action completed (leads.misc.qcr_sheet_id set)Placement copies the QCR sheet
+ +

Where to configure sheet ID per product

+ +

+ Template sheet IDs are stored per product on the policy_type table — + one row per product (Fire, Marine, Burglary, etc.). The lead’s selected + policy_type_id determines which template is copied when RFQ is clicked. +

+ +

Database column: policy_type.misc (JSON text)

+ +
{
+  "rfq_template_sheet_id": "1GXNDNXoWriClb5HCqPYd2GAY1T8aie_Hos0yAOoTaC0"
+}
+ +

Example — set or update for policy type id 12:

+ +
UPDATE policy_type
+SET misc = JSON_SET(COALESCE(misc, '{}'), '$.rfq_template_sheet_id', 'YOUR_GOOGLE_DRIVE_FILE_ID')
+WHERE id = 12;
+ +
+ i +
+ There is no admin UI field for rfq_template_sheet_id today — configure via DB (or extend + MasterController::editPolicyType / policy_type_onboarding if you add a form field). + Model allow-list: app/Models/PolicyTypeModel.php includes misc. +
+
+ +

How to find a template file ID:

+ + +

QCR and Placement: no separate template ID per product. They always copy from the lead’s existing sheets:

+ + + + + + + + + + +
StageCopy sourceStored on lead
RFQpolicy_type.misc.rfq_template_sheet_idleads.misc.rfq_sheet_id
QCRleads.misc.rfq_sheet_idleads.misc.qcr_sheet_id
Placementleads.misc.qcr_sheet_id (filename: QCR → Placement)leads.misc.placement_sheet_id
+ +

App-level config files

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingLocationPurpose
RFQ_PARENT_FOLDER_ID.envConfig\RfqConfig::$rfqParentFolderIdGoogle Drive folder where copied RFQ/QCR/Placement files are created
rfqPlaceholdersapp/Config/RfqConfig.phpMaps template tokens like {{INSURED_NAME}} to lead field keys filled on RFQ create
rfqClaimsPlaceholderapp/Config/RfqConfig.phpDefault {{CLAIMS_DETAILS}} — multi-row claims table from fin_years_claims
protectionsapp/Config/RfqConfig.phpLocked cell ranges on new RFQ sheets only (e.g. RFQ Page!B12:C12)
Service account keynhance-ee8d1-e3c5269b1ec7.json (project root)Google API authentication for all sheet operations
+ +

Placeholder tokens to embed in each product’s RFQ master template:

+ + + + + + + + + + + + + + +
Token in templateFilled from
{{INSURED_NAME}}Client name / short name
{{COMMUNICATION_ADDRESS}}Client or custom field address
{{GST}} / {{PAN}}Lead GST / PAN
{{POLICY_PERIOD}}Policy start – end dates
{{OPPORTUNITY_TYPE}}Fresh / Renewal label
{{RISK_LOCATION}} / {{OCCUPANCY}}Custom policy-type fields
{{CLAIMS_DETAILS}}Claim history table (renewal leads)
+ +

+ After RFQ copy, editors are granted from the lead’s rfq_qcr_viewers JSON email list (selected on the create/edit form). + The same list is applied to QCR and Placement copies. +

+ +

Step 1 — Create opportunity

+ +

From the list

+
    +
  1. Open /leads/list → click Add.
  2. +
  3. Modal Select Opportunity Type → choose Non-EB (lead_form_type=2).
  4. +
  5. Redirect: GET /util/getLeadNonEB/2/0.
  6. +
+ +

Form (leads_non_eb.php)

+ + +

Controller: createLead()

+ + +

Edit flow

+ +

From the list action menu → Edit (available for both EB and Non-EB):

+ +
    +
  1. JS: getLeadsDataForEdit(lead_id, lead_form_type, actual_lead_id)
  2. +
  3. Redirect: GET /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}
  4. +
  5. Controller: getLeadNonEB($type, $actual_lead_id, $id) (~line 4818) +
      +
    • Loads master data (issuer, policy types, sales team, etc.)
    • +
    • When $id present: fetches lead_edit_data, files, custom fields, date formatting
    • +
    • Builds dynamic policy HTML via generateViewPageHtml()
    • +
    • Renewal Non-EB: renders rfq/claims_details_non_eb into claims_details_html
    • +
    • Returns layout with leads_form_handler → includes leads_non_eb.php when selected_lead_type != 1
    • +
    +
  6. +
  7. Form pre-fills client, branch, policy, files, viewers, status, lost reason, etc.
  8. +
  9. Submit same endpoint: POST /leads/create with hidden idupdateOldLead()
  10. +
+ +

Steps 2–7 — List table actions (Non-EB)

+ +

+ When lead_form_type === 2, the row action menu in + app/Views/leads_list.php (lines ~297–343) uses modal/AJAX flows instead of + navigating to /rfq/list/{id}/1|2 (EB behaviour). +

+ +
+ i +
+ Menu order on screen (after opportunity is created): Edit → RFQ → QCR → + Send Internal Mail → Send Insurer Mail → Send Client Mail → Placement → Email History. +
+
+ +

Action → view handler → controller endpoint

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#ActionVisible whenView (JS) / handler classController endpoint(s)Status after
EditAlways (EB + Non-EB)getLeadsDataForEdit() + GET + /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}
+ POST + /leads/create (update when id posted) +
User-selected on form
1RFQlead_form_type === 2 only.btnRfqSheetListcreateRfqSheetFromList() + GET + /leads/createRfqSheet?lead_id={id}
+ LeadsController::createRfqSheet() — opens Google Sheet URL in new tab +
rfq_created
2QCR + Non-EB; status not queued or rfq_created; + role 1, 5, 2, 3 or Business Support team + .btnQcrSheetListcreateQcrSheetFromList() + GET + /leads/createQcrSheet?lead_id={id}
+ LeadsController::createQcrSheet() +
qcr_created
3Send Internal MailNon-EB; status != queued.btnInternalMailListopenInternalMailFromList() + GET + /leads/mailTemplate?lead_id={id}&template_type=rfq
+ POST + /leads/sendMailrecipient_type=internal +
4Send Insurer MailSame as internal mail.btnInsurerMailListopenInsurerMailFromList() + GET + /leads/mailTemplate?lead_id={id}&template_type=rfq
+ POST + /leads/sendMailrecipient_type=insurer (RFQ sheet attach) +
rfq_sent
5Send Client MailSame as internal mail.btnClientMailListopenClientMailFromList() + GET + /leads/mailTemplate?lead_id={id}&template_type=qcr
+ POST + /leads/sendMailrecipient_type=client (QCR sheet attach) +
qcr_sent
6PlacementSame as internal mail.btnPlacementListopenPlacementFromList() + GET + /rfq/placementData/{id}
+ GET + /leads/mailTemplate?lead_id={id}&template_type=placement
+ POST + /leads/sendMailrecipient_type=placement +
won
Email HistoryAlways (EB + Non-EB).btnHistorygetLeadsDataForMailHistory() + GET + /util/getLeadEmailHistory/{id}getLeadEmailHistory() +
+
+ +

EB contrast (same menu, lead_form_type === 1): RFQ/QCR are links to +/rfq/list/{id}/1 and /rfq/list/{id}/2; internal/insurer/client/placement mail items are not shown.

+ +

Shared mail modal helpers (all in leads_list.php):

+ + +

Step 2 — RFQ (createRfqSheet())

+ +
    +
  1. Load lead + policy_type.misc.rfq_template_sheet_id.
  2. +
  3. If misc.rfq_sheet_id exists → return existing Google Sheet URL.
  4. +
  5. Copy template via GoogleSheetLib::copyTemplate() into RfqConfig::rfqParentFolderId.
  6. +
  7. Fill placeholders (buildRfqPlaceholderData()): client, GST, PAN, policy period, claims table.
  8. +
  9. Grant editors from leads.rfq_qcr_viewers email JSON.
  10. +
  11. Save misc.rfq_sheet_id; set status = rfq_created.
  12. +
  13. UI opens sheet in a new browser tab.
  14. +
+ +

Step 3 — QCR (createQcrSheet())

+ +
    +
  1. Requires misc.rfq_sheet_id — returns 400 if RFQ not created yet.
  2. +
  3. If misc.qcr_sheet_id exists → return existing URL.
  4. +
  5. Copy the RFQ sheet (not the policy template) with a QCR filename.
  6. +
  7. Same editor permissions from rfq_qcr_viewers.
  8. +
  9. Save misc.qcr_sheet_id; set status = qcr_created.
  10. +
+ +

Steps 4–6 — Mail actions

+ +

Load template — getLeadMailTemplate()

+

+ Called before each mail modal opens. Query params: + lead_id + template_type (rfq | qcr | placement). + Returns subject, HTML body, and attachment checkbox HTML from lead_files. +

+ +

Send — sendMailWithAttachement()

+ +

For Non-EB (lead_form_type == 2), the Excel attachment comes from Google Sheets:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
recipient_typeSheet usedStatus after send
internalQCR if status is qcr_created/qcr_sent, else RFQUnchanged
insurermisc.rfq_sheet_idrfq_sent (unless already past that stage)
clientmisc.qcr_sheet_idqcr_sent
placementcreateAndDownloadPlacementSheet() — copies QCR sheetwon + saves placement/payment/installment fields
+ +

Implementation detail: downloadFileFromGoogleSheet() exports the chosen sheet to a temp +.xlsx under writable/tmp/ before MailHelper::send_email().

+ +

Step 7 — Placement

+ +

Opened from list action → openPlacementFromList(leadId):

+
    +
  1. GET /rfq/placementData/{id} — pre-fills policy dates, premium, CD, installments, contacts.
  2. +
  3. GET /leads/mailTemplate?template_type=placement — subject/body/attachments.
  4. +
  5. User fills placement modal: dates, premium/CD/total, installment rows, To/CC, optional external CC.
  6. +
  7. POST /leads/sendMail with recipient_type=placement.
  8. +
  9. Backend copies QCR → placement Google Sheet, attaches Excel, updates lead to won, + persists placement_date, premium_amount, cd_amount, installments, etc.
  10. +
+ +

Status lifecycle

+ + + + + + + + + + + + + + +
StatusLabelSet by
queuedIn-QueuedCreate form (default)
rfq_createdRFQ CreatedcreateRfqSheet()
rfq_sentRFQ SentInsurer mail
qcr_createdQCR CreatedcreateQcrSheet()
qcr_sentQCR SentClient mail
wonWonPlacement mail
lostLostUser sets on edit form + lost reason
+ +

leads.misc JSON

+ +
{
+  "rfq_sheet_id": "…",
+  "qcr_sheet_id": "…",
+  "placement_sheet_id": "…"
+}
+ +

Controller reference (list flow)

+ +

Methods in LeadsController.php used by the list Non-EB flow:

+ + + + + + + + + + + + + + + + + + + + +
MethodTriggered from
createLead()Form submit (create + edit)
getLeadNonEB()Add / Edit navigation
getPolicyTypeFields()Policy type change on form
createRfqSheet()RFQ action
createQcrSheet()QCR action
getLeadMailTemplate()All mail modals
getPlacementData()Placement modal pre-fill
uploadLeadAttachment()Attachment upload in mail modals
sendMailWithAttachement()All mail sends + placement
downloadFileFromGoogleSheet()Mail attachment (internal/insurer/client)
createAndDownloadPlacementSheet()Placement mail attachment
getLeadEmailHistory()Email History modal
buildRfqPlaceholderData()RFQ sheet placeholder fill (helper)
+ +

Developer checklist

+ +
    +
  1. Complete Google Sheet config for each Non-EB/Marine policy_type before first RFQ.
  2. +
  3. Set policy_type.misc.rfq_template_sheet_id per product (see sheet ID per product).
  4. +
  5. Configure RFQ_PARENT_FOLDER_ID in .env and share folder/templates with the service account.
  6. +
  7. Ensure placeholders in master templates match Config\RfqConfig::$rfqPlaceholders.
  8. +
  9. Gate new list actions with $isNonEb in leads_list.php.
  10. +
  11. Respect sheet order: RFQ → QCR → mails → placement.
  12. +
  13. QCR button: roles [1, 5, 2, 3] or BUSINESS_SUPPORT_TEAM_ID in user team.
  14. +
+ +

Common pitfalls

+ + diff --git a/app/Views/docs/partials/docs_header.php b/app/Views/docs/partials/docs_header.php index 2b8a8e6e..949ee16e 100644 --- a/app/Views/docs/partials/docs_header.php +++ b/app/Views/docs/partials/docs_header.php @@ -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); diff --git a/app/Views/docs/partials/docs_main_open.php b/app/Views/docs/partials/docs_main_open.php index 8e53fffe..b81630d2 100644 --- a/app/Views/docs/partials/docs_main_open.php +++ b/app/Views/docs/partials/docs_main_open.php @@ -27,10 +27,11 @@ $toc = $toc ?? []; 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"] + + + +

In short:

+ + +

Key files and routes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaLocation
UIapp/Views/employee_upload.php — action SI Enhancement
UploadEmployeeController::employeesUplodWithEvents
ValidationexcelFileFormatValidation, excelFileDataValidation
SI processemployeesSIEnhanceProcess
Onboard SI pathemployeesSIEnhanceProcessWhileOnbboard — SI during dependent addition onboard (not Excel)
JobemployeesSIEnhanceProcess in JobWorker.php
Helpersexcel_util_helper.phptransform_si_excel_row_to_calculatable_format, premium_calculation_manager
+ +

Routes (group /employee, authMVC):

+ + +
+ i +
+ files.policy_id is the client policy id. Slab rates load via + getPolicySlabRatesForEmpOnboard(policy_id, client_id). +
+
+ +

Sync vs background jobs

+ + + + + + + + + + +
Step< 1 MB≥ 1 MB
Format validationInline on uploadJob excelFileFormatValidation
Data validationJob excelFileDataValidationSame
SI enhanceJob employeesSIEnhanceProcessSame
+ +

Step 1: excelFileFormatValidation

+ + + +

Format error codes

+ + + + + + + + + + + + + +
CodeMeaning
1Mandatory missing
2Wrong format
3Not in allowed list
4Custom validation failed (e.g. invalid Augmented SI for slab)
5File / policy / slab problem
6Column headers wrong
+ +

Step 2: excelFileDataValidation

+ +

name_and_empid_check_in_db for si_enhancement:

+ + +

On success → queues employeesSIEnhanceProcess.

+ +

Step 3: employeesSIEnhanceProcess

+ +
+
+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"] +
+
+ +

Per row:

+
    +
  1. Strip number formatting from Augmented SI (removeNumberFormatting).
  2. +
  3. Load employee (emp code + name + branch) and active employee_polices row.
  4. +
  5. Find applicable slab/rack rate from member relationship (Son/Daughter → childrens, parents, etc.).
  6. +
  7. Build calculatable member payload via transform_si_excel_row_to_calculatable_format (uses family max age/count).
  8. +
  9. premium_calculation_manager → new premium for the augmented SI.
  10. +
  11. Insert pending endorsements on employee_polices (actions = si):
  12. +
+ + + + + + + + + + +
field_namenew_value source
basic_cover_siExcel Augmented SI (column D)
premiumRecalculated premium from rack logic
si_enhancement_dateExcel Date of SI Enhancement (column E)
+ +

+ Sets files.status = success when the loop completes. Skipped rows (duplicate pending SI, missing member) + are logged only — the file still succeeds. +

+ +

SI Enhancement Excel columns

+ + + + + + + + + + + + +
ColHeaderRequiredNotes
AS.NoYes
BEMP IDYes
CNAME OF EMP/DEPYesMust match DB
DAugmented SIYesNew SI; validated via check_si
EDate of SI EnhancementYesd-M-Y
+ +

Row layout examples

+ +

Enhance Self SI — one row:

+ + + + + + + + +
EMP IDNAMEAugmented SIDate of SI Enhancement
EMP001Raj Kumar100000019-May-2026
+ +

Enhance spouse only — separate row (premium uses that member’s relationship for rack selection):

+ + + + + + + + +
EMP IDNAMEAugmented SIDate of SI Enhancement
EMP001Priya Kumar50000019-May-2026
+ +

Developer steps

+ +
    +
  1. Confirm slab/rack rates exist for the client policy (same as inception).
  2. +
  3. Upload with upload-action-type=si_enhancement; note file_id.
  4. +
  5. On validation failure, check /employee/excel_error/{file_id} — codes 4 (SI/slab) or 10 (member missing).
  6. +
  7. After success, query emp_endorsement where file_id = upload id, actions = 'si', status = 'pending' — expect up to 3 rows per member (same group_key).
  8. +
  9. Compare new_value on basic_cover_si and premium to Excel and rack expectations.
  10. +
+ +

Common pitfalls

+ + + +

+ Related: + Inception, + Correction, + EB rack rate calculation. +