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. +
+ +In short:
+RuleImportController + commission_file_upload.php — import and manage rules.POST getCommission → InsuranceCommissionController::initiateCommissionCalc — read JSON, pick first matching rule, return payout.writable/uploads/commission/rules/{MONYYYY}/{insurer_id}_{department}.json (e.g. SEP2025/5_motor.json).| Part | File |
|---|---|
| Upload list UI | app/Views/commission_file_upload.php |
| Rules editor UI | app/Views/commission_rules_list.php |
| Upload and editor API | app/Controllers/RuleImportController.php |
| Excel parsing | ruleImportService (via Config\Services::ruleImportService()) |
| Payout calculation API | app/Controllers/InsuranceCommissionController.php |
| Upload metadata DB | commission_files (CommissionFilesModel) |
Admin (commission group):
+| Route | Handler |
|---|---|
GET/POST commission/list | commissionFileUploadList |
POST commission/upload | upload |
GET commission/sample_file | Sample CSV download |
GET commission/downloadErrorFile | Annotated error Excel |
GET commission/checkSameEntry | Duplicate 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/checkRuleUsage | Whether rule is used on partner policies |
GET commission/deleteCommissionData/(:id) | Soft-delete file + mark rules deleted |
Runtime API:
+POST getCommission
+ → InsuranceCommissionController::initiateCommissionCalc
+ → filter: CommissionApiFilter
+
+
+ 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).
+
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”.
+ Header text must match exactly (one row per rule, starting row 2). Empty cells are allowed and simply skip that condition.
+ +| Column | Purpose | Example |
|---|---|---|
| S.No | Serial (not used in logic) | 1 |
| Premium Type | Maps to policy_type | OD, TP, COM |
| Vehicle Type | vehicle_type; comma = multiple (IN) | Two Wheeler, Four Wheeler |
| Vehicle Sub Type | vehicle_sub_type | Car, PCV |
| Make / Model | Vehicle make and model | Honda, i10 |
| CC Min / CC Max | Cubic capacity range | 1000, 3000 |
| Fuel Type | Comma-separated fuels | Petrol, Diesel |
| Vehicle Age Min / Max | Vehicle age range (years) | 1, 5 |
| Vehicle Weight Min / Max | Weight range (kg) | 2000, 3000 |
| RTO State / RTO Code | geo_rto_state, geo_rto_city | RJ, 41 |
| Renewal Type | renewal_type | Online, Cash, Card |
| Commission Type | percentage, composite, flat, tiered | percentage |
| Commission Value | % or flat amount (required for percentage / flat) | 15 or 500 |
| Commission Params (TP) | Composite: % on TP premium | 18 |
| Commission Params (OD) | Composite: % on OD premium | 10 |
| Commission Params (PA) | Composite: % on PA premium | 0 or empty |
| Type | Fill in sheet | Becomes in JSON |
|---|---|---|
percentage |
+ Commission Value = e.g. 10 |
+ 10% of premium |
+
composite |
+ Leave Value empty; set TP / OD / PA param columns (percent each) | +Split % on tp_premium, od_premium, pa_premium |
+
flat |
+ Commission Value = fixed rupee amount e.g. 500 |
+ Fixed payout on premium |
+
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):
Two Wheeler, CC Min/Max = 100composite, TP = 10, OD = 25Example B — percentage four-wheeler:
+Four Wheeler, CC = 1000, Vehicle Age Min/Max = 5percentage, Commission Value = 10Example C — composite TP-only:
+TP, Vehicle Type = Four Wheelercomposite, Commission Params (TP) = 10Example D — flat amount:
+Two Wheeler,Four Wheeler (comma → matches either)flat, Commission Value = 500Tips:
+.csv, .xlsx, .xls, .ods.motor, health; import logic is built for motor columns today.From commission_file_upload.php the user picks insurer, commission month, department (motor / health), and an Excel/CSV file.
checkSameEntry — if a successful upload already exists for the same trio, SweetAlert offers Overwrite or Append (overwrite=1 or 0 on POST).upload — stores file under writable/uploads/commission/files/, inserts commission_files row (pending).ruleImportService->processUpload() — validates Excel rows; on success returns rules array; on failure returns annotated_file for download.rules/{MONYYYY}/{insurer_id}_{department}.json; sets file_status=success and rules_count.file_status=failed; user downloads annotated_{filename} via downloadErrorFile.
+ For successful uploads, action View Rules opens
+ commission/rules/list/{file_id} (commission_rules_list.php).
+
saveRule — create or update a rule (conditions + calculation + name) in the JSON via updateCommissionRules().removeRule — soft-delete one rule (is_deleted=true).checkRuleUsage — warns if partner_policy.commission_applied_rule references the rule.file_id as deleted in JSON, then sets commission_files.is_active=0.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.
initiateCommissionCalc() expects POST/JSON including at least:
policy_issue_date — used to pick folder {MON}{YEAR} (e.g. SEP2025)insurer_iddepartment — motor, health, etc.premium, od_premium)Rules with is_deleted: false are loaded; the first matching rule wins (no priority field yet).
/commission/list (logged-in admin).POST getCommission with the same insurer, department, and a policy_issue_date in that commission month.policy_issue_date to resolve the same MONYYYY folder.status and code in the JSON body, not only HTTP status.
+ 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.
+
authMVC filter. Upload and list are browser AJAX/form calls from an authenticated session, not public API endpoints.
+
+ 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).
+
In short:
+failed and errors are shown per row in the UI.| Area | Location |
|---|---|
| UI | +app/Views/insurer_statement_list.php — DataTable list, upload modal, validation error modal, invoice modal |
+
| Controller | +app/Controllers/PolicyTransactionController.php |
+
| Statement header model | +app/Models/InsurerStatements.php → insurer_statements |
+
| Line-item model | +app/Models/COShareStmtDetailsModel.php → co_share_stmt_details |
+
| NHance source rows | +app/Models/PTCOShareDetailsModel.php → getNonReconcileredPolicyTransactionByPolicyAndEndorsement() |
+
| Sample Excel | +public/sample_excel/insurer_stament_sample.xlsx |
+
Routes (prefix policy_tranction/statement, filter authMVC):
| Method | Route | Handler |
|---|---|---|
| GET | list | statementList |
| POST | upload | uploadInsurerStatement |
| GET | downloadSampleInsurerStatement | Sample file download |
| GET | downloadInsurerStatement/(:num) | Uploaded file download |
| GET | getFileErr/(:any) | Validation failure JSON for modal |
| GET | getInsurerStatementMonth | Used to disable already-used statement numbers |
| GET | deleteStatement/(:any) | Soft-delete statement + related rows |
| GET | getPaymentDetails/(:any) | Invoice modal data |
| POST | saveInvoicePaymentDetails | Invoice / 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');
+ // ...
+ });
+});
+
+
+ statementList() loads insurers/branches via
+ insurerBranchModel::getInsurerBranchesWithInsurerNames(), invoice status labels, and
+ statements from the last 180 days (is_active = 1). The view shows:
+
file_status — success or failed (failed rows show an alert icon → error modal)invoice_status — pending / generated / sent / payment received
+ 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.
+
max_size[statement,16384] KB).WRITEPATH . 'uploads/statements/' (see createStatementFolder() for folder creation).insurer as {insurer_id}-{branch_id}, statement_month (converted to first-of-month Y-m-d), statement_no → stmt_sno.insurer_statements row via InsurerStatements model.validateInsurerStatement(['file_id' => $file_id]).updateInsurerStatement(['file_id' => $file_id]).dataStatus: false, error_data, error_code (HTTP 200).invoice_status = 'pending' and returns dataStatus: true.Runs immediately after upload (and can be re-run manually in dev with a hard-coded file_id in statementList() comment).
insurer_statements by file_id; fail if missing or physical file absent under writable/uploads/statements/.ExcelSanitizeHelper::sanitizeArrayData().1), skipping empty rows via check_row_is_empty_or_null().PTCOShareDetailsModel::getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id, branch_id, policy_no[])
+ — matches pt.policy_no for completed transactions on that insurer branch.policy_no|endorsement_no (sanitized) for source and Excel rows.$matched_entry.error_data[row_index].insurer_statements.line_items, file_status (success / failed), reason (JSON).Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel.
+ +| error_code | Meaning | UI |
|---|---|---|
0 | DB row missing or physical file not found | Plain message in modal |
1 | Legacy: list of row numbers (old format) | Comma-separated row list |
2 | Row-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.
Runs only when validation returned status: true.
pt_co_share_details rows).3, brokerage col 54, brokerage col 60 in current codereward from col 7variance = exp_amt - total_amt (from source exp_amt)COShareStmtDetailsModel::insertBatch($data_to_update) — one row per matched Excel line.file_status = success, invoice_status = pending, clears/sets reason.Header row is removed; data columns used by validation/update:
+ +| Index | Column | Use |
|---|---|---|
1 | B | Policy number (required for matching) |
2 | C | Endorsement number |
3 | D | Actual BP amount |
4 | E | Actual TP amount |
5 | F | Actual BP brokerage |
6 | G | Actual TP brokerage |
7 | H | Reward |
Download the canonical layout from the list page link → downloadSampleInsurerStatement.
getNonReconcileredPolicyTransactionByPolicyAndEndorsement() joins:
pt_co_share_details (active) → policy_transaction (active, status = completed)insurer_id, insurer_branch_id, and pt.policy_no IN (...)
+ 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.
+
After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):
+ +invoice_status: pending, generated, sent, payment_receivedsaveInvoicePaymentDetails — JSON POST from invoice modaldeleteStatement($id) — soft-deletes co_share_stmt_details, inv_payment_details, and insurer_statements for that id
+ getInsurerStatementMonth returns successful statements for insurer+month so the UI can
+ disable statement numbers already used (disableStatementNo() in the view).
+
writable/uploads/statements/ exists and is writable (or call createStatementFolder() once).policy_tranction/statement/list in a logged-in session.getFileErr/{id} and renders reason JSON.validateInsurerStatement / updateInsurerStatement one-liner in statementList() with a known file_id.sanitizeStatementLookupValue(); re-type values if NHance shows a match but upload fails.error_code 2, duplicate message on second row.insurer_statements row remains; user sees failed status; re-upload needs a new statement or delete the failed row.getInsurerStatementMonth.dataStatus, not status code alone.validateInsurerStatementOld, updateInsurerStatementOLD remain in the controller; production path is the non-Old methods documented here.policy_tranction/report/list, variance, finance, outstanding listscronDailyBDSReport (separate from statement upload)
+ 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.
+
In short:
+d-M-Y.| Area | Location |
|---|---|
| UI | +app/Views/employee_upload.php — action Correction |
+
| Upload | +EmployeeController::employeesUplodWithEvents |
+
| Validation | +excelFileFormatValidation, excelFileDataValidation |
+
| Correction process | +EmployeeServiceController::employeesCorrectionProcess |
+
| Column config API | +getCorrectionExcelColumns() — used when building correction Excel programmatically |
+
| Job | +employeesCorrectionProcess in JobWorker.php |
+
| Endorsements | +EmpEndorsementModel — actions = c, table_name = employees, status = pending |
+
Routes (group /employee, authMVC):
GET /employee/upload — upload screenPOST /employee/upload — upload-action-type=correctionGET /employee/excel_error/{file_id} — validation errorsfiles.policy_id is the client policy id. Lookup requires active
+ employees + employee_polices on that policy and branch.
+ | Step | < 1 MB | ≥ 1 MB |
|---|---|---|
| Format validation | Inline on upload | Job excelFileFormatValidation |
| Data validation | Job excelFileDataValidation | Same |
| Correction process | Job employeesCorrectionProcess | Same |
$correction_excel_columns — 8 columns (A–H).C for mandatory rules on correction-specific columns.name, dob, relationship, email_corporate.d-M-Y.| Code | Meaning |
|---|---|
| 1 | Mandatory missing |
| 2 | Wrong format |
| 3 | Not in allowed list (e.g. invalid Field value) |
| 4 | Custom validation failed |
| 5 | File / policy problem |
| 6 | Column headers wrong |
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).
Per row:
+field_name ← column D (name, dob, relationship, email_corporate)new_value ← column E; if field is dob, converted from d-M-Y to Y-m-ddate_of_correction ← column F (converted to Y-m-d)remarks ← column H (optional)old_value ← current value from employees.{field_name}
+ 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.
+
| Col | Header | Required | Notes |
|---|---|---|---|
| A | S.No | Yes | |
| B | EMP ID | Yes | |
| C | NAME OF EMP/DEP | Yes | Must match DB before correction |
| D | Field | Yes | name, dob, relationship, email_corporate |
| E | Value | Yes | New value; DOB as d-M-Y |
| F | Date of Correction | Yes | d-M-Y |
| G | Change event | Yes | e.g. correction |
| H | Remarks | No | Stored on endorsement |
Fix DOB — one row, one field:
+ +| EMP ID | NAME | Field | Value | Date of Correction | Change event | Remarks |
|---|---|---|---|---|---|---|
| EMP001 | +Raj Kumar | +dob | +15-Jan-1985 | +19-May-2026 | +correction | +Typo in upload | +
Multiple fixes — use separate rows (same or different members):
+ +| EMP ID | NAME | Field | Value |
|---|---|---|---|
| EMP001 | Raj Kumar | email_corporate | raj.kumar@company.com |
| EMP001 | Priya Kumar | relationship | Spouse |
upload-action-type=correction; note file_id./employee/excel_error/{file_id} for code 10.emp_endorsement where file_id = upload id, actions = 'c', status = 'pending'.field_name, old_value, new_value per row to the Excel.getCorrectionExcelColumns() for header layout.emp_code + name; rename via a name field row uses the old name in column C.files.status = success does not mean every row created an endorsement.employees columns change only after endorsement approval/application.+ 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.
+
In short:
+D.| Area | Location |
|---|---|
| UI | +app/Views/employee_upload.php — action Deletion |
+
| Upload | +EmployeeController::employeesUplodWithEvents |
+
| Validation | +EmployeeServiceController::excelFileFormatValidation, excelFileDataValidation |
+
| Deletion process | +EmployeeServiceController::employeeDisembark |
+
| TPA auto-deletion | +EmployeeController::initializeDeletionProcessForTpaApiData |
+
| Job | +employeeDisembark in JobWorker.php |
+
| Endorsements | +EmpEndorsementModel → emp_endorsement (actions = d, status = pending) |
+
Routes (group /employee, authMVC):
GET /employee/upload — upload screenPOST /employee/upload — upload-action-type=deletionGET /employee/excel_error/{file_id} — read files.reason after validation failurefiles.policy_id is the client policy id. Member lookup joins
+ employees + employee_polices on that policy and branch.
+ | Step | < 1 MB | ≥ 1 MB |
|---|---|---|
| Format validation | Inline on upload | Job excelFileFormatValidation |
| Data validation | Job excelFileDataValidation | Same |
| Disembark | Job employeeDisembark | Same |
$deletion_excel_columns — 7 columns (A–G).D drives mandatory fields: Change event, Date of exit, Reason for exit, Claim status.d-M-Y.0 or 1.| Code | Meaning |
|---|---|
| 1 | Mandatory missing |
| 2 | Wrong format |
| 3 | Not in allowed list |
| 4 | Custom validation failed |
| 5 | File / policy problem |
| 6 | Column headers wrong |
Rows are grouped by EMP ID. For deletion, the main check is name_and_empid_check_in_db:
employee_polices row on the uploaded policy/branch.On success → queues employeeDisembark (not employeesOnboardPreprocess).
Loads the Excel again and processes each non-empty row.
+ +Per matched member, four pending endorsement rows are inserted on employee_polices:
date_of_exit ← Excel column E (converted to Y-m-d)reason_for_exit ← column Fstatus → inactiveclaim_status ← column G
+ 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”).
+
initializeDeletionProcessForTpaApiData($file_id):
tpa_api_data, action_flag_status = D).writable/uploads/excel/tpa_auto_deletion_{fileId}_{timestamp}.xls.files row with action = deletion.employeeDisembark(['file_id' => $newFileId]) synchronously.getDeletionEmployeeDataForExportExcel.| Col | Header | Required | Notes |
|---|---|---|---|
| A | S.No | Yes | |
| B | EMP ID | Yes | Employee / family code |
| C | NAME OF EMP/DEP | Yes | Must match DB name exactly |
| D | Change event | Yes | e.g. deletion |
| E | Date of exit | Yes | d-M-Y |
| F | Reason for exit | Yes | |
| G | Claim status | Yes | 0 or 1 |
Delete one dependent — only that name appears; Self row is not required in the file.
+ +| Row | EMP ID | NAME | Change event | Date of exit | Reason | Claim |
|---|---|---|---|---|---|---|
| 2 | EMP001 | Arjun Kumar | deletion | 19-May-2026 | Resigned | 0 |
Result: endorsements for Arjun only (relationship ≠ Self).
+ +Delete entire family — list the Self row; disembark loads all active family members for that EMP ID.
+ +| Row | EMP ID | NAME | Change event | Date of exit | Reason | Claim |
|---|---|---|---|---|---|---|
| 2 | EMP001 | Raj Kumar | deletion | 19-May-2026 | Resigned | 0 |
Result: pending endorsements for Self + all active dependents on that policy (same exit date/reason/claim from the row).
+ +/employee/upload with action deletion; note file_id.GET /employee/excel_error/{file_id} — look for code 10 (member not in DB).emp_endorsement where file_id = upload id, actions = 'd', status = 'pending'.not found or Existing endorsement pending.initializeDeletionProcessForTpaApiData and the generated tpa_auto_deletion_*.xls file.employees.name exactly (case/spacing).emp_status = active and employee_polices.status = active match.files.status does not reflect per-row skips; use endorsements table + logs.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"] - - - -get_familiy_compositionadditional_relationship), except
either-parents-pil and elders_count which are stripped before comparison.
++ This function mainly: +
+ 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.
+
In short:
+JobWorker.| Area | Location |
|---|---|
| UI | +app/Views/employee_upload.php — client, branch, policy, action Inception, file upload |
+
| Upload handler | +EmployeeController::employeesUplodWithEvents |
+
| Pipeline logic | +EmployeeServiceController — the three functions on this page |
+
| Job dispatch | +app/Controllers/JobWorker.php — maps job names to EmployeeServiceController |
+
| Column / Excel helpers | +app/Helpers/excel_util_helper.php — check_columns_name, custom validators |
+
| Upload record | +files table via FileModel — status, action, reason |
+
| Premium | +EB rack rate calculation — calculate_premium_new, employeesOnboardProcess |
+
Routes (group /employee, filter authMVC):
GET /employee/upload — upload screenPOST /employee/upload — upload + start validation (upload-action-type=inception)GET /employee/excel_error/{file_id} — returns files.reason JSON for the error modal
+ REST upload (mobile/HR): POST employeeRest/employeeUpload — same pipeline via
+ EmployeeRestController.
+
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.
+ | File size | Step 1 | Steps 2–3 |
|---|---|---|
| < 1 MB | +excelFileFormatValidation runs inline in the upload request |
+ Always queued: excelFileDataValidation → employeesOnboardPreprocess |
+
| ≥ 1 MB | +Job excelFileFormatValidation |
+ Same 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.
+
Job chain for inception (and missed_inception / addition / dependent_addition):
+excelFileFormatValidation → excelFileDataValidation → employeesOnboardPreprocess
Runs on the uploaded sheet before any DB business rules.
+ +writable/uploads/excel/{file_name}.$inception_excel_columns when action is inception (same column set for addition / dependent_addition / missed_inception).I), date/mobile formats, allowed lists, custom helpers (DOB, relationship, SI, mobile duplicate, etc.).| Code | Meaning |
|---|---|
| 1 | Mandatory value missing |
| 2 | Wrong format (e.g. date, mobile) |
| 3 | Value not in allowed list |
| 4 | Custom validation failed (DOB, relationship, SI, etc.) |
| 5 | File / policy / slab configuration problem |
| 6 | Column 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.
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" }
+ }
+ }
+}
+error_type — 1 = format step, 2 = data steperror_summary — counts per error code (after aggregation)error_data — keyed by Excel row number (1-based, header is row 1)Runs after format passes. Groups rows by EMP ID (family) and applies business rules.
+ +Typical inception checks:
+policy_terms (family composition, LGBTQ flag, etc.).name_and_empid_check_in_db).On success for inception: queues job employeesOnboardPreprocess. Other actions queue different jobs (deletion, correction, etc.).
| Code | Meaning |
|---|---|
| 7 | Duplicate name within same family in the Excel file |
| 9 | Record already exists in DB (inception / addition) |
| 10 | Record not found (used on deletion flows) |
| 14 | Self row missing in family |
| 26 | Duplicate employee code in file or DB |
Calculates premium and writes members to the database.
+ +emp_id.calculate_premium_new() using policy terms + slab rates + rack config.employeesOnboardProcess() — insert/update employees, employee_polices, create policy_transaction (inception).files.status = success; else failed with rack-rate message.
+ 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).
+
+ 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.
+
| Col | Header | Required (inception) | Notes |
|---|---|---|---|
| A | S.No | Yes | |
| B | EMP ID | Yes | Family key |
| C | NAME OF EMP/DEP | Yes | |
| D | DOB | Yes | d-M-Y; age vs relationship checked |
| E | Gender | Yes | M / F (several casings allowed) |
| F | RELATIONSHIP | Yes | Self, Spouse, Son, Daughter, … |
| G | BASIC COVER SI | Conditional | Validated against slab when applicable |
| H | Date of Coverage | No | Mandatory for addition / DA only |
| I | DOJ | No | |
| J | Basic Pay | No | Used when policy terms need it |
| K | Band/Grade | No | |
| L | Designation | No | |
| M | Phone | No | Mobile format; duplicate check |
| N | No | Duplicate check in file | |
| O | PRE EXISTING AILMENTS | Yes | 0 or 1 |
| P | Change event | No | Not used for pure inception |
| Q | Date of exit | No | Deletion only |
| R | Reason for exit | No | Deletion only |
| S | Unit | No | Must match branch units when filled |
Sample file: use the download link on the employee upload screen (environment-specific).
+ ++ 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.
| Row | +S.No | +EMP ID | +NAME | +DOB | +Gender | +RELATIONSHIP | +BASIC COVER SI | +PED | +
|---|---|---|---|---|---|---|---|---|
| 1 | Header row (all 19 columns A–S required in file) | |||||||
| 2 | +1 | +EMP001 | +Raj Kumar | +15-Jan-1985 | +M | +Self | +500000 | +0 | +
| 3 | +2 | +EMP001 | +Priya Kumar | +20-Mar-1988 | +F | +Spouse | +500000 | +0 | +
| 4 | +3 | +EMP001 | +Arjun Kumar | +10-Jun-2015 | +M | +Son | +500000 | +0 | +
Rules illustrated:
+d-M-Y; ages are checked against relationship (e.g. Son vs Self).0 or 1 on every member for inception.
+ 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.
+
policy_terms JSON and slab/rack rates for the client policy./employee/upload with action inception; note file_id in response or files table.status = failed, call GET /employee/excel_error/{file_id} or read files.reason.error_data row keys back to Excel (row 1 = header).calculate_premium_new (see rack-rate doc) and SI/slab config.inprogress, check job queue / JobWorker logs for the three job names.policy_terms or slab rates fails step 1 with code 5.calculate_premium_new returns data for every family.files.reason JSON for row-level errors after failure.+ 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.
+
Typical user path:
+50 — see why 50 is hardcoded.is_auto_mail = 1.ticket_claim_status.allowed_status; section visibility updates./non-eb-claim/mail_template (direct URL; not in main Claims sidebar today).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.
+
+ 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
+50in +ticket_claim_status(and matching templates underticket_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.
+
On create (non_eb_claim_form.php):
policy_type_id is set from the URL segment (typically 50) and is not updated when the user selects a branch policy.policy_type_display from the selected policy row (data-policy-type).POST /non-eb-claim/create persists policy_type_id from that hidden field — so new tickets from Add often store policy_type_id = 50 even when the linked policy is Fire, Marine, etc.
+ 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.
+
50 appears in code (change all if this design changes)| Location | Usage |
|---|---|
app/Config/Routes.php | GET non-eb-claim/new → claimForm/50 |
app/Views/non_eb_claim_list.php | DataTable Add button → /non-eb-claim/new/50 |
app/Views/non_eb_claim_form.php | Hidden 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) |
+ 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:
+
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.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.new/50 in
+ non_eb_claim_list.php with the correct id or dynamic selection.#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.getClaimStatusForPolicyType,
+ getTemplateDataByTicketID, and filters that assume a single Non-EB status catalog are tested for
+ the new ticket_type.50 in listClaimStatuses() if mobile
+ clients need per-product statuses.+ 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. +
+ +| Area | File / route |
|---|---|
| Controller | app/Controllers/NonEbClaimController.php |
| Model | app/Models/NonEbTicketMasterModel.php |
| List + filters | app/Views/non_eb_claim_search.php, non_eb_claim_list.php |
| New claim | app/Views/non_eb_claim_form.php |
| View / edit | app/Views/non_eb_claim_edit.php |
| Mail templates | app/Views/non_eb_claim_mail_template.php |
| Reports | app/Views/non_eb_claim_reports.php |
| Routes | app/Config/Routes.php — group /non-eb-claim, filter authMVC |
| ACL | app/Config/Acl.php — #^/non-eb-claim# (Claims team roles) |
| App menu | app/Views/layout/header.php — New / List only (EB mail template link is separate) |
| Method | Route | Controller | Purpose |
|---|---|---|---|
| GET | /non-eb-claim/list | claimList | Search page + default open claims |
| POST | /non-eb-claim/list | claimList | Filter → HTML partial for DataTable |
| GET | /non-eb-claim/new | claimForm/50 | New claim (default policy type 50) |
| GET | /non-eb-claim/new/{policy_type_id} | claimForm | New claim for selected product |
| POST | /non-eb-claim/create | createClaim | Create ticket + assets + history + auto-mail |
| GET | /non-eb-claim/view/{id} | view_claim | Edit layout (non_eb_claim_edit) |
| POST | /non-eb-claim/update | updateClaim | Update; auto-mail on status change |
| GET | /non-eb-claim/remove?ticket_id= | removeClaim | Soft delete (is_active = 0) |
| GET | /non-eb-claim/mail_template | mailTemplate | Template list + modal CRUD UI |
| POST | /non-eb-claim/crud_mail_template/1 | crudTemplate | Save template |
| POST | /non-eb-claim/crud_mail_template/2 | crudTemplate | Fetch one template (edit) |
| POST | /non-eb-claim/crud_mail_template/3 | crudTemplate | Soft delete template |
| POST | /non-eb-claim/note/1 | crudNote | Get note |
| POST | /non-eb-claim/note/2 | crudNote | Save note |
| POST | /non-eb-claim/reply | saveReply | Manual outbound mail + message row |
| GET | /non-eb-claim/reports | claimReports | Reports UI shell |
| POST | /non-eb-claim/reports | claimReports | Not implemented in controller — see Reports |
| POST | /non-eb-claim/getBranchAndPolicy | getBranchAndPolicyByClientID | Branches, policies, contacts for client |
| POST | /non-eb-claim/getVisibleSections | getVisibleSectionsAjax | Section keys for status |
| POST | /non-eb-claim/getMoreInfo | getMoreInfo | Ticket row JSON |
| POST | /non-eb-claim/uploadFile | uploadFile | Claim docs (file or Drive URL) |
| POST | /non-eb-claim/getClaimFiles | getClaimFiles | List files for ticket |
| GET | /non-eb-claim/removeFile?id= | removeFile | Soft delete file |
| POST | /non-eb-claim/saveIRDocs | saveIRDocs | Persist required-docs JSON on ticket |
| GET | /non-eb-claim/testAutoMail/{id} | testAutoMailTrigger | Dev/test auto-mail (optional) |
+ 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.
+
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):
+policy_type_id, insurer_id, claim_number, nhance_claim_ref_noclient_id, claim_status_id (status options filtered by policy type in JS)date_type + start_date / end_date (created_date or updated_date)
+ 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.
+
GET /non-eb-claim/new/{policy_type_id} → getFormData(): ACMs (role 3), clients, insurers, initial claim status for product, visible sections.POST getBranchAndPolicy fills branch, policy (Non-EB/Marine only), branch contact.POST getVisibleSections toggles accordion sections client-side.POST /non-eb-claim/create — validation via getValidationRules(), sanitizeInputArrayAdvanced, date normalization, optional asset file upload.client_id + loss_date + policy_no (if policy set) → HTTP 409-style JSON.non_eb_ticket_master, saveAssets(), history row, sendAutoMailTrigger(), redirect to list.
+ 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.
+
+ $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.
+
Template lookup (NonEbTicketMasterModel::getTemplateDataByTicketID):
ticket_claim_status on ticket’s claim_status_id (or explicit status_id for tests).ticket_mail_template where ticket_type = policy_type_id AND trigger_type = tcs.trigger_type.
+ 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):
+ +| Token | Ticket 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.
+
+ 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.
+
template_name, ticket_type (policy type id), trigger_type (1–9)subject, mail_content (Jodit HTML)is_auto_mail — checkbox “Auto Mail” (1 = send on create / status change when matched)id on save for update
+ 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).
+
| Action | Endpoint | Body | Result |
|---|---|---|---|
| Add / Save | POST …/crud_mail_template/1 | Form fields + mail_content + is_auto_mail | { status: bool } → reload |
| Edit load | POST …/crud_mail_template/2 | id | { status, data } opens modal |
| Delete | POST …/crud_mail_template/3 | id | Soft 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).
+
On the edit screen:
+POST /non-eb-claim/note/1 — id (ticket), optional is_auto_query → fetch active note.POST /non-eb-claim/note/2 — save note (required 3–1000 chars) via TicketNoteModel.uploadFile — multi upload to writable/uploads/claim_files/ or Google Drive URLs (file_type 1 vs 2).getClaimFiles — lists rows; local files expose download URL downloadClaimFile/{id}.removeFile — soft delete by file id.saveIRDocs — stores JSON in non_eb_ticket_master.required_docs.asset_file under writable/uploads/non_eb_asset_files/; loss description required when file uploaded.
+ 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).
+
| Table | Role |
|---|---|
non_eb_ticket_master | Main claim ticket |
non_eb_claim_asset | Repeating asset lines per ticket |
ticket_claim_status | Statuses per ticket_type (policy type id); trigger_type, allowed_status |
ticket_mail_template | Templates; ticket_type = policy type id |
ticket_history | Field-level audit (status, ACM, priority, …) |
ticket_messages | Outbound mail log |
ticket_notes | User notes per ticket |
claim_files | Attachments; ticket_type = 2 for Non-EB |
| Method | Used for |
|---|---|
claimList / claimSearch | List UI and filtered HTML |
claimForm / getFormData | New claim form bootstrap |
view_claim | Edit view |
createClaim / updateClaim | Persist ticket |
getClaimStatusForPolicyType / getVisibleSections* | Status dropdown + sections |
mailTemplate / crudTemplate | Template admin |
sendAutoMailTrigger / constructMailContent | Automated email |
saveReply / getTicketMessage | Manual email thread |
crudNote | Notes |
uploadFile / getClaimFiles / removeFile / saveIRDocs | Documents |
saveAssets / getAssets | Asset grid |
claimHistory / putHistoryAfterInsert | Audit trail |
getBranchAndPolicyByClientID | Client cascade |
claimReports | Reports page (GET only today) |
testAutoMailTrigger | Dev preview/send test |
ticket_claim_status rows exist per Non-EB/Marine policy_type.id with correct trigger_type and allowed_status./non-eb-claim/mail_template with matching ticket_type + trigger_type; enable Auto Mail only where intended.50 if a product needs its own workflow.claimReports() if reports Generate must work.ticket_type = 2 in new file-related code paths.#^/non-eb-claim# if new roles need access.ticket_mail_template for policy type + status trigger_type → no auto-mail and empty reply preview./non-eb-claim/reports but controller only loads view on GET.ticket_type = 50; Add/new routes and hidden form field use 50 — not the policy picked on the form. Different per-product statuses require full backend + frontend changes — checklist./ticket/mail_template, not Non-EB.is_active = 0; list queries filter active only.employeeRest / Api\NonEbClaimApiController — different validation and flows.
+ 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.
+
Typical sequence from the list:
+queuedrfq_createdqcr_createdrfq_sentqcr_sentwon
+ 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).
+
| Area | File |
|---|---|
| List + action dropdown | app/Views/leads_list.php |
| Non-EB add/edit form | app/Views/leads_non_eb.php |
| EB vs Non-EB form include | app/Views/leads_form_handler.php |
| All backend logic | app/Controllers/LeadsController.php |
| Google Sheets | GoogleSheetLib, Config\RfqConfig |
+ 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.
+
Library: app/Libraries/GoogleSheetLib.php — auth via service account JSON at
+{project-root}/nhance-ee8d1-e3c5269b1ec7.json, scopes DRIVE + SPREADSHEETS.
| Step | Before | Why |
|---|---|---|
| 1 | +Google service account JSON present; account email shared on template folder + each template sheet | +GoogleSheetLib cannot copy or edit without Drive access |
+
| 2 | +RFQ_PARENT_FOLDER_ID set in .env |
+ Target folder for every copied RFQ/QCR/Placement file | +
| 3 | +Master RFQ Google Sheet template created per product (Fire, Marine, GPA, etc.) with placeholder tokens | +RFQ copy source — one template per policy_type row |
+
| 4 | +policy_type.misc JSON updated with rfq_template_sheet_id for that product |
+ createRfqSheet() reads this; fails with “RFQ template not found” if missing |
+
| 5 | +Non-EB opportunity created with correct policy_type_id and rfq_qcr_viewers emails |
+ Lead must exist; viewers become sheet editors after copy | +
| 6 | +Before QCR: RFQ action completed (leads.misc.rfq_sheet_id set) |
+ QCR copies the lead’s RFQ sheet, not the policy template | +
| 7 | +Before Placement mail: QCR action completed (leads.misc.qcr_sheet_id set) |
+ Placement copies the QCR sheet | +
+ 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;
+
+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:
+https://docs.google.com/spreadsheets/d/{FILE_ID}/editphp public/index.php cli/list-sheet-folder-files {RFQ_PARENT_FOLDER_ID}
+ → writes sheetid.json with name + sheetId pairs
+ (GoogleSheetController::listFolderSheetFilesCli)QCR and Placement: no separate template ID per product. They always copy from the lead’s existing sheets:
+ +| Stage | Copy source | Stored on lead |
|---|---|---|
| RFQ | policy_type.misc.rfq_template_sheet_id | leads.misc.rfq_sheet_id |
| QCR | leads.misc.rfq_sheet_id | leads.misc.qcr_sheet_id |
| Placement | leads.misc.qcr_sheet_id (filename: QCR → Placement) | leads.misc.placement_sheet_id |
| Setting | Location | Purpose |
|---|---|---|
RFQ_PARENT_FOLDER_ID |
+ .env → Config\RfqConfig::$rfqParentFolderId |
+ Google Drive folder where copied RFQ/QCR/Placement files are created | +
rfqPlaceholders |
+ app/Config/RfqConfig.php |
+ Maps template tokens like {{INSURED_NAME}} to lead field keys filled on RFQ create |
+
rfqClaimsPlaceholder |
+ app/Config/RfqConfig.php |
+ Default {{CLAIMS_DETAILS}} — multi-row claims table from fin_years_claims |
+
protections |
+ app/Config/RfqConfig.php |
+ Locked cell ranges on new RFQ sheets only (e.g. RFQ Page!B12:C12) |
+
| Service account key | +nhance-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 template | Filled 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.
+
/leads/list → click Add.lead_form_type=2).GET /util/getLeadNonEB/2/0.leads_non_eb.php)allocg = Non-EB or Marine.GET /util/getPolicyTypeFields → dynamic fields in #appendArea.fin_years_claims JSON.rfq_qcr_viewers (emails) become Google Sheet editors later.POST /leads/create with lead_form_type=2.createLead()prepareLeadData() → prepareSingleLeadData() (one lead row; EB uses multi-row).claim_history=1.insertNewLead() — no demography background job (EB-only).queued (In-Queued).From the list action menu → Edit (available for both EB and Non-EB):
+ +getLeadsDataForEdit(lead_id, lead_form_type, actual_lead_id)GET /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}getLeadNonEB($type, $actual_lead_id, $id) (~line 4818)
+ $id present: fetches lead_edit_data, files, custom fields, date formattinggenerateViewPageHtml()rfq/claims_details_non_eb into claims_details_htmlleads_form_handler → includes leads_non_eb.php when selected_lead_type != 1POST /leads/create with hidden id → updateOldLead()
+ 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).
+
| # | +Action | +Visible when | +View (JS) / handler class | +Controller endpoint(s) | +Status after | +
|---|---|---|---|---|---|
| — | +Edit | +Always (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 | +
| 1 | +RFQ | +lead_form_type === 2 only |
+ .btnRfqSheetList → createRfqSheetFromList() |
+
+ GET
+ /leads/createRfqSheet?lead_id={id}+ LeadsController::createRfqSheet() — opens Google Sheet URL in new tab
+ |
+ rfq_created |
+
| 2 | +QCR | +
+ Non-EB; status not queued or rfq_created;
+ role 1, 5, 2, 3 or Business Support team
+ |
+ .btnQcrSheetList → createQcrSheetFromList() |
+
+ GET
+ /leads/createQcrSheet?lead_id={id}+ LeadsController::createQcrSheet()
+ |
+ qcr_created |
+
| 3 | +Send Internal Mail | +Non-EB; status != queued |
+ .btnInternalMailList → openInternalMailFromList() |
+
+ GET
+ /leads/mailTemplate?lead_id={id}&template_type=rfq+ POST + /leads/sendMail — recipient_type=internal
+ |
+ — | +
| 4 | +Send Insurer Mail | +Same as internal mail | +.btnInsurerMailList → openInsurerMailFromList() |
+
+ GET
+ /leads/mailTemplate?lead_id={id}&template_type=rfq+ POST + /leads/sendMail — recipient_type=insurer (RFQ sheet attach)
+ |
+ rfq_sent |
+
| 5 | +Send Client Mail | +Same as internal mail | +.btnClientMailList → openClientMailFromList() |
+
+ GET
+ /leads/mailTemplate?lead_id={id}&template_type=qcr+ POST + /leads/sendMail — recipient_type=client (QCR sheet attach)
+ |
+ qcr_sent |
+
| 6 | +Placement | +Same as internal mail | +.btnPlacementList → openPlacementFromList() |
+
+ GET
+ /rfq/placementData/{id}+ GET + /leads/mailTemplate?lead_id={id}&template_type=placement+ POST + /leads/sendMail — recipient_type=placement
+ |
+ won |
+
| — | +Email History | +Always (EB + Non-EB) | +.btnHistory → getLeadsDataForMailHistory() |
+
+ 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):
POST /leads/uploadLeadAttachment — optional extra files (lead_id, docs_name, file)constructURL_ForInternalMailSend(), insurer/client via constructURL_ForInsurerOrClientMailSend(), placement via constructURL_ForPlacementMailSend()#external_cc_disclaimer_modal) before client/placement send to non-user emailscreateRfqSheet())policy_type.misc.rfq_template_sheet_id.misc.rfq_sheet_id exists → return existing Google Sheet URL.GoogleSheetLib::copyTemplate() into RfqConfig::rfqParentFolderId.buildRfqPlaceholderData()): client, GST, PAN, policy period, claims table.leads.rfq_qcr_viewers email JSON.misc.rfq_sheet_id; set status = rfq_created.createQcrSheet())misc.rfq_sheet_id — returns 400 if RFQ not created yet.misc.qcr_sheet_id exists → return existing URL.rfq_qcr_viewers.misc.qcr_sheet_id; set status = qcr_created.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.
+
sendMailWithAttachement()For Non-EB (lead_form_type == 2), the Excel attachment comes from Google Sheets:
| recipient_type | Sheet used | Status after send |
|---|---|---|
internal |
+ QCR if status is qcr_created/qcr_sent, else RFQ |
+ Unchanged | +
insurer |
+ misc.rfq_sheet_id |
+ rfq_sent (unless already past that stage) |
+
client |
+ misc.qcr_sheet_id |
+ qcr_sent |
+
placement |
+ createAndDownloadPlacementSheet() — copies QCR sheet |
+ won + saves placement/payment/installment fields |
+
Implementation detail: downloadFileFromGoogleSheet() exports the chosen sheet to a temp
+.xlsx under writable/tmp/ before MailHelper::send_email().
Opened from list action → openPlacementFromList(leadId):
GET /rfq/placementData/{id} — pre-fills policy dates, premium, CD, installments, contacts.GET /leads/mailTemplate?template_type=placement — subject/body/attachments.POST /leads/sendMail with recipient_type=placement.won,
+ persists placement_date, premium_amount, cd_amount, installments, etc.| Status | Label | Set by |
|---|---|---|
queued | In-Queued | Create form (default) |
rfq_created | RFQ Created | createRfqSheet() |
rfq_sent | RFQ Sent | Insurer mail |
qcr_created | QCR Created | createQcrSheet() |
qcr_sent | QCR Sent | Client mail |
won | Won | Placement mail |
lost | Lost | User sets on edit form + lost reason |
{
+ "rfq_sheet_id": "…",
+ "qcr_sheet_id": "…",
+ "placement_sheet_id": "…"
+}
+
+Methods in LeadsController.php used by the list Non-EB flow:
| Method | Triggered 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) |
policy_type before first RFQ.policy_type.misc.rfq_template_sheet_id per product (see sheet ID per product).RFQ_PARENT_FOLDER_ID in .env and share folder/templates with the service account.Config\RfqConfig::$rfqPlaceholders.$isNonEb in leads_list.php.[1, 5, 2, 3] or BUSINESS_SUPPORT_TEAM_ID in user team.createQcrSheet fails if rfq_sheet_id is missing.queued.POST /leads/create; presence of hidden id triggers update.In short:
+check_si).| Area | Location |
|---|---|
| UI | +app/Views/employee_upload.php — action SI Enhancement |
+
| Upload | +EmployeeController::employeesUplodWithEvents |
+
| Validation | +excelFileFormatValidation, excelFileDataValidation |
+
| SI process | +employeesSIEnhanceProcess |
+
| Onboard SI path | +employeesSIEnhanceProcessWhileOnbboard — SI during dependent addition onboard (not Excel) |
+
| Job | +employeesSIEnhanceProcess in JobWorker.php |
+
| Helpers | +excel_util_helper.php — transform_si_excel_row_to_calculatable_format, premium_calculation_manager |
+
Routes (group /employee, authMVC):
GET /employee/upload — upload screenPOST /employee/upload — upload-action-type=si_enhancementGET /employee/excel_error/{file_id} — validation errorsfiles.policy_id is the client policy id. Slab rates load via
+ getPolicySlabRatesForEmpOnboard(policy_id, client_id).
+ | Step | < 1 MB | ≥ 1 MB |
|---|---|---|
| Format validation | Inline on upload | Job excelFileFormatValidation |
| Data validation | Job excelFileDataValidation | Same |
| SI enhance | Job employeesSIEnhanceProcess | Same |
$si_enhance_excel_columns — 5 columns (A–E).SI for mandatory rules.check_si against policy slabs (same helper as inception SI column).d-M-Y.| Code | Meaning |
|---|---|
| 1 | Mandatory missing |
| 2 | Wrong format |
| 3 | Not in allowed list |
| 4 | Custom validation failed (e.g. invalid Augmented SI for slab) |
| 5 | File / policy / slab problem |
| 6 | Column headers wrong |
name_and_empid_check_in_db for si_enhancement:
employee_polices on the policy.On success → queues employeesSIEnhanceProcess.
Per row:
+removeNumberFormatting).employee_polices row.transform_si_excel_row_to_calculatable_format (uses family max age/count).premium_calculation_manager → new premium for the augmented SI.employee_polices (actions = si):| field_name | new_value source |
|---|---|
basic_cover_si | Excel Augmented SI (column D) |
premium | Recalculated premium from rack logic |
si_enhancement_date | Excel 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.
+
| Col | Header | Required | Notes |
|---|---|---|---|
| A | S.No | Yes | |
| B | EMP ID | Yes | |
| C | NAME OF EMP/DEP | Yes | Must match DB |
| D | Augmented SI | Yes | New SI; validated via check_si |
| E | Date of SI Enhancement | Yes | d-M-Y |
Enhance Self SI — one row:
+ +| EMP ID | NAME | Augmented SI | Date of SI Enhancement |
|---|---|---|---|
| EMP001 | Raj Kumar | 1000000 | 19-May-2026 |
Enhance spouse only — separate row (premium uses that member’s relationship for rack selection):
+ +| EMP ID | NAME | Augmented SI | Date of SI Enhancement |
|---|---|---|---|
| EMP001 | Priya Kumar | 500000 | 19-May-2026 |
upload-action-type=si_enhancement; note file_id./employee/excel_error/{file_id} — codes 4 (SI/slab) or 10 (member missing).emp_endorsement where file_id = upload id, actions = 'si', status = 'pending' — expect up to 3 rows per member (same group_key).new_value on basic_cover_si and premium to Excel and rack expectations.check_si in step 1 must pass before the job runs.files.status = success does not guarantee every row created endorsements.employee_polices.basic_cover_si changes after endorsement processing.+ Related: + Inception, + Correction, + EB rack rate calculation. +