From aac694ca3db52feecff92a498b4c97b377da02c8 Mon Sep 17 00:00:00 2001
From: velz
+ 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: Admin (commission group): Runtime API:
+ Use the template from the upload screen (Download sample file) or copy from
+ Header text must match exactly (one row per rule, starting row 2). Empty cells are allowed and simply skip that condition. Template header + rows ( Example A — composite two-wheeler (from Example B — percentage four-wheeler: Example C — composite TP-only: Example D — flat amount: Tips: From
+ For successful uploads, action View Rules opens
+ Each rule is roughly: Calculation types in Rules with
+ BDS Insurer statement lets finance users upload an insurer-provided Excel statement,
+ validate each row against NHance policy transactions (
+ In short: Routes (prefix
+
+ Upload form fields ( Runs immediately after upload (and can be re-run manually in dev with a hard-coded Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel. Row keys in Runs only when validation returned Header row is removed; data columns used by validation/update: Download the canonical layout from the list page link →
+ Matching is on sanitized After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):
+
+ Correction updates existing member data on an active policy via Excel upload
+ (
+ Processing is in In short: Routes (group Rows grouped by EMP ID; for correction the critical check is On success → queues Per row:
+ Skips insert when a pending correction endorsement already exists for the same
+
+ Sets
+ TPA batch: If Fix DOB — one row, one field: Multiple fixes — use separate rows (same or different members):
+ Related:
+ Inception,
+ Deletion.
+
+ Deletion removes active members from a client policy via Excel upload
+ (
+ Core processing lives in In short: Routes (group Rows are grouped by EMP ID. For deletion, the main check is On success → queues Loads the Excel again and processes each non-empty row. Per matched member, four pending endorsement rows are inserted on
+ Returns an array of processed Delete one dependent — only that name appears; Self row is not required in the file. Result: endorsements for Arjun only (relationship ≠ Self). Delete entire family — list the Self row; disembark loads all active family members for that EMP ID. Result: pending endorsements for Self + all active dependents on that policy (same exit date/reason/claim from the row). Related: Inception (onboard pipeline uses the same upload screen and first two validation steps).Overview
+
+
+
+
+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).Key files
+
+
+
+
+
+
+
+
+ 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)Routes
+
+
+
+
+
+
+
+
+ Route Handler
+ 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
+
+POST getCommission
+ → InsuranceCommissionController::initiateCommissionCalc
+ → filter: CommissionApiFilterHow to build the commission file
+
+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”.
+ Required columns (row 1 headers)
+
+
+
+
+
+
+
+
+ Column Purpose Example
+ S.No Serial (not used in logic) 1
+ Premium Type Maps to policy_typeOD, TP, COM
+ Vehicle Type vehicle_type; comma = multiple (IN)Two Wheeler, Four Wheeler
+ Vehicle Sub Type vehicle_sub_typeCar, 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_cityRJ, 41
+ Renewal Type renewal_typeOnline, Cash, Card
+ Commission Type percentage, composite, flat, tieredpercentage
+ 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 Commission types (what to fill)
+
+
+
+
+
+
+
+
+ Type Fill in sheet Becomes 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
+ premiumSample rows (from project files)
+
+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,writable/.../rules/NOV2025/1_motor.json):
+
+
+Two Wheeler, CC Min/Max = 100composite, TP = 10, OD = 25
+
+
+Four Wheeler, CC = 1000, Vehicle Age Min/Max = 5percentage, Commission Value = 10
+
+
+TP, Vehicle Type = Four Wheelercomposite, Commission Params (TP) = 10
+
+
+Two Wheeler,Four Wheeler (comma → matches either)flat, Commission Value = 500
+
+
+.csv, .xlsx, .xls, .ods.motor, health; import logic is built for motor columns today.Rule upload
+
+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.Rules editor
+
+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.Rule JSON shape
+
+
+
+{
+ "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"
+ }
+}InsuranceCommissionController: percentage, composite, fixed. Conditions support ==, !=, >, >=, <, <=, between, in.Commission calculation API
+
+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)is_deleted: false are loaded; the first matching rule wins (no priority field yet).Developer steps
+
+
+
+
+/commission/list (logged-in admin).POST getCommission with the same insurer, department, and a policy_issue_date in that commission month.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 @@
+
+
+policy_issue_date to resolve the same MONYYYY folder.status and code in the JSON body, not only HTTP status.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.
+ Overview
+
+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).
+
+
+
+failed and errors are shown per row in the UI.Key files and routes
+
+
+
+
+
+
+
+
+ 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.xlsxpolicy_tranction/statement, filter authMVC):
+
+
+
+
+
+
+ Method Route Handler
+ GET liststatementList
+ POST uploaduploadInsurerStatement
+ GET downloadSampleInsurerStatementSample file download
+ GET downloadInsurerStatement/(:num)Uploaded file download
+ GET getFileErr/(:any)Validation failure JSON for modal
+ GET getInsurerStatementMonthUsed to disable already-used statement numbers
+ GET deleteStatement/(:any)Soft-delete statement + related rows
+ GET getPaymentDetails/(:any)Invoice modal data
+
+POST saveInvoicePaymentDetailsInvoice / 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:
+
+
+
+file_status — success or failed (failed rows show an alert icon → error modal)invoice_status — pending / generated / sent / payment received#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()
+
+
+
+
+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.validateInsurerStatement($params)
+
+file_id in statementList() comment).Steps
+
+
+
+
+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).sanitizeStatementLookupValue()
+
+Validation error codes
+
+
+
+
+
+
+
+
+ error_code Meaning UI
+ 0DB row missing or physical file not found Plain 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 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)
+
+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.Excel column mapping (0-based index)
+
+
+
+
+
+
+
+
+ Index Column Use
+ 1B Policy number (required for matching)
+ 2C Endorsement number
+ 3D Actual BP amount
+ 4E Actual TP amount
+ 5F Actual BP brokerage
+ 6G Actual TP brokerage
+
+7H Reward downloadSampleInsurerStatement.NHance source query
+
+getNonReconcileredPolicyTransactionByPolicyAndEndorsement() joins:
+
+
+pt_co_share_details (active) → policy_transaction (active, status = completed)insurer_id, insurer_branch_id, and pt.policy_no IN (...)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
+
+
+
+
+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 idgetInsurerStatementMonth returns successful statements for insurer+month so the UI can
+ disable statement numbers already used (disableStatementNo() in the view).
+Developer steps
+
+
+
+
+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.Common pitfalls
+
+
+
+
+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.Related BDS features
+
+
+
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 @@
+
+
+policy_tranction/report/list, variance, finance, outstanding listscronDailyBDSReport (separate from statement 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.
+EmployeeServiceController::employeesCorrectionProcess, queued after the same
+ format and data validation steps used for inception and deletion.
+Overview
+
+
+
+
+d-M-Y.Key files and routes
+
+
+
+
+
+
+
+
+ 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/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.
+ Sync vs background jobs
+
+
+
+
+
+
+
+
+ Step < 1 MB ≥ 1 MB
+ Format validation Inline on upload Job excelFileFormatValidation
+ Data validation Job excelFileDataValidationSame
+
+Correction process Job employeesCorrectionProcessSame Step 1: excelFileFormatValidation
+
+
+
+
+$correction_excel_columns — 8 columns (A–H).C for mandatory rules on correction-specific columns.name, dob, relationship, email_corporate.d-M-Y.Format error codes
+
+
+
+
+
+
+
+
+ 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 Step 2: excelFileDataValidation
+
+name_and_empid_check_in_db:
+
+
+employeesCorrectionProcess (with optional batch_file_id for TPA multi-file flows).Step 3: employeesCorrectionProcess
+
+
+
+
+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}emp_code, name, and field_name
+ (actions = c, endorsement_id IS NULL, status != truncated).
+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).
+batch_file_id is set, also queues
+ updateEmployeeDataFromTpa, reconTpaApiDataWithEmployeepolicies, and
+ initializeDeletionProcessForTpaApiData.
+Correction Excel columns
+
+
+
+
+
+
+
+
+ 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 Row layout examples
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ EMP ID NAME Field Value
+ EMP001 Raj Kumar email_corporate raj.kumar@company.com
+
+EMP001 Priya Kumar relationship Spouse Developer steps
+
+
+
+
+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.Common pitfalls
+
+
+
+
+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.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.
+EmployeeServiceController::employeeDisembark.
+ EmployeeController::initializeDeletionProcessForTpaApiData is a separate TPA-reconcile path that
+ builds a deletion Excel file and calls the same disembark function.
+Overview
+
+
+
+
+D.Key files and routes
+
+
+
+
+
+
+
+
+ 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)/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.
+ Sync vs background jobs
+
+
+
+
+
+
+
+
+ Step < 1 MB ≥ 1 MB
+ Format validation Inline on upload Job excelFileFormatValidation
+ Data validation Job excelFileDataValidationSame
+
+Disembark Job employeeDisembarkSame Step 1: excelFileFormatValidation
+
+
+
+
+$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.Format error codes
+
+
+
+
+
+
+
+
+ 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 Step 2: excelFileDataValidation
+
+name_and_empid_check_in_db:
+
+
+employee_polices row on the uploaded policy/branch.employeeDisembark (not employeesOnboardPreprocess).Step 3: employeeDisembark
+
+employee_polices:
+
+
+date_of_exit ← Excel column E (converted to Y-m-d)reason_for_exit ← column Fstatus → inactiveclaim_status ← column Gemployee.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):
+
+
+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.Deletion Excel columns
+
+
+
+
+
+
+
+
+ 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 1Row layout examples
+
+
+
+
+
+
+
+ Row EMP ID NAME Change event Date of exit Reason Claim
+
+2 EMP001 Arjun Kumar deletion 19-May-2026 Resigned 0
+
+
+
+
+
+ Row EMP ID NAME Change event Date of exit Reason Claim
+
+2 EMP001 Raj Kumar deletion 19-May-2026 Resigned 0 Developer steps
+
+
+
+
+/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.Common pitfalls
+
+
+
+
+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.
@@ -321,13 +294,122 @@ flowchart LR
(already grouped to one employee). Keys align with the JSON used in rack configuration (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. +