FEAT_DOCS_CONTD
This commit is contained in:
parent
ced690d86b
commit
aac694ca3d
@ -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'],
|
||||
],
|
||||
|
||||
|
||||
@ -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');
|
||||
|
||||
292
app/Views/docs/bds-commission.php
Normal file
292
app/Views/docs/bds-commission.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
/**
|
||||
* BDS commission — content only
|
||||
* app/Views/docs/bds-commission.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/commission_file_upload.php
|
||||
* - app/Views/commission_rules_list.php
|
||||
* - app/Controllers/RuleImportController.php
|
||||
* - app/Controllers/InsuranceCommissionController.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>BDS commission</strong> is a two-part flow: admins upload commission <strong>rules</strong>
|
||||
(Excel) per insurer, month, and department; partner/BDS systems then call an API to
|
||||
<strong>calculate payout</strong> for a policy using those rules.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload rules Excel"] --> B["Save JSON on disk"]
|
||||
B --> C["Edit rules in UI"]
|
||||
D["Policy data POST"] --> E["Load matching JSON"]
|
||||
E --> F["Match rule and return payout"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Setup:</strong> <code>RuleImportController</code> + <code>commission_file_upload.php</code> — import and manage rules.</li>
|
||||
<li><strong>Runtime:</strong> <code>POST getCommission</code> → <code>InsuranceCommissionController::initiateCommissionCalc</code> — read JSON, pick first matching rule, return payout.</li>
|
||||
<li>Rules file path: <code>writable/uploads/commission/rules/{MONYYYY}/{insurer_id}_{department}.json</code> (e.g. <code>SEP2025/5_motor.json</code>).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files">Key files</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Part</th><th>File</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Upload list UI</td><td><code>app/Views/commission_file_upload.php</code></td></tr>
|
||||
<tr><td>Rules editor UI</td><td><code>app/Views/commission_rules_list.php</code></td></tr>
|
||||
<tr><td>Upload and editor API</td><td><code>app/Controllers/RuleImportController.php</code></td></tr>
|
||||
<tr><td>Excel parsing</td><td><code>ruleImportService</code> (via <code>Config\Services::ruleImportService()</code>)</td></tr>
|
||||
<tr><td>Payout calculation API</td><td><code>app/Controllers/InsuranceCommissionController.php</code></td></tr>
|
||||
<tr><td>Upload metadata DB</td><td><code>commission_files</code> (<code>CommissionFilesModel</code>)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="routes">Routes</h2>
|
||||
|
||||
<p><strong>Admin (commission group):</strong></p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Route</th><th>Handler</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>GET/POST commission/list</code></td><td><code>commissionFileUploadList</code></td></tr>
|
||||
<tr><td><code>POST commission/upload</code></td><td><code>upload</code></td></tr>
|
||||
<tr><td><code>GET commission/sample_file</code></td><td>Sample CSV download</td></tr>
|
||||
<tr><td><code>GET commission/downloadErrorFile</code></td><td>Annotated error Excel</td></tr>
|
||||
<tr><td><code>GET commission/checkSameEntry</code></td><td>Duplicate insurer + month + department check</td></tr>
|
||||
<tr><td><code>GET commission/rules/list/(:id)</code></td><td><code>ruleList</code> — rules editor page</td></tr>
|
||||
<tr><td><code>POST commission/rules/save/</code></td><td><code>saveRule</code></td></tr>
|
||||
<tr><td><code>POST commission/rules/remove/</code></td><td><code>removeRule</code></td></tr>
|
||||
<tr><td><code>GET commission/checkRuleUsage</code></td><td>Whether rule is used on partner policies</td></tr>
|
||||
<tr><td><code>GET commission/deleteCommissionData/(:id)</code></td><td>Soft-delete file + mark rules deleted</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Runtime API:</strong></p>
|
||||
<pre><code class="language-text">POST getCommission
|
||||
→ InsuranceCommissionController::initiateCommissionCalc
|
||||
→ filter: CommissionApiFilter</code></pre>
|
||||
|
||||
<h2 id="commission-file-format">How to build the commission file</h2>
|
||||
|
||||
<p>
|
||||
Use the template from the upload screen (<strong>Download sample file</strong>) or copy from
|
||||
<code>public/sample_excel/sample_commission.csv</code>. Dev copies also live under
|
||||
<code>writable/uploads/commission/files/</code> (e.g. <code>sample_commission.csv</code>,
|
||||
<code>Sample_commission_file-New.xlsx</code>). After a successful import, see the generated JSON under
|
||||
<code>writable/uploads/commission/rules/{MONYYYY}/</code> (example: <code>NOV2025/1_motor.json</code>).
|
||||
</p>
|
||||
|
||||
<div class="callout warning">
|
||||
<span>!</span>
|
||||
<div>
|
||||
<strong>Use the current column layout</strong>
|
||||
Some older CSVs in <code>writable/uploads/commission/files/</code> use legacy headers
|
||||
(<code>Rule Name</code>, <code>Commission Params(TP:OD:PA)</code>). The importer expects the
|
||||
<strong>S.No</strong> layout below (<code>RuleImportService</code>). Wrong headers fail with
|
||||
“Missing required columns”.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="required-columns">Required columns (row 1 headers)</h3>
|
||||
|
||||
<p>Header text must match <strong>exactly</strong> (one row per rule, starting row 2). Empty cells are allowed and simply skip that condition.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Column</th><th>Purpose</th><th>Example</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>S.No</td><td>Serial (not used in logic)</td><td>1</td></tr>
|
||||
<tr><td>Premium Type</td><td>Maps to <code>policy_type</code></td><td>OD, TP, COM</td></tr>
|
||||
<tr><td>Vehicle Type</td><td><code>vehicle_type</code>; comma = multiple (IN)</td><td>Two Wheeler, Four Wheeler</td></tr>
|
||||
<tr><td>Vehicle Sub Type</td><td><code>vehicle_sub_type</code></td><td>Car, PCV</td></tr>
|
||||
<tr><td>Make / Model</td><td>Vehicle make and model</td><td>Honda, i10</td></tr>
|
||||
<tr><td>CC Min / CC Max</td><td>Cubic capacity range</td><td>1000, 3000</td></tr>
|
||||
<tr><td>Fuel Type</td><td>Comma-separated fuels</td><td>Petrol, Diesel</td></tr>
|
||||
<tr><td>Vehicle Age Min / Max</td><td>Vehicle age range (years)</td><td>1, 5</td></tr>
|
||||
<tr><td>Vehicle Weight Min / Max</td><td>Weight range (kg)</td><td>2000, 3000</td></tr>
|
||||
<tr><td>RTO State / RTO Code</td><td><code>geo_rto_state</code>, <code>geo_rto_city</code></td><td>RJ, 41</td></tr>
|
||||
<tr><td>Renewal Type</td><td><code>renewal_type</code></td><td>Online, Cash, Card</td></tr>
|
||||
<tr><td>Commission Type</td><td><code>percentage</code>, <code>composite</code>, <code>flat</code>, <code>tiered</code></td><td>percentage</td></tr>
|
||||
<tr><td>Commission Value</td><td>% or flat amount (required for percentage / flat)</td><td>15 or 500</td></tr>
|
||||
<tr><td>Commission Params (TP)</td><td>Composite: % on TP premium</td><td>18</td></tr>
|
||||
<tr><td>Commission Params (OD)</td><td>Composite: % on OD premium</td><td>10</td></tr>
|
||||
<tr><td>Commission Params (PA)</td><td>Composite: % on PA premium</td><td>0 or empty</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="commission-types">Commission types (what to fill)</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Type</th><th>Fill in sheet</th><th>Becomes in JSON</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>percentage</code></td>
|
||||
<td>Commission Value = e.g. <code>10</code></td>
|
||||
<td>10% of <code>premium</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>composite</code></td>
|
||||
<td>Leave Value empty; set TP / OD / PA param columns (percent each)</td>
|
||||
<td>Split % on <code>tp_premium</code>, <code>od_premium</code>, <code>pa_premium</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>flat</code></td>
|
||||
<td>Commission Value = fixed rupee amount e.g. <code>500</code></td>
|
||||
<td>Fixed payout on <code>premium</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="commission-file-samples">Sample rows (from project files)</h3>
|
||||
|
||||
<p><strong>Template header + rows</strong> (<code>public/sample_excel/sample_commission.csv</code>):</p>
|
||||
<pre><code class="language-text">S.No,Premium Type,Vehicle Type,...,Commission Type,Commission Value,Commission Params (TP),Commission Params (OD),Commission Params (PA)
|
||||
1,OD,Car,...,percentage,15,,,
|
||||
2,TP,Two Wheeler,...,composite,,18,10,
|
||||
3,COM,PCV,...,percentage,25,,,
|
||||
4,COM,GCV,...,composite,,,10,</code></pre>
|
||||
|
||||
<p><strong>Example A — composite two-wheeler</strong> (from <code>writable/.../rules/NOV2025/1_motor.json</code>):</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Two Wheeler</code>, CC Min/Max = <code>100</code></li>
|
||||
<li>Commission Type = <code>composite</code>, TP = <code>10</code>, OD = <code>25</code></li>
|
||||
<li>Result: 10% on TP premium + 25% on OD premium when policy matches</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example B — percentage four-wheeler</strong>:</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Four Wheeler</code>, CC = <code>1000</code>, Vehicle Age Min/Max = <code>5</code></li>
|
||||
<li>Commission Type = <code>percentage</code>, Commission Value = <code>10</code></li>
|
||||
<li>Result: 10% of total premium</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example C — composite TP-only</strong>:</p>
|
||||
<ul>
|
||||
<li>Premium Type = <code>TP</code>, Vehicle Type = <code>Four Wheeler</code></li>
|
||||
<li>Commission Type = <code>composite</code>, Commission Params (TP) = <code>10</code></li>
|
||||
<li>Result: 10% on TP premium only</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Example D — flat amount</strong>:</p>
|
||||
<ul>
|
||||
<li>Vehicle Type = <code>Two Wheeler,Four Wheeler</code> (comma → matches either)</li>
|
||||
<li>Commission Type = <code>flat</code>, Commission Value = <code>500</code></li>
|
||||
<li>Result: fixed ₹500 when matched</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Tips:</strong></p>
|
||||
<ul>
|
||||
<li>Min must not be greater than Max (CC, age, weight) or the row fails validation.</li>
|
||||
<li>On failure, download the <strong>annotated</strong> file — errors are written into the sheet.</li>
|
||||
<li>Supported formats: <code>.csv</code>, <code>.xlsx</code>, <code>.xls</code>, <code>.ods</code>.</li>
|
||||
<li>Upload UI departments: <code>motor</code>, <code>health</code>; import logic is built for <strong>motor</strong> columns today.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="upload-flow">Rule upload</h2>
|
||||
|
||||
<p>From <code>commission_file_upload.php</code> the user picks insurer, commission month, department (motor / health), and an Excel/CSV file.</p>
|
||||
|
||||
<ol>
|
||||
<li><code>checkSameEntry</code> — if a successful upload already exists for the same trio, SweetAlert offers <strong>Overwrite</strong> or <strong>Append</strong> (<code>overwrite=1</code> or <code>0</code> on POST).</li>
|
||||
<li><code>upload</code> — stores file under <code>writable/uploads/commission/files/</code>, inserts <code>commission_files</code> row (<code>pending</code>).</li>
|
||||
<li><code>ruleImportService->processUpload()</code> — validates Excel rows; on success returns <code>rules</code> array; on failure returns <code>annotated_file</code> for download.</li>
|
||||
<li>On success — writes JSON to <code>rules/{MONYYYY}/{insurer_id}_{department}.json</code>; sets <code>file_status=success</code> and <code>rules_count</code>.</li>
|
||||
<li>On failure — <code>file_status=failed</code>; user downloads <code>annotated_{filename}</code> via <code>downloadErrorFile</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="rules-editor">Rules editor</h2>
|
||||
|
||||
<p>
|
||||
For successful uploads, action <strong>View Rules</strong> opens
|
||||
<code>commission/rules/list/{file_id}</code> (<code>commission_rules_list.php</code>).
|
||||
</p>
|
||||
<ul>
|
||||
<li>Lists rules from the JSON file for that upload’s insurer, month, and department.</li>
|
||||
<li><code>saveRule</code> — create or update a rule (conditions + calculation + name) in the JSON via <code>updateCommissionRules()</code>.</li>
|
||||
<li><code>removeRule</code> — soft-delete one rule (<code>is_deleted=true</code>).</li>
|
||||
<li><code>checkRuleUsage</code> — warns if <code>partner_policy.commission_applied_rule</code> references the rule.</li>
|
||||
<li>Deleting the whole upload marks all rules with that <code>file_id</code> as deleted in JSON, then sets <code>commission_files.is_active=0</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="rule-json">Rule JSON shape</h2>
|
||||
|
||||
<p>Each rule is roughly:</p>
|
||||
<pre><code class="language-json">{
|
||||
"id": "rule_…",
|
||||
"name": "Rule name",
|
||||
"department": "motor",
|
||||
"file_id": 12,
|
||||
"is_deleted": false,
|
||||
"conditions": [
|
||||
{ "field": "vehicle_type", "operator": "==", "value": "car" }
|
||||
],
|
||||
"calculation": {
|
||||
"type": "percentage",
|
||||
"value": 10,
|
||||
"on": "premium"
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Calculation types in <code>InsuranceCommissionController</code>: <code>percentage</code>, <code>composite</code>, <code>fixed</code>. Conditions support <code>==</code>, <code>!=</code>, <code>></code>, <code>>=</code>, <code><</code>, <code><=</code>, <code>between</code>, <code>in</code>.</p>
|
||||
|
||||
<h2 id="calculation-api">Commission calculation API</h2>
|
||||
|
||||
<p><code>initiateCommissionCalc()</code> expects POST/JSON including at least:</p>
|
||||
<ul>
|
||||
<li><code>policy_issue_date</code> — used to pick folder <code>{MON}{YEAR}</code> (e.g. SEP2025)</li>
|
||||
<li><code>insurer_id</code></li>
|
||||
<li><code>department</code> — motor, health, etc.</li>
|
||||
<li>Fields referenced in rule conditions and calculation bases (e.g. <code>premium</code>, <code>od_premium</code>)</li>
|
||||
</ul>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["POST getCommission"] --> B{"Required fields present?"}
|
||||
B -->|No| C["Validation error"]
|
||||
B -->|Yes| D["Load rules JSON for month, insurer, department"]
|
||||
D --> E{"File exists?"}
|
||||
E -->|No| F["Rules file not found"]
|
||||
E -->|Yes| G["Find first rule where all conditions match"]
|
||||
G --> H{"Rule found?"}
|
||||
H -->|No| I["No matching rules"]
|
||||
H -->|Yes| J["Apply calculation type"]
|
||||
J --> K["Return payout and rule"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Rules with <code>is_deleted: false</code> are loaded; the <strong>first</strong> matching rule wins (no priority field yet).</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Open <code>/commission/list</code> (logged-in admin).</li>
|
||||
<li>Download sample file, fill rules for insurer + month + department, upload.</li>
|
||||
<li>If validation fails, download the annotated error file and fix the sheet.</li>
|
||||
<li>Use <strong>View Rules</strong> to tweak conditions or calculation without re-uploading the whole file.</li>
|
||||
<li>Test payout: <code>POST getCommission</code> with the same insurer, department, and a <code>policy_issue_date</code> in that commission month.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Month folder must match policy date</strong> — upload uses commission month; API uses <code>policy_issue_date</code> to resolve the same <code>MONYYYY</code> folder.</li>
|
||||
<li><strong>Append vs overwrite</strong> — append merges JSON arrays; overwrite backs up the old file then replaces.</li>
|
||||
<li><strong>Departments</strong> — upload UI currently offers motor and health; API department string must match the JSON filename slug.</li>
|
||||
<li><strong>HTTP 200 on upload errors</strong> — check <code>status</code> and <code>code</code> in the JSON body, not only HTTP status.</li>
|
||||
</ul>
|
||||
309
app/Views/docs/bds-insurer-statement.php
Normal file
309
app/Views/docs/bds-insurer-statement.php
Normal file
@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/**
|
||||
* BDS Insurer statement — content only
|
||||
* app/Views/docs/bds-insurer-statement.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/insurer_statement_list.php
|
||||
* - app/Controllers/PolicyTransactionController.php
|
||||
* (statementList, uploadInsurerStatement, validateInsurerStatement, updateInsurerStatement)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>BDS Insurer statement</strong> lets finance users upload an insurer-provided Excel statement,
|
||||
validate each row against NHance policy transactions (<code>pt_co_share_details</code> +
|
||||
<code>policy_transaction</code>), and persist matched brokerage amounts into
|
||||
<code>co_share_stmt_details</code>. The admin UI is
|
||||
<code>app/Views/insurer_statement_list.php</code>; all server logic lives in
|
||||
<code>PolicyTransactionController</code> under the <code>policy_tranction/statement</code> route group.
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong>Auth</strong>
|
||||
Statement routes use the <code>authMVC</code> filter. Upload and list are browser AJAX/form calls from an authenticated session, not public API endpoints.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A[Upload Excel] --> B[validateInsurerStatement]
|
||||
B --> C{All rows OK?}
|
||||
C -->|Yes| D[updateInsurerStatement]
|
||||
C -->|No| E[file_status failed]
|
||||
D --> F[co_share_stmt_details]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="row-validation-flowchart">Row validation logic</h2>
|
||||
|
||||
<p>
|
||||
<code>validateInsurerStatement()</code> checks that every Excel line maps to a real NHance transaction
|
||||
for the selected insurer branch. Each row is matched on <strong>policy number</strong> (column B) and
|
||||
<strong>endorsement number</strong> (column C).
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read Excel"] --> B["Fetch NHance rows for policies in the file"]
|
||||
B --> C["Check each non-empty row"]
|
||||
C --> D{"Policy and endorsement found in NHance?"}
|
||||
D -->|"Yes, first time in file"| E["Row valid"]
|
||||
D -->|"Same combo again"| F["Duplicate"]
|
||||
D -->|"Policy not found"| G["Invalid policy"]
|
||||
D -->|"Policy OK, wrong endorsement"| H["Invalid endorsement"]
|
||||
E --> I{"Any bad rows?"}
|
||||
F --> I
|
||||
G --> I
|
||||
H --> I
|
||||
I -->|No| J["Validation passes"]
|
||||
I -->|Yes| K["Validation fails"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li>Empty rows are ignored.</li>
|
||||
<li>A row is <strong>valid</strong> only if that policy + endorsement exists in NHance and appears once in the upload.</li>
|
||||
<li>If anything fails, the whole file is marked <code>failed</code> and errors are shown per row in the UI.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/insurer_statement_list.php</code> — DataTable list, upload modal, validation error modal, invoice modal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Controller</td>
|
||||
<td><code>app/Controllers/PolicyTransactionController.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Statement header model</td>
|
||||
<td><code>app/Models/InsurerStatements.php</code> → <code>insurer_statements</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Line-item model</td>
|
||||
<td><code>app/Models/COShareStmtDetailsModel.php</code> → <code>co_share_stmt_details</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NHance source rows</td>
|
||||
<td><code>app/Models/PTCOShareDetailsModel.php</code> → <code>getNonReconcileredPolicyTransactionByPolicyAndEndorsement()</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sample Excel</td>
|
||||
<td><code>public/sample_excel/insurer_stament_sample.xlsx</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Routes (prefix <code>policy_tranction/statement</code>, filter <code>authMVC</code>):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Route</th><th>Handler</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>GET</td><td><code>list</code></td><td><code>statementList</code></td></tr>
|
||||
<tr><td>POST</td><td><code>upload</code></td><td><code>uploadInsurerStatement</code></td></tr>
|
||||
<tr><td>GET</td><td><code>downloadSampleInsurerStatement</code></td><td>Sample file download</td></tr>
|
||||
<tr><td>GET</td><td><code>downloadInsurerStatement/(:num)</code></td><td>Uploaded file download</td></tr>
|
||||
<tr><td>GET</td><td><code>getFileErr/(:any)</code></td><td>Validation failure JSON for modal</td></tr>
|
||||
<tr><td>GET</td><td><code>getInsurerStatementMonth</code></td><td>Used to disable already-used statement numbers</td></tr>
|
||||
<tr><td>GET</td><td><code>deleteStatement/(:any)</code></td><td>Soft-delete statement + related rows</td></tr>
|
||||
<tr><td>GET</td><td><code>getPaymentDetails/(:any)</code></td><td>Invoice modal data</td></tr>
|
||||
<tr><td>POST</td><td><code>saveInvoicePaymentDetails</code></td><td>Invoice / payment save</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<pre><code class="language-php">$routes->group('policy_tranction', ['filter' => 'authMVC'], function ($routes) {
|
||||
$routes->group('statement', ['filter' => 'authMVC'], function ($routes) {
|
||||
$routes->get('list', 'PolicyTransactionController::statementList');
|
||||
$routes->post('upload', 'PolicyTransactionController::uploadInsurerStatement');
|
||||
// ...
|
||||
});
|
||||
});</code></pre>
|
||||
|
||||
<h2 id="statement-list-ui">Statement list UI</h2>
|
||||
|
||||
<p>
|
||||
<code>statementList()</code> loads insurers/branches via
|
||||
<code>insurerBranchModel::getInsurerBranchesWithInsurerNames()</code>, invoice status labels, and
|
||||
statements from the last <strong>180 days</strong> (<code>is_active = 1</code>). The view shows:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Insurer branch, statement month, statement serial no, filename (download link), line items count</li>
|
||||
<li><code>file_status</code> — <code>success</code> or <code>failed</code> (failed rows show an alert icon → error modal)</li>
|
||||
<li><code>invoice_status</code> — pending / generated / sent / payment received</li>
|
||||
<li>Actions (non-failed only): invoice status update, delete</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Upload form fields (<code>#insurer_statement_upload_form</code>): insurer
|
||||
(<code>insurer_id-branch_id</code>), statement month (flatpickr), statement no (1–7), Excel file.
|
||||
Submit is AJAX POST to relative <code>upload</code>. On success the page reloads; on validation failure
|
||||
the API still returns HTTP 200 with <code>dataStatus: false</code> and <code>error_data</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="upload-flow">uploadInsurerStatement()</h2>
|
||||
|
||||
<ol>
|
||||
<li>Validates uploaded file: Excel MIME types, max 16 MB (<code>max_size[statement,16384]</code> KB).</li>
|
||||
<li>Moves file to <code>WRITEPATH . 'uploads/statements/'</code> (see <code>createStatementFolder()</code> for folder creation).</li>
|
||||
<li>Parses POST: <code>insurer</code> as <code>{insurer_id}-{branch_id}</code>, <code>statement_month</code> (converted to first-of-month <code>Y-m-d</code>), <code>statement_no</code> → <code>stmt_sno</code>.</li>
|
||||
<li>Inserts <code>insurer_statements</code> row via <code>InsurerStatements</code> model.</li>
|
||||
<li>Calls <code>validateInsurerStatement(['file_id' => $file_id])</code>.</li>
|
||||
<li>If validation passes, calls <code>updateInsurerStatement(['file_id' => $file_id])</code>.</li>
|
||||
<li>On validation failure: responds with <code>dataStatus: false</code>, <code>error_data</code>, <code>error_code</code> (HTTP 200).</li>
|
||||
<li>On success: sets <code>invoice_status = 'pending'</code> and returns <code>dataStatus: true</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="validate-flow">validateInsurerStatement($params)</h2>
|
||||
|
||||
<p>Runs immediately after upload (and can be re-run manually in dev with a hard-coded <code>file_id</code> in <code>statementList()</code> comment).</p>
|
||||
|
||||
<h3 id="validate-steps">Steps</h3>
|
||||
|
||||
<ol>
|
||||
<li>Load <code>insurer_statements</code> by <code>file_id</code>; fail if missing or physical file absent under <code>writable/uploads/statements/</code>.</li>
|
||||
<li>Load active sheet via PhpSpreadsheet; drop header row; sanitize with <code>ExcelSanitizeHelper::sanitizeArrayData()</code>.</li>
|
||||
<li>Collect unique policy numbers from column <strong>B</strong> (index <code>1</code>), skipping empty rows via <code>check_row_is_empty_or_null()</code>.</li>
|
||||
<li>Fetch NHance candidates:
|
||||
<code>PTCOShareDetailsModel::getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id, branch_id, policy_no[])</code>
|
||||
— matches <code>pt.policy_no</code> for completed transactions on that insurer branch.</li>
|
||||
<li>Build lookup keys <code>policy_no|endorsement_no</code> (sanitized) for source and Excel rows.</li>
|
||||
<li>For each non-empty Excel row, match policy + endorsement; track duplicates in <code>$matched_entry</code>.</li>
|
||||
<li>On mismatch, append row-wise HTML messages under <code>error_data[row_index]</code>.</li>
|
||||
<li>Update <code>insurer_statements.line_items</code>, <code>file_status</code> (<code>success</code> / <code>failed</code>), <code>reason</code> (JSON).</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="sanitize">sanitizeStatementLookupValue()</h3>
|
||||
|
||||
<p>Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel.</p>
|
||||
|
||||
<h3 id="validation-errors">Validation error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>error_code</th><th>Meaning</th><th>UI</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>0</code></td><td>DB row missing or physical file not found</td><td>Plain message in modal</td></tr>
|
||||
<tr><td><code>1</code></td><td>Legacy: list of row numbers (old format)</td><td>Comma-separated row list</td></tr>
|
||||
<tr><td><code>2</code></td><td>Row-wise validation (current)</td><td>Modal lists each row with policy / endorsement / duplicate messages</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Row keys in <code>error_data</code> are the <strong>array index</strong> from the Excel loop (first data row is typically <code>1</code> after header removal), not necessarily the Excel row number on sheet.</p>
|
||||
|
||||
<h2 id="update-flow">updateInsurerStatement($params)</h2>
|
||||
|
||||
<p>Runs only when validation returned <code>status: true</code>.</p>
|
||||
|
||||
<ol>
|
||||
<li>Same file load / sanitize path as validation.</li>
|
||||
<li>Rebuild policy list and source lookup (policy + endorsement → array of <code>pt_co_share_details</code> rows).</li>
|
||||
<li>For each Excel row with a matching source entry, compute brokerage totals and variance:
|
||||
<ul>
|
||||
<li>BP: amount col <code>3</code>, brokerage col <code>5</code></li>
|
||||
<li>TP: amount col <code>4</code>, brokerage col <code>6</code></li>
|
||||
<li>TEP: forced to <code>0</code> in current code</li>
|
||||
<li><code>reward</code> from col <code>7</code></li>
|
||||
<li><code>variance = exp_amt - total_amt</code> (from source <code>exp_amt</code>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>COShareStmtDetailsModel::insertBatch($data_to_update)</code> — one row per matched Excel line.</li>
|
||||
<li>Sets <code>file_status = success</code>, <code>invoice_status = pending</code>, clears/sets <code>reason</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="excel-columns">Excel column mapping (0-based index)</h2>
|
||||
|
||||
<p>Header row is removed; data columns used by validation/update:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Index</th><th>Column</th><th>Use</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>1</code></td><td>B</td><td>Policy number (required for matching)</td></tr>
|
||||
<tr><td><code>2</code></td><td>C</td><td>Endorsement number</td></tr>
|
||||
<tr><td><code>3</code></td><td>D</td><td>Actual BP amount</td></tr>
|
||||
<tr><td><code>4</code></td><td>E</td><td>Actual TP amount</td></tr>
|
||||
<tr><td><code>5</code></td><td>F</td><td>Actual BP brokerage</td></tr>
|
||||
<tr><td><code>6</code></td><td>G</td><td>Actual TP brokerage</td></tr>
|
||||
<tr><td><code>7</code></td><td>H</td><td>Reward</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Download the canonical layout from the list page link → <code>downloadSampleInsurerStatement</code>.</p>
|
||||
|
||||
<h2 id="nhance-source-query">NHance source query</h2>
|
||||
|
||||
<p><code>getNonReconcileredPolicyTransactionByPolicyAndEndorsement()</code> joins:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>pt_co_share_details</code> (active) → <code>policy_transaction</code> (active, <code>status = completed</code>)</li>
|
||||
<li>Filtered by <code>insurer_id</code>, <code>insurer_branch_id</code>, and <code>pt.policy_no IN (...)</code></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Matching is on sanitized <code>policy_no</code> + <code>endorsement_no</code>. The method name suggests
|
||||
“non-reconciled” but the current query does <strong>not</strong> filter <code>statement_id IS NULL</code>;
|
||||
be aware when re-uploading or debugging duplicate reconciliation.
|
||||
</p>
|
||||
|
||||
<h2 id="invoice-and-delete">Invoice status and delete</h2>
|
||||
|
||||
<p>After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):</p>
|
||||
|
||||
<ul>
|
||||
<li><code>invoice_status</code>: <code>pending</code>, <code>generated</code>, <code>sent</code>, <code>payment_received</code></li>
|
||||
<li><code>saveInvoicePaymentDetails</code> — JSON POST from invoice modal</li>
|
||||
<li><code>deleteStatement($id)</code> — soft-deletes <code>co_share_stmt_details</code>, <code>inv_payment_details</code>, and <code>insurer_statements</code> for that id</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<code>getInsurerStatementMonth</code> returns successful statements for insurer+month so the UI can
|
||||
disable statement numbers already used (<code>disableStatementNo()</code> in the view).
|
||||
</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Ensure <code>writable/uploads/statements/</code> exists and is writable (or call <code>createStatementFolder()</code> once).</li>
|
||||
<li>Open <code>policy_tranction/statement/list</code> in a logged-in session.</li>
|
||||
<li>Use sample Excel; pick insurer branch and month; choose an unused statement number (1–7 per insurer/month).</li>
|
||||
<li>Confirm policy/endorsement exist on a <strong>completed</strong> BDS transaction for that insurer branch.</li>
|
||||
<li>On failure, open the alert icon → modal calls <code>getFileErr/{id}</code> and renders <code>reason</code> JSON.</li>
|
||||
<li>To debug validation only: temporarily uncomment the <code>validateInsurerStatement</code> / <code>updateInsurerStatement</code> one-liner in <code>statementList()</code> with a known <code>file_id</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="common-pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Hidden Excel characters</strong> — policy/endorsement must pass <code>sanitizeStatementLookupValue()</code>; re-type values if NHance shows a match but upload fails.</li>
|
||||
<li><strong>Duplicate policy + endorsement</strong> in the same file → <code>error_code 2</code>, duplicate message on second row.</li>
|
||||
<li><strong>Validation failed but file on disk</strong> — <code>insurer_statements</code> row remains; user sees <code>failed</code> status; re-upload needs a new statement or delete the failed row.</li>
|
||||
<li><strong>Statement number reuse</strong> — only successful uploads for that insurer/month block numbers in the dropdown via <code>getInsurerStatementMonth</code>.</li>
|
||||
<li><strong>Upload response HTTP 200 on error</strong> — front-end checks <code>dataStatus</code>, not status code alone.</li>
|
||||
<li><strong>Legacy handlers</strong> — <code>validateInsurerStatementOld</code>, <code>updateInsurerStatementOLD</code> remain in the controller; production path is the non-<code>Old</code> methods documented here.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="related-bds">Related BDS features</h2>
|
||||
|
||||
<ul>
|
||||
<li>BDS reports: <code>policy_tranction/report/list</code>, variance, finance, outstanding lists</li>
|
||||
<li>Daily BDS cron mail: <code>cronDailyBDSReport</code> (separate from statement upload)</li>
|
||||
</ul>
|
||||
265
app/Views/docs/correction.php
Normal file
265
app/Views/docs/correction.php
Normal file
@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* Correction (employee Excel upload) — content only
|
||||
* app/Views/docs/correction.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesCorrectionProcess)
|
||||
* - app/Controllers/EmployeeController.php (employeesUplodWithEvents)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Correction</strong> updates existing member data on an active policy via Excel upload
|
||||
(<code>files.action = correction</code>). The final step creates <strong>pending correction endorsements</strong>
|
||||
on the <code>employees</code> table — data is not updated until those endorsements are applied downstream.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Processing is in <code>EmployeeServiceController::employeesCorrectionProcess</code>, queued after the same
|
||||
format and data validation steps used for inception and deletion.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action correction"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesCorrectionProcess job"]
|
||||
D --> E["emp_endorsement pending on employees"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|loop done| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 8 columns (A–H); field must be one of four allowed names; dates <code>d-M-Y</code>.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist (emp code + name + active policy); code <strong>10</strong> if not found.</li>
|
||||
<li><strong>Step 3 — Correction:</strong> One pending endorsement per row per field (skips duplicate pending corrections).</li>
|
||||
<li>Each Excel row = one field change for one member (not a full-family operation).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>Correction</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Correction process</td>
|
||||
<td><code>EmployeeServiceController::employeesCorrectionProcess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Column config API</td>
|
||||
<td><code>getCorrectionExcelColumns()</code> — used when building correction Excel programmatically</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeesCorrectionProcess</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endorsements</td>
|
||||
<td><code>EmpEndorsementModel</code> — <code>actions = c</code>, <code>table_name = employees</code>, <code>status = pending</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=correction</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — validation errors</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the client policy id. Lookup requires active
|
||||
<code>employees</code> + <code>employee_polices</code> on that policy and branch.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>Correction process</td><td>Job <code>employeesCorrectionProcess</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$correction_excel_columns</code> — <strong>8 columns (A–H)</strong>.</li>
|
||||
<li>Action code <code>C</code> for mandatory rules on correction-specific columns.</li>
|
||||
<li><strong>Field</strong> (column D): only <code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code>.</li>
|
||||
<li><strong>Date of Correction</strong> (F): <code>d-M-Y</code>.</li>
|
||||
<li><strong>Change event</strong> (G) and <strong>Value</strong> (E) are mandatory.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list (e.g. invalid Field value)</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed</td></tr>
|
||||
<tr><td>5</td><td>File / policy problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Rows grouped by EMP ID; for correction the critical check is <code>name_and_empid_check_in_db</code>:</p>
|
||||
<ul>
|
||||
<li>Each row must match an active employee on the uploaded policy (emp code + name).</li>
|
||||
<li>Not found → error code <strong>10</strong> (“Record Not found”).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeesCorrectionProcess</code> (with optional <code>batch_file_id</code> for TPA multi-file flows).</p>
|
||||
|
||||
<h2 id="correction-process">Step 3: employeesCorrectionProcess</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read each Excel row"] --> B["Match emp_code plus name on active policy"]
|
||||
B --> C{"Pending correction for same field?"}
|
||||
C -->|Yes| D["Skip row"]
|
||||
C -->|No| E["Insert emp_endorsement actions c"]
|
||||
E --> F["old_value from DB new_value from Excel"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per row:</strong></p>
|
||||
<ul>
|
||||
<li><code>field_name</code> ← column D (<code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code>)</li>
|
||||
<li><code>new_value</code> ← column E; if field is <code>dob</code>, converted from <code>d-M-Y</code> to <code>Y-m-d</code></li>
|
||||
<li><code>date_of_correction</code> ← column F (converted to <code>Y-m-d</code>)</li>
|
||||
<li><code>remarks</code> ← column H (optional)</li>
|
||||
<li><code>old_value</code> ← current value from <code>employees.{field_name}</code></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Skips insert when a pending correction endorsement already exists for the same
|
||||
<code>emp_code</code>, <code>name</code>, and <code>field_name</code>
|
||||
(<code>actions = c</code>, <code>endorsement_id IS NULL</code>, <code>status != truncated</code>).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Sets <code>files.status = success</code> when the loop completes and sends a success pull notification.
|
||||
Rows with no DB match are not endorsed (no row-level failure on the file record).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>TPA batch:</strong> If <code>batch_file_id</code> is set, also queues
|
||||
<code>updateEmployeeDataFromTpa</code>, <code>reconTpaApiDataWithEmployeepolicies</code>, and
|
||||
<code>initializeDeletionProcessForTpaApiData</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">Correction Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB before correction</td></tr>
|
||||
<tr><td>D</td><td>Field</td><td>Yes</td><td><code>name</code>, <code>dob</code>, <code>relationship</code>, <code>email_corporate</code></td></tr>
|
||||
<tr><td>E</td><td>Value</td><td>Yes</td><td>New value; DOB as <code>d-M-Y</code></td></tr>
|
||||
<tr><td>F</td><td>Date of Correction</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
<tr><td>G</td><td>Change event</td><td>Yes</td><td>e.g. <code>correction</code></td></tr>
|
||||
<tr><td>H</td><td>Remarks</td><td>No</td><td>Stored on endorsement</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Fix DOB</strong> — one row, one field:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Field</th><th>Value</th><th>Date of Correction</th><th>Change event</th><th>Remarks</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>EMP001</td>
|
||||
<td>Raj Kumar</td>
|
||||
<td>dob</td>
|
||||
<td>15-Jan-1985</td>
|
||||
<td>19-May-2026</td>
|
||||
<td>correction</td>
|
||||
<td>Typo in upload</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Multiple fixes</strong> — use separate rows (same or different members):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Field</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Raj Kumar</td><td>email_corporate</td><td>raj.kumar@company.com</td></tr>
|
||||
<tr><td>EMP001</td><td>Priya Kumar</td><td>relationship</td><td>Spouse</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Upload with <code>upload-action-type=correction</code>; note <code>file_id</code>.</li>
|
||||
<li>On validation failure, check <code>/employee/excel_error/{file_id}</code> for code <strong>10</strong>.</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'c'</code>, <code>status = 'pending'</code>.</li>
|
||||
<li>Compare <code>field_name</code>, <code>old_value</code>, <code>new_value</code> per row to the Excel.</li>
|
||||
<li>To generate correction Excel in code, use <code>getCorrectionExcelColumns()</code> for header layout.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Name must match DB</strong> — correction identifies the member by current <code>emp_code</code> + <code>name</code>; rename via a <code>name</code> field row uses the old name in column C.</li>
|
||||
<li><strong>Only four fields</strong> — mobile, SI, band, etc. are not supported in this upload path.</li>
|
||||
<li><strong>Duplicate pending correction</strong> — second upload for the same field is skipped until the first endorsement is processed or truncated.</li>
|
||||
<li><strong>File success vs rows</strong> — <code>files.status = success</code> does not mean every row created an endorsement.</li>
|
||||
<li><strong>Not live update</strong> — <code>employees</code> columns change only after endorsement approval/application.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Related:
|
||||
<a href="<?= base_url('docs/inception') ?>">Inception</a>,
|
||||
<a href="<?= base_url('docs/deletion') ?>">Deletion</a>.
|
||||
</p>
|
||||
259
app/Views/docs/deletion.php
Normal file
259
app/Views/docs/deletion.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Deletion (employee Excel upload) — content only
|
||||
* app/Views/docs/deletion.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeeDisembark)
|
||||
* - app/Controllers/EmployeeController.php
|
||||
* (employeesUplodWithEvents, initializeDeletionProcessForTpaApiData)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Deletion</strong> removes active members from a client policy via Excel upload
|
||||
(<code>files.action = deletion</code>). Unlike inception, the final step does not delete rows immediately —
|
||||
it creates <strong>pending endorsement</strong> records on <code>employee_polices</code> for approval/processing later.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Core processing lives in <code>EmployeeServiceController::employeeDisembark</code>.
|
||||
<code>EmployeeController::initializeDeletionProcessForTpaApiData</code> is a separate TPA-reconcile path that
|
||||
builds a deletion Excel file and calls the same disembark function.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action deletion"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeeDisembark job"]
|
||||
D --> E["emp_endorsement pending rows"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|always| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 7 columns (A–G), mandatory exit fields per action code <code>D</code>.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist in DB (emp code + name + active policy); code 10 if not found.</li>
|
||||
<li><strong>Step 3 — Disembark:</strong> Writes pending deletion endorsements; Self row removes whole family, dependent row removes one member.</li>
|
||||
<li>Files < 1 MB run format validation inline; data validation and disembark are always queued.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>Deletion</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>EmployeeServiceController::excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deletion process</td>
|
||||
<td><code>EmployeeServiceController::employeeDisembark</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TPA auto-deletion</td>
|
||||
<td><code>EmployeeController::initializeDeletionProcessForTpaApiData</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeeDisembark</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endorsements</td>
|
||||
<td><code>EmpEndorsementModel</code> → <code>emp_endorsement</code> (<code>actions = d</code>, <code>status = pending</code>)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=deletion</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — read <code>files.reason</code> after validation failure</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the <strong>client policy id</strong>. Member lookup joins
|
||||
<code>employees</code> + <code>employee_polices</code> on that policy and branch.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>Disembark</td><td>Job <code>employeeDisembark</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$deletion_excel_columns</code> — <strong>7 columns (A–G)</strong>.</li>
|
||||
<li>Action code <code>D</code> drives mandatory fields: Change event, Date of exit, Reason for exit, Claim status.</li>
|
||||
<li>Date of exit format: <code>d-M-Y</code>.</li>
|
||||
<li>Claim status allowed: <code>0</code> or <code>1</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed</td></tr>
|
||||
<tr><td>5</td><td>File / policy problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Rows are grouped by EMP ID. For deletion, the main check is <code>name_and_empid_check_in_db</code>:</p>
|
||||
<ul>
|
||||
<li>Each row must match an <strong>active</strong> employee + <strong>active</strong> <code>employee_polices</code> row on the uploaded policy/branch.</li>
|
||||
<li>If not found → error code <strong>10</strong> (“Record Not found”) on that Excel row.</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeeDisembark</code> (not <code>employeesOnboardPreprocess</code>).</p>
|
||||
|
||||
<h2 id="disembark">Step 3: employeeDisembark</h2>
|
||||
|
||||
<p>Loads the Excel again and processes each non-empty row.</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Match emp_code plus name on active policy"] --> B{"Found?"}
|
||||
B -->|No| C["Log error skip row"]
|
||||
B -->|Yes| D{"Pending deletion endorsement?"}
|
||||
D -->|Yes| E["Log skip row"]
|
||||
D -->|No| F{"Relationship Self?"}
|
||||
F -->|No| G["Endorse this member only"]
|
||||
F -->|Yes| H["Endorse all active family members"]
|
||||
G --> I["emp_endorsement pending"]
|
||||
H --> I
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per matched member</strong>, four pending endorsement rows are inserted on <code>employee_polices</code>:</p>
|
||||
<ul>
|
||||
<li><code>date_of_exit</code> ← Excel column E (converted to <code>Y-m-d</code>)</li>
|
||||
<li><code>reason_for_exit</code> ← column F</li>
|
||||
<li><code>status</code> → <code>inactive</code></li>
|
||||
<li><code>claim_status</code> ← column G</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Returns an array of processed <code>employee.id</code> values. Sets <code>files.status = success</code> when the loop finishes
|
||||
(even if some rows were skipped — check logs for “not found” or “existing endorsement pending”).
|
||||
</p>
|
||||
|
||||
<h2 id="tpa-auto">TPA auto-deletion (EmployeeController)</h2>
|
||||
|
||||
<p><code>initializeDeletionProcessForTpaApiData($file_id)</code>:</p>
|
||||
<ol>
|
||||
<li>Reads TPA reconcile file context (<code>tpa_api_data</code>, <code>action_flag_status = D</code>).</li>
|
||||
<li>Builds candidate members and writes <code>writable/uploads/excel/tpa_auto_deletion_{fileId}_{timestamp}.xls</code>.</li>
|
||||
<li>Inserts a new <code>files</code> row with <code>action = deletion</code>.</li>
|
||||
<li>Calls <code>employeeDisembark(['file_id' => $newFileId])</code> synchronously.</li>
|
||||
<li>Exports endorsement data for TPA via <code>getDeletionEmployeeDataForExportExcel</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="excel-columns">Deletion Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td>Employee / family code</td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB name exactly</td></tr>
|
||||
<tr><td>D</td><td>Change event</td><td>Yes</td><td>e.g. <code>deletion</code></td></tr>
|
||||
<tr><td>E</td><td>Date of exit</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
<tr><td>F</td><td>Reason for exit</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>G</td><td>Claim status</td><td>Yes</td><td><code>0</code> or <code>1</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Delete one dependent</strong> — only that name appears; Self row is not required in the file.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Row</th><th>EMP ID</th><th>NAME</th><th>Change event</th><th>Date of exit</th><th>Reason</th><th>Claim</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>2</td><td>EMP001</td><td>Arjun Kumar</td><td>deletion</td><td>19-May-2026</td><td>Resigned</td><td>0</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Result: endorsements for <strong>Arjun only</strong> (relationship ≠ Self).</p>
|
||||
|
||||
<p><strong>Delete entire family</strong> — list the <strong>Self</strong> row; disembark loads all active family members for that EMP ID.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Row</th><th>EMP ID</th><th>NAME</th><th>Change event</th><th>Date of exit</th><th>Reason</th><th>Claim</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>2</td><td>EMP001</td><td>Raj Kumar</td><td>deletion</td><td>19-May-2026</td><td>Resigned</td><td>0</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Result: pending endorsements for <strong>Self + all active dependents</strong> on that policy (same exit date/reason/claim from the row).</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Upload via <code>/employee/upload</code> with action <code>deletion</code>; note <code>file_id</code>.</li>
|
||||
<li>If validation fails, use <code>GET /employee/excel_error/{file_id}</code> — look for code <strong>10</strong> (member not in DB).</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'd'</code>, <code>status = 'pending'</code>.</li>
|
||||
<li>If rows were skipped, search logs for <code>not found</code> or <code>Existing endorsement pending</code>.</li>
|
||||
<li>TPA path: trace <code>initializeDeletionProcessForTpaApiData</code> and the generated <code>tpa_auto_deletion_*.xls</code> file.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Name mismatch</strong> — Excel name must match <code>employees.name</code> exactly (case/spacing).</li>
|
||||
<li><strong>Not active</strong> — only <code>emp_status = active</code> and <code>employee_polices.status = active</code> match.</li>
|
||||
<li><strong>Duplicate pending deletion</strong> — row skipped if a pending deletion endorsement already exists for that policy row.</li>
|
||||
<li><strong>Self vs dependent</strong> — wrong relationship in the row changes scope (one member vs whole family).</li>
|
||||
<li><strong>File always success after disembark</strong> — <code>files.status</code> does not reflect per-row skips; use endorsements table + logs.</li>
|
||||
<li><strong>Not immediate delete</strong> — members stay active until endorsements are approved/applied downstream.</li>
|
||||
</ul>
|
||||
|
||||
<p>Related: <a href="<?= base_url('docs/inception') ?>">Inception</a> (onboard pipeline uses the same upload screen and first two validation steps).</p>
|
||||
@ -39,6 +39,7 @@ $base = base_url();
|
||||
/* ─── TOKENS ─────────────────────────────── */
|
||||
:root {
|
||||
--sidebar-w : 260px;
|
||||
--toc-w : 200px;
|
||||
--topbar-h : 56px;
|
||||
--bg : #ffffff;
|
||||
--bg2 : #f7f8fa;
|
||||
@ -143,6 +144,8 @@ $base = base_url();
|
||||
/* ─── LAYOUT WRAPPER ─────────────────────── */
|
||||
.docs-layout {
|
||||
display : flex;
|
||||
width : 100%;
|
||||
align-items: flex-start;
|
||||
margin-top: var(--topbar-h);
|
||||
min-height: calc(100vh - var(--topbar-h));
|
||||
}
|
||||
@ -208,6 +211,15 @@ $base = base_url();
|
||||
.callout.success { background: #f0fdf4; border-color: #4ade80; color: #15803d; }
|
||||
|
||||
/* ─── TABLES ─────────────────────────────── */
|
||||
.docs-table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.docs-table-wrap table { margin: 0; min-width: 640px; }
|
||||
.docs-table-wrap--fluid table { min-width: 0; width: 100%; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13.5px; }
|
||||
th {
|
||||
background : var(--bg2);
|
||||
|
||||
@ -27,10 +27,11 @@ $toc = $toc ?? [];
|
||||
<style>
|
||||
/* ─── MAIN CONTENT ───────────────────────── */
|
||||
.docs-main {
|
||||
flex : 1;
|
||||
max-width: 760px;
|
||||
padding : 48px 56px 80px;
|
||||
min-width: 0;
|
||||
flex : 1 1 auto;
|
||||
min-width : 0;
|
||||
max-width : none;
|
||||
width : 0;
|
||||
padding : 48px 40px 80px 48px;
|
||||
}
|
||||
|
||||
.docs-main__breadcrumb {
|
||||
@ -62,13 +63,18 @@ $toc = $toc ?? [];
|
||||
|
||||
/* ─── RIGHT TOC ──────────────────────────── */
|
||||
.docs-toc {
|
||||
width : 200px;
|
||||
min-width: 200px;
|
||||
padding : 56px 20px 0;
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
align-self: flex-start;
|
||||
width : var(--toc-w);
|
||||
min-width : var(--toc-w);
|
||||
max-width : var(--toc-w);
|
||||
padding : 56px 20px 48px 16px;
|
||||
border-left: 1px solid var(--border);
|
||||
background : var(--bg);
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
max-height : calc(100vh - var(--topbar-h) - 48px);
|
||||
overflow-y : auto;
|
||||
align-self : flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -92,6 +98,14 @@ $toc = $toc ?? [];
|
||||
|
||||
.docs-toc a:hover { color: var(--accent); }
|
||||
.docs-toc a.is-sub { padding-left: 10px; font-size: 12px; }
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.docs-toc { display: none; }
|
||||
.docs-main {
|
||||
width: auto;
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
|
||||
@ -285,34 +285,7 @@ flowchart TD
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="flow-group-slabs"><code>group_slab_rates_basedon_name</code></h2>
|
||||
|
||||
<p>
|
||||
Flattens the list returned from the model into a map keyed by <code>rack_rate_name</code>. Each bucket keeps
|
||||
<code>['slab_rates' => [...], 'grid_master' => ...]</code> where <code>grid_master</code> comes from the row’s policy grid record
|
||||
(<code>ui_type</code> becomes the numeric grid id used later in <code>premium_calculation_manager</code>).
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper" id="flowchart-group-slab-rates">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
S["slab_rates rows"] --> L["iterate in DB order"]
|
||||
L --> K{"rack_rate_name changed"}
|
||||
K -->|yes| B["open new bucket"]
|
||||
K -->|no| U["same bucket"]
|
||||
B --> P["append row to slab_rates array"]
|
||||
U --> P
|
||||
P --> G["store grid_master from row"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
When maintaining this helper, inspect the implementation for <strong>duplicate pushes</strong> on the first row of a new rack name;
|
||||
downstream code tolerates duplicate slab rows but it can confuse debugging of premium matches.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="flow-family-composition"><code>get_familiy_composition</code></h2>
|
||||
|
||||
@ -321,13 +294,122 @@ flowchart LR
|
||||
(already grouped to one employee). Keys align with the JSON used in rack configuration (<code>additional_relationship</code>), except
|
||||
<code>either-parents-pil</code> and <code>elders_count</code> which are stripped before comparison.
|
||||
</p>
|
||||
<p>
|
||||
This function mainly:
|
||||
<ul>
|
||||
<li>Reads all family members</li>
|
||||
<li>Normalizes relationship names</li>
|
||||
<li>Builds a summarized family composition object</li>
|
||||
<li>Maintains counts for:
|
||||
<ul style="padding-left: 20px;">
|
||||
<li>self</li>
|
||||
<li>spouse</li>
|
||||
<li>children</li>
|
||||
<li>parents</li>
|
||||
<li>parents-in-law</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper" id="flowchart-family-composition">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["single pass over family_data rows"] --> B["slugify column 5 per row"]
|
||||
B --> C["bump self spouse childrens parents parents-in-law counters"]
|
||||
C --> D["return family_composition map"]
|
||||
|
||||
START([Start get_familiy_composition])
|
||||
|
||||
START --> INIT["Initialize family_composition array and slug service"]
|
||||
|
||||
INIT --> LOOP_MEMBERS{"Loop each family member"}
|
||||
|
||||
LOOP_MEMBERS --> GET_REL["Read relationship from row[5]"]
|
||||
|
||||
GET_REL --> SLUGIFY["Slugify relationship"]
|
||||
|
||||
%% SELF
|
||||
|
||||
SLUGIFY --> CHECK_SELF{"relationship == self"}
|
||||
|
||||
CHECK_SELF -->|Yes| SET_SELF_ONE["Set self = 1"]
|
||||
|
||||
CHECK_SELF -->|No| CHECK_SELF_EXISTS{"self key exists?"}
|
||||
|
||||
CHECK_SELF_EXISTS -->|No| SET_SELF_ZERO["Set self = 0"]
|
||||
|
||||
CHECK_SELF_EXISTS -->|Yes| CHECK_SPOUSE
|
||||
|
||||
SET_SELF_ONE --> CHECK_SPOUSE
|
||||
SET_SELF_ZERO --> CHECK_SPOUSE
|
||||
|
||||
%% SPOUSE
|
||||
|
||||
CHECK_SPOUSE{"relationship == spouse"}
|
||||
|
||||
CHECK_SPOUSE -->|Yes| SET_SPOUSE_ONE["Set spouse = 1"]
|
||||
|
||||
CHECK_SPOUSE -->|No| CHECK_SPOUSE_EXISTS{"spouse key exists?"}
|
||||
|
||||
CHECK_SPOUSE_EXISTS -->|No| SET_SPOUSE_ZERO["Set spouse = 0"]
|
||||
|
||||
CHECK_SPOUSE_EXISTS -->|Yes| CHECK_CHILDREN
|
||||
|
||||
SET_SPOUSE_ONE --> CHECK_CHILDREN
|
||||
SET_SPOUSE_ZERO --> CHECK_CHILDREN
|
||||
|
||||
%% CHILDREN
|
||||
|
||||
CHECK_CHILDREN{"relationship == son OR daughter"}
|
||||
|
||||
CHECK_CHILDREN -->|Yes| INC_CHILDREN["Increment childrens count"]
|
||||
|
||||
CHECK_CHILDREN -->|No| CHECK_CHILDREN_EXISTS{"childrens key exists?"}
|
||||
|
||||
CHECK_CHILDREN_EXISTS -->|No| SET_CHILDREN_ZERO["Set childrens = 0"]
|
||||
|
||||
CHECK_CHILDREN_EXISTS -->|Yes| CHECK_PARENTS
|
||||
|
||||
INC_CHILDREN --> CHECK_PARENTS
|
||||
SET_CHILDREN_ZERO --> CHECK_PARENTS
|
||||
|
||||
%% PARENTS
|
||||
|
||||
CHECK_PARENTS{"relationship == father OR mother"}
|
||||
|
||||
CHECK_PARENTS -->|Yes| INC_PARENTS["Increment parents count"]
|
||||
|
||||
CHECK_PARENTS -->|No| CHECK_PARENTS_EXISTS{"parents key exists?"}
|
||||
|
||||
CHECK_PARENTS_EXISTS -->|No| SET_PARENTS_ZERO["Set parents = 0"]
|
||||
|
||||
CHECK_PARENTS_EXISTS -->|Yes| CHECK_INLAWS
|
||||
|
||||
INC_PARENTS --> CHECK_INLAWS
|
||||
SET_PARENTS_ZERO --> CHECK_INLAWS
|
||||
|
||||
%% PARENTS IN LAW
|
||||
|
||||
CHECK_INLAWS{"relationship == father-in-law OR mother-in-law"}
|
||||
|
||||
CHECK_INLAWS -->|Yes| INC_INLAWS["Increment parents-in-law count"]
|
||||
|
||||
CHECK_INLAWS -->|No| CHECK_INLAW_EXISTS{"parents-in-law key exists?"}
|
||||
|
||||
CHECK_INLAW_EXISTS -->|No| SET_INLAW_ZERO["Set parents-in-law = 0"]
|
||||
|
||||
CHECK_INLAW_EXISTS -->|Yes| NEXT_MEMBER
|
||||
|
||||
INC_INLAWS --> NEXT_MEMBER
|
||||
SET_INLAW_ZERO --> NEXT_MEMBER
|
||||
|
||||
%% LOOP
|
||||
|
||||
NEXT_MEMBER --> MORE_MEMBERS{"More family members?"}
|
||||
|
||||
MORE_MEMBERS -->|Yes| LOOP_MEMBERS
|
||||
|
||||
MORE_MEMBERS -->|No| RETURN_RESULT
|
||||
|
||||
RETURN_RESULT(["Return family_composition"])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -348,12 +430,87 @@ flowchart TD
|
||||
<div class="mermaid-wrapper" id="flowchart-compare-slab">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
J["decode additional_relationship JSON"] --> U["strip either-parents-pil and elders_count"]
|
||||
U --> E{"non-NA rules exist"}
|
||||
E -->|no| F["rack not applicable"]
|
||||
E -->|yes| K["sequential AND each non-NA key"]
|
||||
K -->|incoming matches value or any| Y["rack applicable plus relationship tokens"]
|
||||
K -->|missing key or mismatch| X["rack not applicable"]
|
||||
|
||||
START([Start compare_incoming_family_slab_with_configured_slab])
|
||||
|
||||
START --> INIT_MAP["Initialize relationship mapping"]
|
||||
|
||||
INIT_MAP --> INIT_RESULT["Initialize result
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INIT_RESULT --> READ_CONFIG["Read configured family composition from slab"]
|
||||
|
||||
READ_CONFIG --> REMOVE_KEYS["Remove ignored keys
|
||||
either-parents-pil
|
||||
elders_count"]
|
||||
|
||||
REMOVE_KEYS --> CHECK_CONFIG{"Configured composition has values?"}
|
||||
|
||||
%% EMPTY CONFIGURATION
|
||||
|
||||
CHECK_CONFIG -->|No| RESET_RESULT["Set result as not applicable"]
|
||||
|
||||
RESET_RESULT --> RETURN_RESULT
|
||||
|
||||
%% LOOP START
|
||||
|
||||
CHECK_CONFIG -->|Yes| LOOP_CONFIG{"Loop configured relationships"}
|
||||
|
||||
%% CHECK NA
|
||||
|
||||
LOOP_CONFIG --> CHECK_NA{"Value != NA ?"}
|
||||
|
||||
CHECK_NA -->|No| NEXT_RELATION
|
||||
|
||||
%% CHECK KEY EXISTS
|
||||
|
||||
CHECK_NA -->|Yes| CHECK_KEY_EXISTS{"Relationship exists in incoming composition?"}
|
||||
|
||||
CHECK_KEY_EXISTS -->|No| INVALID_RESULT_1["Set:
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INVALID_RESULT_1 --> BREAK_LOOP
|
||||
|
||||
%% VALUE COMPARISON
|
||||
|
||||
CHECK_KEY_EXISTS -->|Yes| CHECK_MATCH{
|
||||
Incoming count == configured count
|
||||
OR
|
||||
configured value == any
|
||||
}
|
||||
|
||||
%% MATCH FOUND
|
||||
|
||||
CHECK_MATCH -->|Yes| SET_APPLICABLE["Set is_applicable = true"]
|
||||
|
||||
SET_APPLICABLE --> MERGE_MEMBERS["Merge mapped relationships into applicable_members"]
|
||||
|
||||
MERGE_MEMBERS --> NEXT_RELATION
|
||||
|
||||
%% MATCH FAILED
|
||||
|
||||
CHECK_MATCH -->|No| INVALID_RESULT_2["Set:
|
||||
is_applicable = false
|
||||
applicable_members = []"]
|
||||
|
||||
INVALID_RESULT_2 --> BREAK_LOOP
|
||||
|
||||
%% LOOP CONTROL
|
||||
|
||||
NEXT_RELATION --> MORE_RELATIONS{"More relationships?"}
|
||||
|
||||
MORE_RELATIONS -->|Yes| LOOP_CONFIG
|
||||
|
||||
MORE_RELATIONS -->|No| RETURN_RESULT
|
||||
|
||||
BREAK_LOOP --> RETURN_RESULT
|
||||
|
||||
%% RETURN
|
||||
|
||||
RETURN_RESULT(["Return result array"])
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -378,13 +535,60 @@ flowchart TD
|
||||
<div class="mermaid-wrapper" id="flowchart-applicable-members">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["applicable_members from compare"] --> B["scan family_data by index"]
|
||||
B --> C{"relationship slug in list"}
|
||||
C -->|yes| D["record index and age from DOB"]
|
||||
C -->|no| B
|
||||
D --> B
|
||||
B -->|done| E["max_count equals number of indexes"]
|
||||
E --> F["walk indexes in order first gets acting_self true remainder false"]
|
||||
|
||||
START([Start get_applicable_familiy_members])
|
||||
|
||||
START --> INIT["Initialize:
|
||||
index = []
|
||||
max_age = []
|
||||
max_count = 0"]
|
||||
|
||||
INIT --> INIT_SLUG["Initialize slug service"]
|
||||
|
||||
INIT_SLUG --> LOOP_MEMBERS{"Loop each family member"}
|
||||
|
||||
%% READ RELATIONSHIP
|
||||
|
||||
LOOP_MEMBERS --> READ_REL["Read relationship from family_member[5]"]
|
||||
|
||||
READ_REL --> SLUGIFY["Slugify relationship"]
|
||||
|
||||
%% CHECK APPLICABLE
|
||||
|
||||
SLUGIFY --> CHECK_APPLICABLE{
|
||||
Relationship exists in applicable_members?
|
||||
}
|
||||
|
||||
%% MATCH FOUND
|
||||
|
||||
CHECK_APPLICABLE -->|Yes| STORE_INDEX["Add member index into result.index"]
|
||||
|
||||
STORE_INDEX --> CALCULATE_AGE["Calculate member age from DOB"]
|
||||
|
||||
CALCULATE_AGE --> STORE_AGE["Add age into result.max_age"]
|
||||
|
||||
STORE_AGE --> NEXT_MEMBER
|
||||
|
||||
%% NO MATCH
|
||||
|
||||
CHECK_APPLICABLE -->|No| NEXT_MEMBER
|
||||
|
||||
%% LOOP CONTROL
|
||||
|
||||
NEXT_MEMBER --> MORE_MEMBERS{"More family members?"}
|
||||
|
||||
MORE_MEMBERS -->|Yes| LOOP_MEMBERS
|
||||
|
||||
%% FINAL COUNT
|
||||
|
||||
MORE_MEMBERS -->|No| CALCULATE_COUNT["Set max_count =
|
||||
count(result.index)"]
|
||||
|
||||
CALCULATE_COUNT --> RETURN_RESULT
|
||||
|
||||
%% RETURN
|
||||
|
||||
RETURN_RESULT(["Return result array"])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
389
app/Views/docs/inception.php
Normal file
389
app/Views/docs/inception.php
Normal file
@ -0,0 +1,389 @@
|
||||
<?php
|
||||
/**
|
||||
* Inception (employee Excel upload) — content only
|
||||
* app/Views/docs/inception.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesOnboardPreprocess)
|
||||
* - app/Controllers/EmployeeController.php (upload entry)
|
||||
* - app/Controllers/JobWorker.php (background jobs)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>Inception</strong> here means onboarding employees and dependents from an Excel upload
|
||||
(<code>files.action = inception</code> or related actions like <code>missed_inception</code>,
|
||||
<code>addition</code>, <code>dependent_addition</code>). The same three-step pipeline runs for those
|
||||
actions; this page focuses on the <strong>inception</strong> path.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Manual policy inception (form UI under <code>policy_tranction/inception</code>) is a separate flow
|
||||
in <code>PolicyTransactionController</code> — not covered by these three functions.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel on employee upload"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesOnboardPreprocess"]
|
||||
D --> E["employeesOnboardProcess plus policy_transaction"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|no families inserted| F
|
||||
D -->|OK| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> Headers, column count/order, per-cell type and mandatory rules.</li>
|
||||
<li><strong>Step 2 — Data:</strong> Family-level checks (Self row, duplicates, policy terms, DB conflicts).</li>
|
||||
<li><strong>Step 3 — Preprocess:</strong> Premium via rack rates, then insert employees and inception policy transaction.</li>
|
||||
<li>Large files (> 1 MB) run steps 1–3 as background jobs via <code>JobWorker</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — client, branch, policy, action <strong>Inception</strong>, file upload</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload handler</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pipeline logic</td>
|
||||
<td><code>EmployeeServiceController</code> — the three functions on this page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job dispatch</td>
|
||||
<td><code>app/Controllers/JobWorker.php</code> — maps job names to <code>EmployeeServiceController</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Column / Excel helpers</td>
|
||||
<td><code>app/Helpers/excel_util_helper.php</code> — <code>check_columns_name</code>, custom validators</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload record</td>
|
||||
<td><code>files</code> table via <code>FileModel</code> — <code>status</code>, <code>action</code>, <code>reason</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Premium</td>
|
||||
<td><a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a> — <code>calculate_premium_new</code>, <code>employeesOnboardProcess</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, filter <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — upload + start validation (<code>upload-action-type=inception</code>)</li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — returns <code>files.reason</code> JSON for the error modal</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
REST upload (mobile/HR): <code>POST employeeRest/employeeUpload</code> — same pipeline via
|
||||
<code>EmployeeRestController</code>.
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> stores <strong>client policy id</strong> (<code>client_policies.id</code>),
|
||||
not the insurer policy master id. Slab/rack lookups use this id with <code>client_id</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>File size</th><th>Step 1</th><th>Steps 2–3</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>< 1 MB</td>
|
||||
<td><code>excelFileFormatValidation</code> runs inline in the upload request</td>
|
||||
<td>Always queued: <code>excelFileDataValidation</code> → <code>employeesOnboardPreprocess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>≥ 1 MB</td>
|
||||
<td>Job <code>excelFileFormatValidation</code></td>
|
||||
<td>Same job chain after format passes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
After upload the UI usually shows <code>files.status = inprogress</code> until jobs finish.
|
||||
Poll notifications or refresh the upload list; use <code>/employee/excel_error/{id}</code> when status is <code>failed</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="entry-points">Entry points</h2>
|
||||
|
||||
<p>Job chain for inception (and missed_inception / addition / dependent_addition):</p>
|
||||
<p><code>excelFileFormatValidation</code> → <code>excelFileDataValidation</code> → <code>employeesOnboardPreprocess</code></p>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<p>Runs on the uploaded sheet before any DB business rules.</p>
|
||||
|
||||
<ul>
|
||||
<li>Loads file from <code>writable/uploads/excel/{file_name}</code>.</li>
|
||||
<li>Uses <code>$inception_excel_columns</code> when <code>action</code> is inception (same column set for addition / dependent_addition / missed_inception).</li>
|
||||
<li>Checks policy has terms and slab rates configured — otherwise fails early.</li>
|
||||
<li>Validates each data row: mandatory (by action code <code>I</code>), date/mobile formats, allowed lists, custom helpers (DOB, relationship, SI, mobile duplicate, etc.).</li>
|
||||
<li>Stops at first empty row (treated as end of data).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Common format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory value missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format (e.g. date, mobile)</td></tr>
|
||||
<tr><td>3</td><td>Value not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed (DOB, relationship, SI, etc.)</td></tr>
|
||||
<tr><td>5</td><td>File / policy / slab configuration problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong or out of order</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>On failure: <code>files.status = failed</code>, <code>reason</code> JSON with row/column errors; user notification via pull notification.</p>
|
||||
|
||||
<p><strong>Lead-policy branch:</strong> If inception file is for a policy created from leads (<code>policy_entry_from == 3</code> and <code>is_from_lead</code> set), format validation queues <code>compareMemberDataAndInceptionData</code> instead of going straight to data validation.</p>
|
||||
|
||||
<h3 id="reason-json">files.reason shape (debugging)</h3>
|
||||
|
||||
<p>Stored as JSON string on <code>files.reason</code>. Typical failure payload:</p>
|
||||
<pre><code>{
|
||||
"error_type": 1,
|
||||
"error_summary": { "4": 2, "1": 1 },
|
||||
"error_data": {
|
||||
"3": {
|
||||
"dob": { "error": ["Invalid date format"], "value": "01/01/1990" }
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
<ul>
|
||||
<li><code>error_type</code> — <code>1</code> = format step, <code>2</code> = data step</li>
|
||||
<li><code>error_summary</code> — counts per error code (after aggregation)</li>
|
||||
<li><code>error_data</code> — keyed by <strong>Excel row number</strong> (1-based, header is row 1)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p>Runs after format passes. Groups rows by <strong>EMP ID</strong> (family) and applies business rules.</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Group rows by emp_id"] --> B{"Self in family?"}
|
||||
B -->|No| C["Error: Self not found"]
|
||||
B -->|Yes| D{"Duplicate name in family?"}
|
||||
D -->|Yes| E["Error: Twofold name"]
|
||||
D -->|No| F{"Emp code already in DB?"}
|
||||
F -->|Yes| G["Error: duplicate emp code"]
|
||||
F -->|No| H{"Dependents match policy terms?"}
|
||||
H -->|No| I["Dependent conflict errors"]
|
||||
H -->|Yes| J["Row OK for this family"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Typical inception checks:</p>
|
||||
<ul>
|
||||
<li><strong>Self</strong> row required (unless GMC parents policy allows otherwise).</li>
|
||||
<li>No duplicate names within the same family in the file.</li>
|
||||
<li>Employee code must not already exist for inception / addition / enrollment.</li>
|
||||
<li>Dependent rules vs <code>policy_terms</code> (family composition, LGBTQ flag, etc.).</li>
|
||||
<li>Name + emp id consistency vs database (<code>name_and_empid_check_in_db</code>).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success for inception: queues job <code>employeesOnboardPreprocess</code>. Other actions queue different jobs (deletion, correction, etc.).</p>
|
||||
|
||||
<h3 id="data-errors">Common data-validation error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>7</td><td>Duplicate name within same family in the Excel file</td></tr>
|
||||
<tr><td>9</td><td>Record already exists in DB (inception / addition)</td></tr>
|
||||
<tr><td>10</td><td>Record not found (used on deletion flows)</td></tr>
|
||||
<tr><td>14</td><td>Self row missing in family</td></tr>
|
||||
<tr><td>26</td><td>Duplicate employee code in file or DB</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="preprocess">Step 3: employeesOnboardPreprocess</h2>
|
||||
|
||||
<p>Calculates premium and writes members to the database.</p>
|
||||
|
||||
<ol>
|
||||
<li>Reload Excel; group by <code>emp_id</code>.</li>
|
||||
<li>For each family: <code>calculate_premium_new()</code> using policy terms + slab rates + rack config.</li>
|
||||
<li><code>employeesOnboardProcess()</code> — insert/update <code>employees</code>, <code>employee_polices</code>, create <code>policy_transaction</code> (inception).</li>
|
||||
<li>If at least one family inserted → <code>files.status = success</code>; else failed with rack-rate message.</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
Optional second path: <code>client_policy_id</code> without <code>file_id</code> — converts enrolled DB members
|
||||
to inception (enrollment → inception), not from Excel.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>TPA batch:</strong> When <code>batch_file_id</code> is present on the job payload, success also queues
|
||||
<code>updateEmployeeDataFromTpa</code>, <code>reconTpaApiDataWithEmployeepolicies</code>, and
|
||||
<code>initializeDeletionProcessForTpaApiData</code> (multi-file TPA reconcile flow).
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">Inception Excel columns</h2>
|
||||
|
||||
<p>
|
||||
Defined in <code>EmployeeServiceController::$inception_excel_columns</code>.
|
||||
Header row must match exactly — <strong>19 columns (A–S)</strong>, row 1 only.
|
||||
Dates use format <code>d-M-Y</code> (e.g. <code>4-Apr-1990</code>).
|
||||
One <strong>Self</strong> row per <strong>EMP ID</strong>; other rows are dependents.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required (inception)</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td>Family key</td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>D</td><td>DOB</td><td>Yes</td><td><code>d-M-Y</code>; age vs relationship checked</td></tr>
|
||||
<tr><td>E</td><td>Gender</td><td>Yes</td><td>M / F (several casings allowed)</td></tr>
|
||||
<tr><td>F</td><td>RELATIONSHIP</td><td>Yes</td><td>Self, Spouse, Son, Daughter, …</td></tr>
|
||||
<tr><td>G</td><td>BASIC COVER SI</td><td>Conditional</td><td>Validated against slab when applicable</td></tr>
|
||||
<tr><td>H</td><td>Date of Coverage</td><td>No</td><td>Mandatory for addition / DA only</td></tr>
|
||||
<tr><td>I</td><td>DOJ</td><td>No</td><td></td></tr>
|
||||
<tr><td>J</td><td>Basic Pay</td><td>No</td><td>Used when policy terms need it</td></tr>
|
||||
<tr><td>K</td><td>Band/Grade</td><td>No</td><td></td></tr>
|
||||
<tr><td>L</td><td>Designation</td><td>No</td><td></td></tr>
|
||||
<tr><td>M</td><td>Phone</td><td>No</td><td>Mobile format; duplicate check</td></tr>
|
||||
<tr><td>N</td><td>Email</td><td>No</td><td>Duplicate check in file</td></tr>
|
||||
<tr><td>O</td><td>PRE EXISTING AILMENTS</td><td>Yes</td><td><code>0</code> or <code>1</code></td></tr>
|
||||
<tr><td>P</td><td>Change event</td><td>No</td><td>Not used for pure inception</td></tr>
|
||||
<tr><td>Q</td><td>Date of exit</td><td>No</td><td>Deletion only</td></tr>
|
||||
<tr><td>R</td><td>Reason for exit</td><td>No</td><td>Deletion only</td></tr>
|
||||
<tr><td>S</td><td>Unit</td><td>No</td><td>Must match branch units when filled</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Sample file: use the download link on the employee upload screen (environment-specific).</p>
|
||||
|
||||
<h2 id="family-example">Family row layout example</h2>
|
||||
|
||||
<p>
|
||||
All rows with the same <strong>EMP ID</strong> (column B) are treated as one family.
|
||||
Step 2 requires exactly one <strong>Self</strong> row in that group; dependents share the same EMP ID.
|
||||
Step 3 runs premium and DB insert once per family.
|
||||
</p>
|
||||
|
||||
<p><strong>Example:</strong> one employee (<code>EMP001</code>) with spouse and son — three data rows plus header.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row</th>
|
||||
<th>S.No</th>
|
||||
<th>EMP ID</th>
|
||||
<th>NAME</th>
|
||||
<th>DOB</th>
|
||||
<th>Gender</th>
|
||||
<th>RELATIONSHIP</th>
|
||||
<th>BASIC COVER SI</th>
|
||||
<th>PED</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td colspan="8"><em>Header row (all 19 columns A–S required in file)</em></td></tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>1</td>
|
||||
<td>EMP001</td>
|
||||
<td>Raj Kumar</td>
|
||||
<td>15-Jan-1985</td>
|
||||
<td>M</td>
|
||||
<td>Self</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>2</td>
|
||||
<td>EMP001</td>
|
||||
<td>Priya Kumar</td>
|
||||
<td>20-Mar-1988</td>
|
||||
<td>F</td>
|
||||
<td>Spouse</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>3</td>
|
||||
<td>EMP001</td>
|
||||
<td>Arjun Kumar</td>
|
||||
<td>10-Jun-2015</td>
|
||||
<td>M</td>
|
||||
<td>Son</td>
|
||||
<td>500000</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Rules illustrated:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Same EMP ID</strong> on rows 2–4 → one family processed in step 3.</li>
|
||||
<li><strong>Self</strong> on row 2 only — row 3 without Self would fail with code 14.</li>
|
||||
<li><strong>DOB</strong> uses <code>d-M-Y</code>; ages are checked against relationship (e.g. Son vs Self).</li>
|
||||
<li><strong>PED</strong> (PRE EXISTING AILMENTS) = <code>0</code> or <code>1</code> on every member for inception.</li>
|
||||
<li><strong>BASIC COVER SI</strong> must match slab rules when the policy uses SI-based racks (often same amount across the family).</li>
|
||||
<li>Columns H–S can be blank for inception when not mandatory; phone/email must be unique in the file if filled.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
A second employee in the same file uses a <strong>different EMP ID</strong> (e.g. <code>EMP002</code>) with its own Self row — each EMP ID is a separate family loop in preprocess.
|
||||
</p>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Confirm policy has <code>policy_terms</code> JSON and slab/rack rates for the client policy.</li>
|
||||
<li>Upload via <code>/employee/upload</code> with action <code>inception</code>; note <code>file_id</code> in response or <code>files</code> table.</li>
|
||||
<li>If <code>status = failed</code>, call <code>GET /employee/excel_error/{file_id}</code> or read <code>files.reason</code>.</li>
|
||||
<li>Map <code>error_data</code> row keys back to Excel (row 1 = header).</li>
|
||||
<li>If format passes but preprocess fails with rack message, debug <code>calculate_premium_new</code> (see rack-rate doc) and SI/slab config.</li>
|
||||
<li>For stuck <code>inprogress</code>, check job queue / <code>JobWorker</code> logs for the three job names.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Policy not ready</strong> — missing <code>policy_terms</code> or slab rates fails step 1 with code 5.</li>
|
||||
<li><strong>Missing Self row</strong> — one Self per EMP ID in the file (code 14 in step 2).</li>
|
||||
<li><strong>Wrong header row</strong> — column count or name mismatch (code 5 / 6).</li>
|
||||
<li><strong>Rack rate / SI</strong> — preprocess succeeds only if <code>calculate_premium_new</code> returns data for every family.</li>
|
||||
<li><strong>File status</strong> — watch <code>files.reason</code> JSON for row-level errors after failure.</li>
|
||||
</ul>
|
||||
488
app/Views/docs/non-eb-claims.php
Normal file
488
app/Views/docs/non-eb-claims.php
Normal file
@ -0,0 +1,488 @@
|
||||
<?php
|
||||
/**
|
||||
* Non-EB Claims — content only
|
||||
* app/Views/docs/non-eb-claims.php
|
||||
*
|
||||
* Scope: web MVC only (/non-eb-claim/*). REST API under employeeRest is out of scope.
|
||||
* Sources:
|
||||
* - app/Controllers/NonEbClaimController.php
|
||||
* - app/Views/non_eb_claim_*.php
|
||||
* - app/Config/Routes.php (authMVC group)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
This page documents the <strong>Non-EB Claims</strong> web workflow: list and filter claims,
|
||||
create and edit tickets, status-driven form sections, document uploads, manual email reply,
|
||||
<strong>mail template CRUD</strong>, auto-mail on create/status change, and claim reports.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Scope:</strong> authenticated MVC routes under <code>/non-eb-claim/*</code> only.
|
||||
Mobile/REST endpoints in <code>Api\NonEbClaimApiController</code> are not covered here.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Policy types are limited to <code>policy_type.allocg IN ('Non-EB', 'Marine')</code>.
|
||||
Claim records live in <code>non_eb_ticket_master</code>; claim files use <code>ticket_type = 2</code>
|
||||
in <code>claim_files</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">End-to-end flow</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
L["GET /non-eb-claim/list"] --> F["Filter sidebar → POST /list"]
|
||||
F --> T["Table non_eb_claim_list.php"]
|
||||
T --> A{"Action"}
|
||||
A -->|Add| N["GET /non-eb-claim/new/50 or new/{policy_type_id}"]
|
||||
N --> C["POST /non-eb-claim/create"]
|
||||
C --> AM["sendAutoMailTrigger if template is_auto_mail=1"]
|
||||
A -->|Row click / View| V["GET /non-eb-claim/view/{id}"]
|
||||
V --> U["POST /non-eb-claim/update"]
|
||||
U --> SC{"claim_status_id changed?"}
|
||||
SC -->|yes| AM2["sendAutoMailTrigger"]
|
||||
MT["GET /non-eb-claim/mail_template"] --> CRUD["POST crud_mail_template/1|2|3"]
|
||||
R["GET /non-eb-claim/reports"] --> RP["POST /reports — UI only; see pitfalls"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Typical user path:</strong></p>
|
||||
<ol>
|
||||
<li>Open claim list (default: open claims excluding settled/closed/rejected/withdrawn).</li>
|
||||
<li>Add claim → form opens with canonical status policy type <code>50</code> — see <a href="#policy-type-50-default">why 50 is hardcoded</a>.</li>
|
||||
<li>On save, optional auto-mail fires if a matching template has <code>is_auto_mail = 1</code>.</li>
|
||||
<li>Open claim from list → edit view with history, messages, files, notes.</li>
|
||||
<li>Change status → allowed next statuses from <code>ticket_claim_status.allowed_status</code>; section visibility updates.</li>
|
||||
<li>Configure templates at <code>/non-eb-claim/mail_template</code> (direct URL; not in main Claims sidebar today).</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="policy-type-50-default">Why policy type <code>50</code> is hardcoded (read this before changing Non-EB Claims)</h2>
|
||||
|
||||
<div style="border-left: 4px solid #c0392b; background: #fdf2f2; padding: 1rem 1.25rem; margin: 1.25rem 0;">
|
||||
<p style="margin-top: 0;">
|
||||
<strong>Non-EB has many real policy products</strong> (Fire, Marine, Liability, etc. — each row in
|
||||
<code>policy_type</code> with <code>allocg</code> Non-EB or Marine). Claim <strong>status workflows</strong> are
|
||||
<em>not</em> stored separately per product today. The team configured <strong>one canonical policy type,
|
||||
id <code>50</code></strong>, in the database as the single source of status definitions. The application
|
||||
assumes <strong>every</strong> Non-EB/Marine claim uses that same status set unless you deliberately change
|
||||
backend and frontend.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 id="policy-type-50-assumption">Design assumption</h3>
|
||||
|
||||
<p>
|
||||
Claim statuses live in <code>ticket_claim_status</code>, keyed by <code>ticket_type</code> (which equals
|
||||
<code>policy_type.id</code>). Mail templates in <code>ticket_mail_template</code> also key off
|
||||
<code>ticket_type</code> + <code>trigger_type</code> from that status row.
|
||||
</p>
|
||||
|
||||
<p>Instead of maintaining duplicate status trees for every Non-EB product, developers assumed:</p>
|
||||
|
||||
<blockquote>
|
||||
<p style="margin: 0;">
|
||||
<strong>All Non-EB and Marine policy types share one identical claim status lifecycle.</strong>
|
||||
That lifecycle is configured only under policy type <code>50</code> in
|
||||
<code>ticket_claim_status</code> (and matching templates under <code>ticket_type = 50</code>).
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
New claims opened via the list <strong>Add</strong> button or <strong>New Non EB Claim</strong> menu therefore
|
||||
pass <code>50</code> into the form URL. Status dropdowns, section visibility, and initial status resolution in
|
||||
<code>getClaimStatusForPolicyType($policy_type_id)</code> all use that id on create — not the id of the policy
|
||||
the user later picks from <code>client_policy</code>.
|
||||
</p>
|
||||
|
||||
<h3 id="policy-type-50-vs-selected-policy">Policy type 50 vs policy selected on the form</h3>
|
||||
|
||||
<p>On create (<code>non_eb_claim_form.php</code>):</p>
|
||||
<ul>
|
||||
<li>Hidden <code>policy_type_id</code> is set from the URL segment (typically <code>50</code>) and is <strong>not</strong> updated when the user selects a branch policy.</li>
|
||||
<li>The UI shows the real product name in <code>policy_type_display</code> from the selected policy row (<code>data-policy-type</code>).</li>
|
||||
<li><code>POST /non-eb-claim/create</code> persists <code>policy_type_id</code> from that hidden field — so new tickets from Add often store <code>policy_type_id = 50</code> even when the linked policy is Fire, Marine, etc.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
On edit (<code>non_eb_claim_edit.php</code>), <code>policy_type_id</code> comes from
|
||||
<code>non_eb_ticket_master</code> as saved. Status changes and templates use that stored id.
|
||||
</p>
|
||||
|
||||
<h3 id="policy-type-50-where-hardcoded">Where <code>50</code> appears in code (change all if this design changes)</h3>
|
||||
|
||||
<div class="docs-table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Location</th><th>Usage</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>app/Config/Routes.php</code></td><td><code>GET non-eb-claim/new</code> → <code>claimForm/50</code></td></tr>
|
||||
<tr><td><code>app/Views/non_eb_claim_list.php</code></td><td>DataTable Add button → <code>/non-eb-claim/new/50</code></td></tr>
|
||||
<tr><td><code>app/Views/non_eb_claim_form.php</code></td><td>Hidden <code>policy_type_id</code> from route; status/section AJAX uses this value</td></tr>
|
||||
<tr><td><code>ticket_claim_status</code> (DB)</td><td>Master status rows maintained with <code>ticket_type = 50</code></td></tr>
|
||||
<tr><td><code>ticket_mail_template</code> (DB)</td><td>Non-EB auto-mail templates should use <code>ticket_type = 50</code> if they follow the shared workflow</td></tr>
|
||||
<tr><td><code>Api\NonEbClaimApiController::listClaimStatuses()</code></td><td>API hardcodes <code>where('ticket_type', 50)</code> (out of scope for this page but same assumption)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 id="policy-type-50-future">When a Non-EB product needs a <em>different</em> status set</h3>
|
||||
|
||||
<p>
|
||||
If a new (or existing) policy type must have its <strong>own</strong> statuses, triggers, or allowed transitions
|
||||
(not the shared tree under <code>50</code>), you cannot only change the product row in
|
||||
<code>policy_type</code>. You must update <strong>both backend and frontend</strong>:
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Database</strong> — Add full <code>ticket_claim_status</code> rows with
|
||||
<code>ticket_type = <that policy_type.id></code> (claim_status, display_name, trigger_type,
|
||||
allowed_status JSON). Add matching <code>ticket_mail_template</code> rows for that
|
||||
<code>ticket_type</code> if auto-mail applies.</li>
|
||||
<li><strong>Routes / entry URL</strong> — Stop routing every new claim through <code>50</code>: e.g. change
|
||||
<code>claimForm/50</code>, restore policy-type picker modal (commented in
|
||||
<code>non_eb_claim_search.php</code>), or pass the correct <code>policy_type_id</code> per product.</li>
|
||||
<li><strong>List Add button</strong> — Replace hardcoded <code>new/50</code> in
|
||||
<code>non_eb_claim_list.php</code> with the correct id or dynamic selection.</li>
|
||||
<li><strong>Create form</strong> — On policy selection, set hidden <code>#policy_type_id</code> to the real
|
||||
<code>policy_type_id</code> from <code>getBranchAndPolicy</code> (field <code>p.policy_type_id</code> is
|
||||
already returned) so create/update and <code>getVisibleSections</code> use the right status tree.</li>
|
||||
<li><strong>Controller logic</strong> — Ensure <code>getClaimStatusForPolicyType</code>,
|
||||
<code>getTemplateDataByTicketID</code>, and filters that assume a single Non-EB status catalog are tested for
|
||||
the new <code>ticket_type</code>.</li>
|
||||
<li><strong>API</strong> — Replace hardcoded <code>50</code> in <code>listClaimStatuses()</code> if mobile
|
||||
clients need per-product statuses.</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
Until those steps are done, pointing Add at another id without cloning the full status + template set under
|
||||
that id will produce <strong>empty status lists</strong>, <strong>wrong section visibility</strong>, or
|
||||
<strong>missing auto-mail</strong>.
|
||||
</p>
|
||||
|
||||
<h2 id="key-files">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>File / route</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Controller</td><td><code>app/Controllers/NonEbClaimController.php</code></td></tr>
|
||||
<tr><td>Model</td><td><code>app/Models/NonEbTicketMasterModel.php</code></td></tr>
|
||||
<tr><td>List + filters</td><td><code>app/Views/non_eb_claim_search.php</code>, <code>non_eb_claim_list.php</code></td></tr>
|
||||
<tr><td>New claim</td><td><code>app/Views/non_eb_claim_form.php</code></td></tr>
|
||||
<tr><td>View / edit</td><td><code>app/Views/non_eb_claim_edit.php</code></td></tr>
|
||||
<tr><td>Mail templates</td><td><code>app/Views/non_eb_claim_mail_template.php</code></td></tr>
|
||||
<tr><td>Reports</td><td><code>app/Views/non_eb_claim_reports.php</code></td></tr>
|
||||
<tr><td>Routes</td><td><code>app/Config/Routes.php</code> — group <code>/non-eb-claim</code>, filter <code>authMVC</code></td></tr>
|
||||
<tr><td>ACL</td><td><code>app/Config/Acl.php</code> — <code>#^/non-eb-claim#</code> (Claims team roles)</td></tr>
|
||||
<tr><td>App menu</td><td><code>app/Views/layout/header.php</code> — New / List only (EB mail template link is separate)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="route-map">MVC route map</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Route</th><th>Controller</th><th>Purpose</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/list</code></td><td><code>claimList</code></td><td>Search page + default open claims</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/list</code></td><td><code>claimList</code></td><td>Filter → HTML partial for DataTable</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/new</code></td><td><code>claimForm/50</code></td><td>New claim (default policy type 50)</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/new/{policy_type_id}</code></td><td><code>claimForm</code></td><td>New claim for selected product</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/create</code></td><td><code>createClaim</code></td><td>Create ticket + assets + history + auto-mail</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/view/{id}</code></td><td><code>view_claim</code></td><td>Edit layout (<code>non_eb_claim_edit</code>)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/update</code></td><td><code>updateClaim</code></td><td>Update; auto-mail on status change</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/remove?ticket_id=</code></td><td><code>removeClaim</code></td><td>Soft delete (<code>is_active = 0</code>)</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/mail_template</code></td><td><code>mailTemplate</code></td><td>Template list + modal CRUD UI</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/1</code></td><td><code>crudTemplate</code></td><td>Save template</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/2</code></td><td><code>crudTemplate</code></td><td>Fetch one template (edit)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/crud_mail_template/3</code></td><td><code>crudTemplate</code></td><td>Soft delete template</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/note/1</code></td><td><code>crudNote</code></td><td>Get note</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/note/2</code></td><td><code>crudNote</code></td><td>Save note</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/reply</code></td><td><code>saveReply</code></td><td>Manual outbound mail + message row</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/reports</code></td><td><code>claimReports</code></td><td>Reports UI shell</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/reports</code></td><td><code>claimReports</code></td><td><em>Not implemented in controller</em> — see <a href="#reports">Reports</a></td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getBranchAndPolicy</code></td><td><code>getBranchAndPolicyByClientID</code></td><td>Branches, policies, contacts for client</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getVisibleSections</code></td><td><code>getVisibleSectionsAjax</code></td><td>Section keys for status</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getMoreInfo</code></td><td><code>getMoreInfo</code></td><td>Ticket row JSON</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/uploadFile</code></td><td><code>uploadFile</code></td><td>Claim docs (file or Drive URL)</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/getClaimFiles</code></td><td><code>getClaimFiles</code></td><td>List files for ticket</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/removeFile?id=</code></td><td><code>removeFile</code></td><td>Soft delete file</td></tr>
|
||||
<tr><td>POST</td><td><code>/non-eb-claim/saveIRDocs</code></td><td><code>saveIRDocs</code></td><td>Persist required-docs JSON on ticket</td></tr>
|
||||
<tr><td>GET</td><td><code>/non-eb-claim/testAutoMail/{id}</code></td><td><code>testAutoMailTrigger</code></td><td>Dev/test auto-mail (optional)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="access">Access control</h2>
|
||||
|
||||
<p>
|
||||
All routes in the <code>/non-eb-claim</code> group use the <code>authMVC</code> filter.
|
||||
<code>Acl.php</code> allows roles <code>HEAD</code>, <code>ADMIN</code>, <code>MANAGER</code>,
|
||||
<code>ACCOUNT_MANAGER</code> on the Claims team.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
List row actions (view / delete) render only for roles <code>1, 2, 5</code> in
|
||||
<code>non_eb_claim_list.php</code>.
|
||||
</p>
|
||||
|
||||
<h2 id="list-flow">List and filter</h2>
|
||||
|
||||
<p><strong>GET <code>/non-eb-claim/list</code></strong> loads <code>non_eb_claim_search.php</code>, which includes the table partial. Initial data comes from <code>claimSearch(1)</code>: active tickets where status display name is <em>not</em> in Claim Settled, Claim Closed, Claim Rejected, Claim Withdrawn.</p>
|
||||
|
||||
<p><strong>Filter sidebar</strong> posts the same URL with criteria (at least one required):</p>
|
||||
<ul>
|
||||
<li><code>policy_type_id</code>, <code>insurer_id</code>, <code>claim_number</code>, <code>nhance_claim_ref_no</code></li>
|
||||
<li><code>client_id</code>, <code>claim_status_id</code> (status options filtered by policy type in JS)</li>
|
||||
<li><code>date_type</code> + <code>start_date</code> / <code>end_date</code> (<code>created_date</code> or <code>updated_date</code>)</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Response is JSON <code>{ status: true, html: "…" }</code>; JS replaces <code>#claim_list_div</code>
|
||||
and re-initializes the DataTable. Row click navigates to <code>/non-eb-claim/view/{id}</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Add</strong> button: <code>window.location.href = '/non-eb-claim/new/50'</code>.
|
||||
Header menu uses <code>/non-eb-claim/new</code> (routes to <code>claimForm/50</code>).
|
||||
See <a href="#policy-type-50-default">Policy type 50</a> for why this id is fixed and what to change if a product needs its own statuses.
|
||||
</p>
|
||||
|
||||
<h2 id="create-edit">Create and edit claim</h2>
|
||||
|
||||
<h3 id="create">Create</h3>
|
||||
|
||||
<ol>
|
||||
<li><code>GET /non-eb-claim/new/{policy_type_id}</code> → <code>getFormData()</code>: ACMs (role 3), clients, insurers, initial claim status for product, visible sections.</li>
|
||||
<li>User selects client → <code>POST getBranchAndPolicy</code> fills branch, policy (Non-EB/Marine only), branch contact.</li>
|
||||
<li>Status change → <code>POST getVisibleSections</code> toggles accordion sections client-side.</li>
|
||||
<li><code>POST /non-eb-claim/create</code> — validation via <code>getValidationRules()</code>, <code>sanitizeInputArrayAdvanced</code>, date normalization, optional asset file upload.</li>
|
||||
<li>Duplicate guard: same <code>client_id</code> + <code>loss_date</code> + <code>policy_no</code> (if policy set) → HTTP 409-style JSON.</li>
|
||||
<li>On success: insert <code>non_eb_ticket_master</code>, <code>saveAssets()</code>, history row, <code>sendAutoMailTrigger()</code>, redirect to list.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="edit">View / edit</h3>
|
||||
|
||||
<p>
|
||||
<code>view_claim($id)</code> loads ticket via <code>NonEbTicketMasterModel::getTicketDataByTicketID()</code>,
|
||||
merges mail template preview (<code>getTemplateDataByTicketID</code> + placeholder replace),
|
||||
messages, history, assets, and reuses the edit view <code>non_eb_claim_edit.php</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>POST /non-eb-claim/update</code> mirrors create validation. If <code>claim_status_id</code> changes,
|
||||
auto-mail runs again. <code>remark_mode=append</code> appends to <code>closure_remark</code> with a separator.
|
||||
</p>
|
||||
|
||||
<h2 id="status-sections">Status-driven sections</h2>
|
||||
|
||||
<p>
|
||||
<code>$statusSectionVisibility</code> in the controller maps each <code>ticket_claim_status.claim_status</code>
|
||||
label to section keys: <code>policy_account</code>, <code>loss_incident</code>, <code>intimation</code>,
|
||||
<code>insured_contact</code>, <code>asset</code>, <code>documents</code>, <code>surveyor</code>, <code>settlement</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Allowed next statuses come from <code>getClaimStatusForPolicyType()</code>: current status plus IDs in
|
||||
<code>allowed_status</code> JSON on the status row. Both create and edit forms call
|
||||
<code>getVisibleSectionsAjax</code> when the user changes status.
|
||||
</p>
|
||||
|
||||
<h2 id="auto-mail">Auto-mail and placeholders</h2>
|
||||
|
||||
<p>Template lookup (<code>NonEbTicketMasterModel::getTemplateDataByTicketID</code>):</p>
|
||||
<ul>
|
||||
<li>Join <code>ticket_claim_status</code> on ticket’s <code>claim_status_id</code> (or explicit <code>status_id</code> for tests).</li>
|
||||
<li>Join <code>ticket_mail_template</code> where <code>ticket_type = policy_type_id</code> AND <code>trigger_type = tcs.trigger_type</code>.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<code>sendAutoMailTrigger($ticket_id)</code> sends only when the matched template has
|
||||
<code>is_auto_mail = 1</code>. Mail goes to <code>insured_contact_email</code> from
|
||||
<code>constructMailContent()</code>; from address <code>claims@nhanceindia.in</code> via <code>MailHelper::send_email()</code>.
|
||||
Successful sends insert a <code>ticket_messages</code> row via <code>autoMessageInsertBasedOnMailResponse()</code>.
|
||||
</p>
|
||||
|
||||
<p>Placeholders (subject/body):</p>
|
||||
|
||||
<div class="docs-table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Token</th><th>Ticket field</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>((ACM))</code></td><td><code>acm</code></td></tr>
|
||||
<tr><td><code>((ACM_CONTACT))</code></td><td><code>acm_mobile</code></td></tr>
|
||||
<tr><td><code>((INSURED_NAME))</code></td><td><code>insured_contact_name</code></td></tr>
|
||||
<tr><td><code>((CORPORATE_NAME))</code></td><td><code>client_name</code></td></tr>
|
||||
<tr><td><code>((CLAIM_NO))</code></td><td><code>claim_number</code></td></tr>
|
||||
<tr><td><code>((POLICY_TYPE))</code></td><td><code>policy_type_name</code></td></tr>
|
||||
<tr><td><code>((NHANCE_REF_NO))</code></td><td><code>nhance_claim_ref_no</code></td></tr>
|
||||
<tr><td><code>((LOSS_DATE))</code></td><td><code>loss_date</code></td></tr>
|
||||
<tr><td><code>((LOSS_LOCATION))</code></td><td><code>loss_location</code></td></tr>
|
||||
<tr><td><code>((NATURE_OF_LOSS))</code></td><td><code>nature_of_loss</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Edit screen also supports <strong>manual reply</strong>: <code>POST /non-eb-claim/reply</code> validates To/Subject,
|
||||
inserts <code>ticket_messages</code>, sends via <code>sendReplyMessage()</code> with placeholder replacement.
|
||||
</p>
|
||||
|
||||
<h2 id="mail-template-crud">Mail template CRUD</h2>
|
||||
|
||||
<p>
|
||||
Page: <code>GET /non-eb-claim/mail_template</code>. DataTable lists rows from
|
||||
<code>ticket_mail_template</code> (<code>is_active = 1</code>). Tooltip on each row shows the
|
||||
matching <code>ticket_claim_status.claim_status</code> for that policy type + trigger type.
|
||||
</p>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
UI["mail_template UI"] --> S1["POST crud_mail_template/1 Save"]
|
||||
UI --> S2["POST crud_mail_template/2 Fetch"]
|
||||
UI --> S3["POST crud_mail_template/3 Delete"]
|
||||
S1 --> DB["ticket_mail_template"]
|
||||
S2 --> DB
|
||||
S3 --> DB
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="template-fields">Template fields</h3>
|
||||
|
||||
<ul>
|
||||
<li><code>template_name</code>, <code>ticket_type</code> (policy type id), <code>trigger_type</code> (1–9)</li>
|
||||
<li><code>subject</code>, <code>mail_content</code> (Jodit HTML)</li>
|
||||
<li><code>is_auto_mail</code> — checkbox “Auto Mail” (1 = send on create / status change when matched)</li>
|
||||
<li>Optional <code>id</code> on save for update</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<strong>Trigger ↔ status:</strong> Each <code>ticket_claim_status</code> row for a Non-EB/Marine
|
||||
<code>ticket_type</code> has a <code>trigger_type</code>. The template’s <code>trigger_type</code> must match
|
||||
that column for auto-mail and for the “Claim Status” readonly hint (<code>tool_tip</code> from server on fetch).
|
||||
</p>
|
||||
|
||||
<h3 id="template-actions">UI actions</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Action</th><th>Endpoint</th><th>Body</th><th>Result</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Add / Save</td><td><code>POST …/crud_mail_template/1</code></td><td>Form fields + <code>mail_content</code> + <code>is_auto_mail</code></td><td><code>{ status: bool }</code> → reload</td></tr>
|
||||
<tr><td>Edit load</td><td><code>POST …/crud_mail_template/2</code></td><td><code>id</code></td><td><code>{ status, data }</code> opens modal</td></tr>
|
||||
<tr><td>Delete</td><td><code>POST …/crud_mail_template/3</code></td><td><code>id</code></td><td>Soft delete (<code>is_active = 0</code>)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Placeholder dropdown in the modal inserts tokens into subject (focused input) or Jodit body.
|
||||
Validation errors return HTTP 400 with <code>errors</code> map (shown via toastr).
|
||||
</p>
|
||||
|
||||
<h2 id="notes">Notes</h2>
|
||||
|
||||
<p>On the edit screen:</p>
|
||||
<ul>
|
||||
<li><code>POST /non-eb-claim/note/1</code> — <code>id</code> (ticket), optional <code>is_auto_query</code> → fetch active note.</li>
|
||||
<li><code>POST /non-eb-claim/note/2</code> — save note (required 3–1000 chars) via <code>TicketNoteModel</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="documents">Documents and IR checklist</h2>
|
||||
|
||||
<ul>
|
||||
<li><code>uploadFile</code> — multi upload to <code>writable/uploads/claim_files/</code> or Google Drive URLs (<code>file_type</code> 1 vs 2).</li>
|
||||
<li><code>getClaimFiles</code> — lists rows; local files expose download URL <code>downloadClaimFile/{id}</code>.</li>
|
||||
<li><code>removeFile</code> — soft delete by file id.</li>
|
||||
<li><code>saveIRDocs</code> — stores JSON in <code>non_eb_ticket_master.required_docs</code>.</li>
|
||||
<li>Asset spreadsheet on ticket: <code>asset_file</code> under <code>writable/uploads/non_eb_asset_files/</code>; loss description required when file uploaded.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="reports">Reports</h2>
|
||||
|
||||
<p>
|
||||
<code>GET /non-eb-claim/reports</code> renders filters: policy type, ACM name, date range (default last 60 days).
|
||||
<code>generateReport()</code> in the view POSTs to the same URL and expects
|
||||
<code>{ status: true, data: [ rows ] }</code> for DataTable columns (status, policy type, claim/ref, client, insurer, loss fields, surveyor, settlement, ACM, created date).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Gap:</strong> <code>NonEbClaimController::claimReports()</code> only handles GET (layout load).
|
||||
There is no POST handler to return report data — Generate Report will fail until POST logic is added
|
||||
(mirror EB <code>ticket_reports</code> or reuse <code>claimSearch</code> with report-specific selects).
|
||||
</p>
|
||||
|
||||
<h2 id="data-model">Data model (summary)</h2>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Table</th><th>Role</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>non_eb_ticket_master</code></td><td>Main claim ticket</td></tr>
|
||||
<tr><td><code>non_eb_claim_asset</code></td><td>Repeating asset lines per ticket</td></tr>
|
||||
<tr><td><code>ticket_claim_status</code></td><td>Statuses per <code>ticket_type</code> (policy type id); <code>trigger_type</code>, <code>allowed_status</code></td></tr>
|
||||
<tr><td><code>ticket_mail_template</code></td><td>Templates; <code>ticket_type</code> = policy type id</td></tr>
|
||||
<tr><td><code>ticket_history</code></td><td>Field-level audit (status, ACM, priority, …)</td></tr>
|
||||
<tr><td><code>ticket_messages</code></td><td>Outbound mail log</td></tr>
|
||||
<tr><td><code>ticket_notes</code></td><td>User notes per ticket</td></tr>
|
||||
<tr><td><code>claim_files</code></td><td>Attachments; <code>ticket_type = 2</code> for Non-EB</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="controller-reference">Controller reference</h2>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table>
|
||||
<thead><tr><th>Method</th><th>Used for</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>claimList</code> / <code>claimSearch</code></td><td>List UI and filtered HTML</td></tr>
|
||||
<tr><td><code>claimForm</code> / <code>getFormData</code></td><td>New claim form bootstrap</td></tr>
|
||||
<tr><td><code>view_claim</code></td><td>Edit view</td></tr>
|
||||
<tr><td><code>createClaim</code> / <code>updateClaim</code></td><td>Persist ticket</td></tr>
|
||||
<tr><td><code>getClaimStatusForPolicyType</code> / <code>getVisibleSections*</code></td><td>Status dropdown + sections</td></tr>
|
||||
<tr><td><code>mailTemplate</code> / <code>crudTemplate</code></td><td>Template admin</td></tr>
|
||||
<tr><td><code>sendAutoMailTrigger</code> / <code>constructMailContent</code></td><td>Automated email</td></tr>
|
||||
<tr><td><code>saveReply</code> / <code>getTicketMessage</code></td><td>Manual email thread</td></tr>
|
||||
<tr><td><code>crudNote</code></td><td>Notes</td></tr>
|
||||
<tr><td><code>uploadFile</code> / <code>getClaimFiles</code> / <code>removeFile</code> / <code>saveIRDocs</code></td><td>Documents</td></tr>
|
||||
<tr><td><code>saveAssets</code> / <code>getAssets</code></td><td>Asset grid</td></tr>
|
||||
<tr><td><code>claimHistory</code> / <code>putHistoryAfterInsert</code></td><td>Audit trail</td></tr>
|
||||
<tr><td><code>getBranchAndPolicyByClientID</code></td><td>Client cascade</td></tr>
|
||||
<tr><td><code>claimReports</code></td><td>Reports page (GET only today)</td></tr>
|
||||
<tr><td><code>testAutoMailTrigger</code></td><td>Dev preview/send test</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="developer-steps">Developer checklist</h2>
|
||||
|
||||
<ol>
|
||||
<li>Ensure <code>ticket_claim_status</code> rows exist per Non-EB/Marine <code>policy_type.id</code> with correct <code>trigger_type</code> and <code>allowed_status</code>.</li>
|
||||
<li>Create mail templates at <code>/non-eb-claim/mail_template</code> with matching <code>ticket_type</code> + <code>trigger_type</code>; enable Auto Mail only where intended.</li>
|
||||
<li>Verify insured email is present before relying on auto-mail.</li>
|
||||
<li>Before changing Add/new URLs: read <a href="#policy-type-50-default">Policy type 50</a> — clone statuses + templates in DB and update every hardcoded <code>50</code> if a product needs its own workflow.</li>
|
||||
<li>Implement POST branch in <code>claimReports()</code> if reports Generate must work.</li>
|
||||
<li>Claim files: always set <code>ticket_type = 2</code> in new file-related code paths.</li>
|
||||
<li>ACL: extend <code>#^/non-eb-claim#</code> if new roles need access.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Template mismatch:</strong> No row in <code>ticket_mail_template</code> for policy type + status <code>trigger_type</code> → no auto-mail and empty reply preview.</li>
|
||||
<li><strong>Reports POST missing:</strong> UI POSTs to <code>/non-eb-claim/reports</code> but controller only loads view on GET.</li>
|
||||
<li><strong>Duplicate claims:</strong> Same client + loss date + policy number blocked on create.</li>
|
||||
<li><strong>Policy type 50 assumption:</strong> Shared status catalog under DB <code>ticket_type = 50</code>; Add/new routes and hidden form field use <code>50</code> — not the policy picked on the form. Different per-product statuses require full backend + frontend changes — <a href="#policy-type-50-future">checklist</a>.</li>
|
||||
<li><strong>Menu vs Non-EB templates:</strong> Sidebar “Mail Template” points to EB <code>/ticket/mail_template</code>, not Non-EB.</li>
|
||||
<li><strong>Soft deletes:</strong> Remove claim/file/template sets <code>is_active = 0</code>; list queries filter active only.</li>
|
||||
<li><strong>Asset file:</strong> Upload without loss description fails validation on create/update.</li>
|
||||
<li><strong>REST API separate:</strong> Mobile create/list under <code>employeeRest</code> / <code>Api\NonEbClaimApiController</code> — different validation and flows.</li>
|
||||
</ul>
|
||||
638
app/Views/docs/non-eb-opportunities.php
Normal file
638
app/Views/docs/non-eb-opportunities.php
Normal file
@ -0,0 +1,638 @@
|
||||
<?php
|
||||
/**
|
||||
* Non-EB Opportunities — content only
|
||||
* app/Views/docs/non-eb-opportunities.php
|
||||
*
|
||||
* Scope: list-view flow only — create/edit form, then table actions on /leads/list.
|
||||
* Sources:
|
||||
* - app/Views/leads_list.php (Non-EB action buttons)
|
||||
* - app/Views/leads_non_eb.php (add/edit form)
|
||||
* - app/Views/leads_form_handler.php
|
||||
* - app/Controllers/LeadsController.php (line ~4793+ and createLead / sendMail paths)
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
This page documents the <strong>Non-EB Opportunities</strong> workflow from the
|
||||
<strong>Opportunities list</strong> (<code>/leads/list</code>). A user creates a Non-EB opportunity,
|
||||
then progresses through RFQ → QCR → mail actions → placement — all from the list row action menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Non-EB rows are identified by <code>leads.lead_form_type = 2</code> (EB = <code>1</code>).
|
||||
RFQ and QCR are managed in <strong>Google Sheets</strong>; sheet IDs are stored in
|
||||
<code>leads.misc</code> JSON.
|
||||
</p>
|
||||
|
||||
<h2 id="overview">End-to-end flow</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["/leads/list — Add → Non-EB"] --> B["Add form leads_non_eb.php"]
|
||||
B --> C["POST /leads/create"]
|
||||
C --> D["status: queued"]
|
||||
D --> E["Action: RFQ"]
|
||||
E --> F["GET /leads/createRfqSheet"]
|
||||
F --> G["status: rfq_created"]
|
||||
G --> H["Action: QCR"]
|
||||
H --> I["GET /leads/createQcrSheet"]
|
||||
I --> J["status: qcr_created"]
|
||||
J --> K["Action: Send Internal Mail"]
|
||||
K --> L["POST /leads/sendMail internal"]
|
||||
L --> M["Action: Send Insurer Mail"]
|
||||
M --> N["POST /leads/sendMail insurer → rfq_sent"]
|
||||
N --> O["Action: Send Client Mail"]
|
||||
O --> P["POST /leads/sendMail client → qcr_sent"]
|
||||
P --> Q["Action: Placement"]
|
||||
Q --> R["POST /leads/sendMail placement → won"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Typical sequence from the list:</strong></p>
|
||||
<ol>
|
||||
<li><strong>Create</strong> opportunity (form submit) → <code>queued</code></li>
|
||||
<li><strong>RFQ</strong> — create/open Google Sheet → <code>rfq_created</code></li>
|
||||
<li><strong>QCR</strong> — copy RFQ sheet → <code>qcr_created</code></li>
|
||||
<li><strong>Send Internal Mail</strong> — team mail with RFQ or QCR attachment (by current status)</li>
|
||||
<li><strong>Send Insurer Mail</strong> — RFQ sheet attached → <code>rfq_sent</code></li>
|
||||
<li><strong>Send Client Mail</strong> — QCR sheet attached → <code>qcr_sent</code></li>
|
||||
<li><strong>Placement</strong> — placement sheet + policy/payment fields → <code>won</code></li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
<strong>Edit</strong> is available at any stage from the same action menu and reuses the add form
|
||||
with pre-filled data (<code>getLeadNonEB</code> + <code>POST /leads/create</code> with lead id).
|
||||
</p>
|
||||
|
||||
<h2 id="key-files">Key files</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>File</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>List + action dropdown</td><td><code>app/Views/leads_list.php</code></td></tr>
|
||||
<tr><td>Non-EB add/edit form</td><td><code>app/Views/leads_non_eb.php</code></td></tr>
|
||||
<tr><td>EB vs Non-EB form include</td><td><code>app/Views/leads_form_handler.php</code></td></tr>
|
||||
<tr><td>All backend logic</td><td><code>app/Controllers/LeadsController.php</code></td></tr>
|
||||
<tr><td>Google Sheets</td><td><code>GoogleSheetLib</code>, <code>Config\RfqConfig</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="google-sheet-config">Google Sheet config</h2>
|
||||
|
||||
<p>
|
||||
Non-EB RFQ, QCR, and Placement sheets are <strong>Google Drive files</strong> created at runtime by
|
||||
<code>GoogleSheetLib</code> using a service account. Only <strong>RFQ</strong> needs a pre-configured
|
||||
template per product; QCR and Placement are copies of the lead’s RFQ/QCR sheets.
|
||||
</p>
|
||||
|
||||
<h3 id="gsheet-app-drive-flow">App ↔ Google Drive flow</h3>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TB
|
||||
subgraph setup ["One-time / per product setup"]
|
||||
T["Drive: master RFQ template per product"]
|
||||
PT["policy_type.misc.rfq_template_sheet_id"]
|
||||
ENV[".env RFQ_PARENT_FOLDER_ID"]
|
||||
SA["Service account JSON + share folder/templates"]
|
||||
end
|
||||
|
||||
subgraph rfq ["RFQ — createRfqSheet"]
|
||||
R1["Read template ID from policy_type.misc"]
|
||||
R1 --> C1["Drive copyTemplate → parent folder"]
|
||||
C1 --> F1["Sheets find/replace placeholders"]
|
||||
F1 --> P1["Drive applyPermissions viewers"]
|
||||
P1 --> PR1["Sheets applyProtections"]
|
||||
PR1 --> S1["Save leads.misc.rfq_sheet_id"]
|
||||
end
|
||||
|
||||
subgraph qcr ["QCR — createQcrSheet"]
|
||||
S1 --> C2["Drive copyTemplate from rfq_sheet_id"]
|
||||
C2 --> P2["applyPermissions viewers"]
|
||||
P2 --> S2["Save leads.misc.qcr_sheet_id"]
|
||||
end
|
||||
|
||||
subgraph placement ["Placement — createAndDownloadPlacementSheet"]
|
||||
S2 --> C3["Drive copyTemplate from qcr_sheet_id"]
|
||||
C3 --> P3["applyPermissions viewers"]
|
||||
P3 --> S3["Save leads.misc.placement_sheet_id"]
|
||||
S3 --> X1["Drive export .xlsx → mail attachment"]
|
||||
end
|
||||
|
||||
subgraph mail ["Mail — downloadFileFromGoogleSheet"]
|
||||
S1 --> X2["Export RFQ or QCR as Excel"]
|
||||
S2 --> X2
|
||||
end
|
||||
|
||||
PT --> R1
|
||||
ENV --> C1
|
||||
T --> PT
|
||||
SA --> C1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Library:</strong> <code>app/Libraries/GoogleSheetLib.php</code> — auth via service account JSON at
|
||||
<code>{project-root}/nhance-ee8d1-e3c5269b1ec7.json</code>, scopes <code>DRIVE</code> + <code>SPREADSHEETS</code>.</p>
|
||||
|
||||
<h3 id="gsheet-prerequisites">What must be done before RFQ / QCR</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>Before</th><th>Why</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Google service account JSON present; account email shared on template folder + each template sheet</td>
|
||||
<td><code>GoogleSheetLib</code> cannot copy or edit without Drive access</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><code>RFQ_PARENT_FOLDER_ID</code> set in <code>.env</code></td>
|
||||
<td>Target folder for every copied RFQ/QCR/Placement file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Master RFQ Google Sheet template created per product (Fire, Marine, GPA, etc.) with placeholder tokens</td>
|
||||
<td>RFQ copy source — one template per <code>policy_type</code> row</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><code>policy_type.misc</code> JSON updated with <code>rfq_template_sheet_id</code> for that product</td>
|
||||
<td><code>createRfqSheet()</code> reads this; fails with “RFQ template not found” if missing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Non-EB opportunity created with correct <code>policy_type_id</code> and <code>rfq_qcr_viewers</code> emails</td>
|
||||
<td>Lead must exist; viewers become sheet editors after copy</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Before QCR:</strong> RFQ action completed (<code>leads.misc.rfq_sheet_id</code> set)</td>
|
||||
<td>QCR copies the lead’s RFQ sheet, not the policy template</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><strong>Before Placement mail:</strong> QCR action completed (<code>leads.misc.qcr_sheet_id</code> set)</td>
|
||||
<td>Placement copies the QCR sheet</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="gsheet-product-config">Where to configure sheet ID per product</h3>
|
||||
|
||||
<p>
|
||||
Template sheet IDs are stored <strong>per product</strong> on the <code>policy_type</code> table —
|
||||
one row per product (Fire, Marine, Burglary, etc.). The lead’s selected
|
||||
<code>policy_type_id</code> determines which template is copied when RFQ is clicked.
|
||||
</p>
|
||||
|
||||
<p><strong>Database column:</strong> <code>policy_type.misc</code> (JSON text)</p>
|
||||
|
||||
<pre><code>{
|
||||
"rfq_template_sheet_id": "1GXNDNXoWriClb5HCqPYd2GAY1T8aie_Hos0yAOoTaC0"
|
||||
}</code></pre>
|
||||
|
||||
<p>Example — set or update for policy type id <code>12</code>:</p>
|
||||
|
||||
<pre><code>UPDATE policy_type
|
||||
SET misc = JSON_SET(COALESCE(misc, '{}'), '$.rfq_template_sheet_id', 'YOUR_GOOGLE_DRIVE_FILE_ID')
|
||||
WHERE id = 12;</code></pre>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
There is no admin UI field for <code>rfq_template_sheet_id</code> today — configure via DB (or extend
|
||||
<code>MasterController::editPolicyType</code> / <code>policy_type_onboarding</code> if you add a form field).
|
||||
Model allow-list: <code>app/Models/PolicyTypeModel.php</code> includes <code>misc</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>How to find a template file ID:</strong></p>
|
||||
<ul>
|
||||
<li>From the Google Sheet URL: <code>https://docs.google.com/spreadsheets/d/<strong>{FILE_ID}</strong>/edit</code></li>
|
||||
<li>CLI — list all sheets in the RFQ parent folder:
|
||||
<code>php public/index.php cli/list-sheet-folder-files {RFQ_PARENT_FOLDER_ID}</code>
|
||||
→ writes <code>sheetid.json</code> with <code>name</code> + <code>sheetId</code> pairs
|
||||
(<code>GoogleSheetController::listFolderSheetFilesCli</code>)</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>QCR and Placement:</strong> no separate template ID per product. They always copy from the lead’s existing sheets:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Stage</th><th>Copy source</th><th>Stored on lead</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>RFQ</td><td><code>policy_type.misc.rfq_template_sheet_id</code></td><td><code>leads.misc.rfq_sheet_id</code></td></tr>
|
||||
<tr><td>QCR</td><td><code>leads.misc.rfq_sheet_id</code></td><td><code>leads.misc.qcr_sheet_id</code></td></tr>
|
||||
<tr><td>Placement</td><td><code>leads.misc.qcr_sheet_id</code> (filename: QCR → Placement)</td><td><code>leads.misc.placement_sheet_id</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="gsheet-app-config">App-level config files</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Setting</th><th>Location</th><th>Purpose</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>RFQ_PARENT_FOLDER_ID</code></td>
|
||||
<td><code>.env</code> → <code>Config\RfqConfig::$rfqParentFolderId</code></td>
|
||||
<td>Google Drive folder where copied RFQ/QCR/Placement files are created</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rfqPlaceholders</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Maps template tokens like <code>{{INSURED_NAME}}</code> to lead field keys filled on RFQ create</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rfqClaimsPlaceholder</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Default <code>{{CLAIMS_DETAILS}}</code> — multi-row claims table from <code>fin_years_claims</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>protections</code></td>
|
||||
<td><code>app/Config/RfqConfig.php</code></td>
|
||||
<td>Locked cell ranges on new RFQ sheets only (e.g. <code>RFQ Page!B12:C12</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Service account key</td>
|
||||
<td><code>nhance-ee8d1-e3c5269b1ec7.json</code> (project root)</td>
|
||||
<td>Google API authentication for all sheet operations</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Placeholder tokens to embed in each product’s RFQ master template:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Token in template</th><th>Filled from</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>{{INSURED_NAME}}</code></td><td>Client name / short name</td></tr>
|
||||
<tr><td><code>{{COMMUNICATION_ADDRESS}}</code></td><td>Client or custom field address</td></tr>
|
||||
<tr><td><code>{{GST}}</code> / <code>{{PAN}}</code></td><td>Lead GST / PAN</td></tr>
|
||||
<tr><td><code>{{POLICY_PERIOD}}</code></td><td>Policy start – end dates</td></tr>
|
||||
<tr><td><code>{{OPPORTUNITY_TYPE}}</code></td><td>Fresh / Renewal label</td></tr>
|
||||
<tr><td><code>{{RISK_LOCATION}}</code> / <code>{{OCCUPANCY}}</code></td><td>Custom policy-type fields</td></tr>
|
||||
<tr><td><code>{{CLAIMS_DETAILS}}</code></td><td>Claim history table (renewal leads)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
After RFQ copy, editors are granted from the lead’s <code>rfq_qcr_viewers</code> JSON email list (selected on the create/edit form).
|
||||
The same list is applied to QCR and Placement copies.
|
||||
</p>
|
||||
|
||||
<h2 id="create-edit">Step 1 — Create opportunity</h2>
|
||||
|
||||
<h3 id="create-ui">From the list</h3>
|
||||
<ol>
|
||||
<li>Open <code>/leads/list</code> → click <strong>Add</strong>.</li>
|
||||
<li>Modal <strong>Select Opportunity Type</strong> → choose <strong>Non-EB</strong> (<code>lead_form_type=2</code>).</li>
|
||||
<li>Redirect: <code>GET /util/getLeadNonEB/2/0</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="create-form">Form (<code>leads_non_eb.php</code>)</h3>
|
||||
<ul>
|
||||
<li>Sections: Opportunity Details, Client Information, Branch Information, Policy Details, Sales & Assignment.</li>
|
||||
<li>Policy types limited to <code>allocg</code> = <strong>Non-EB</strong> or <strong>Marine</strong>.</li>
|
||||
<li>Policy type change → <code>GET /util/getPolicyTypeFields</code> → dynamic fields in <code>#appendArea</code>.</li>
|
||||
<li>Renewal types may show claim history rows → stored as <code>fin_years_claims</code> JSON.</li>
|
||||
<li><code>rfq_qcr_viewers</code> (emails) become Google Sheet editors later.</li>
|
||||
<li>Submit → AJAX <code>POST /leads/create</code> with <code>lead_form_type=2</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="create-backend">Controller: <code>createLead()</code></h3>
|
||||
<ul>
|
||||
<li><code>prepareLeadData()</code> → <code>prepareSingleLeadData()</code> (one lead row; EB uses multi-row).</li>
|
||||
<li>Non-EB validation: policy type, DOC/DOE, claim-history rows when <code>claim_history=1</code>.</li>
|
||||
<li><code>insertNewLead()</code> — no demography background job (EB-only).</li>
|
||||
<li>Initial status: <code>queued</code> (In-Queued).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="edit-flow">Edit flow</h2>
|
||||
|
||||
<p>From the list action menu → <strong>Edit</strong> (available for both EB and Non-EB):</p>
|
||||
|
||||
<ol>
|
||||
<li>JS: <code>getLeadsDataForEdit(lead_id, lead_form_type, actual_lead_id)</code></li>
|
||||
<li>Redirect: <code>GET /util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code></li>
|
||||
<li>Controller: <code>getLeadNonEB($type, $actual_lead_id, $id)</code> (~line 4818)
|
||||
<ul>
|
||||
<li>Loads master data (issuer, policy types, sales team, etc.)</li>
|
||||
<li>When <code>$id</code> present: fetches <code>lead_edit_data</code>, files, custom fields, date formatting</li>
|
||||
<li>Builds dynamic policy HTML via <code>generateViewPageHtml()</code></li>
|
||||
<li>Renewal Non-EB: renders <code>rfq/claims_details_non_eb</code> into <code>claims_details_html</code></li>
|
||||
<li>Returns layout with <code>leads_form_handler</code> → includes <code>leads_non_eb.php</code> when <code>selected_lead_type != 1</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Form pre-fills client, branch, policy, files, viewers, status, lost reason, etc.</li>
|
||||
<li>Submit same endpoint: <code>POST /leads/create</code> with hidden <code>id</code> → <code>updateOldLead()</code></li>
|
||||
</ol>
|
||||
|
||||
<h2 id="list-actions">Steps 2–7 — List table actions (Non-EB)</h2>
|
||||
|
||||
<p>
|
||||
When <code>lead_form_type === 2</code>, the row action menu in
|
||||
<code>app/Views/leads_list.php</code> (lines ~297–343) uses modal/AJAX flows instead of
|
||||
navigating to <code>/rfq/list/{id}/1|2</code> (EB behaviour).
|
||||
</p>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong>Menu order on screen</strong> (after opportunity is created): Edit → RFQ → QCR →
|
||||
Send Internal Mail → Send Insurer Mail → Send Client Mail → Placement → Email History.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="action-map">Action → view handler → controller endpoint</h3>
|
||||
|
||||
<div class="docs-table-wrap docs-table-wrap--fluid">
|
||||
<table class="docs-action-map">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Action</th>
|
||||
<th>Visible when</th>
|
||||
<th>View (JS) / handler class</th>
|
||||
<th>Controller endpoint(s)</th>
|
||||
<th>Status after</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>—</td>
|
||||
<td><strong>Edit</strong></td>
|
||||
<td>Always (EB + Non-EB)</td>
|
||||
<td><code>getLeadsDataForEdit()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/util/getLeadNonEB/{lead_form_type}/{actual_lead_id}/{lead_id}</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/create</code> (update when <code>id</code> posted)
|
||||
</td>
|
||||
<td>User-selected on form</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><strong>RFQ</strong></td>
|
||||
<td><code>lead_form_type === 2</code> only</td>
|
||||
<td><code>.btnRfqSheetList</code> → <code>createRfqSheetFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/createRfqSheet?lead_id={id}</code><br>
|
||||
<code>LeadsController::createRfqSheet()</code> — opens Google Sheet URL in new tab
|
||||
</td>
|
||||
<td><code>rfq_created</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><strong>QCR</strong></td>
|
||||
<td>
|
||||
Non-EB; status not <code>queued</code> or <code>rfq_created</code>;
|
||||
role <code>1, 5, 2, 3</code> or Business Support team
|
||||
</td>
|
||||
<td><code>.btnQcrSheetList</code> → <code>createQcrSheetFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/createQcrSheet?lead_id={id}</code><br>
|
||||
<code>LeadsController::createQcrSheet()</code>
|
||||
</td>
|
||||
<td><code>qcr_created</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><strong>Send Internal Mail</strong></td>
|
||||
<td>Non-EB; <code>status != queued</code></td>
|
||||
<td><code>.btnInternalMailList</code> → <code>openInternalMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=rfq</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=internal</code>
|
||||
</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><strong>Send Insurer Mail</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnInsurerMailList</code> → <code>openInsurerMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=rfq</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=insurer</code> (RFQ sheet attach)
|
||||
</td>
|
||||
<td><code>rfq_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><strong>Send Client Mail</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnClientMailList</code> → <code>openClientMailFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=qcr</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=client</code> (QCR sheet attach)
|
||||
</td>
|
||||
<td><code>qcr_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Placement</strong></td>
|
||||
<td>Same as internal mail</td>
|
||||
<td><code>.btnPlacementList</code> → <code>openPlacementFromList()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/rfq/placementData/{id}</code><br>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/leads/mailTemplate?lead_id={id}&template_type=placement</code><br>
|
||||
<span class="badge post">POST</span>
|
||||
<code>/leads/sendMail</code> — <code>recipient_type=placement</code>
|
||||
</td>
|
||||
<td><code>won</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>—</td>
|
||||
<td><strong>Email History</strong></td>
|
||||
<td>Always (EB + Non-EB)</td>
|
||||
<td><code>.btnHistory</code> → <code>getLeadsDataForMailHistory()</code></td>
|
||||
<td>
|
||||
<span class="badge get">GET</span>
|
||||
<code>/util/getLeadEmailHistory/{id}</code> → <code>getLeadEmailHistory()</code>
|
||||
</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><strong>EB contrast</strong> (same menu, <code>lead_form_type === 1</code>): RFQ/QCR are links to
|
||||
<code>/rfq/list/{id}/1</code> and <code>/rfq/list/{id}/2</code>; internal/insurer/client/placement mail items are not shown.</p>
|
||||
|
||||
<p><strong>Shared mail modal helpers</strong> (all in <code>leads_list.php</code>):</p>
|
||||
<ul>
|
||||
<li><code>POST /leads/uploadLeadAttachment</code> — optional extra files (<code>lead_id</code>, <code>docs_name</code>, file)</li>
|
||||
<li><code>constructURL_ForInternalMailSend()</code>, insurer/client via <code>constructURL_ForInsurerOrClientMailSend()</code>, placement via <code>constructURL_ForPlacementMailSend()</code></li>
|
||||
<li>External CC disclaimer modal (<code>#external_cc_disclaimer_modal</code>) before client/placement send to non-user emails</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="step-rfq">Step 2 — RFQ (<code>createRfqSheet()</code>)</h2>
|
||||
|
||||
<ol>
|
||||
<li>Load lead + <code>policy_type.misc.rfq_template_sheet_id</code>.</li>
|
||||
<li>If <code>misc.rfq_sheet_id</code> exists → return existing Google Sheet URL.</li>
|
||||
<li>Copy template via <code>GoogleSheetLib::copyTemplate()</code> into <code>RfqConfig::rfqParentFolderId</code>.</li>
|
||||
<li>Fill placeholders (<code>buildRfqPlaceholderData()</code>): client, GST, PAN, policy period, claims table.</li>
|
||||
<li>Grant editors from <code>leads.rfq_qcr_viewers</code> email JSON.</li>
|
||||
<li>Save <code>misc.rfq_sheet_id</code>; set <code>status = rfq_created</code>.</li>
|
||||
<li>UI opens sheet in a new browser tab.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="step-qcr">Step 3 — QCR (<code>createQcrSheet()</code>)</h2>
|
||||
|
||||
<ol>
|
||||
<li>Requires <code>misc.rfq_sheet_id</code> — returns 400 if RFQ not created yet.</li>
|
||||
<li>If <code>misc.qcr_sheet_id</code> exists → return existing URL.</li>
|
||||
<li>Copy the RFQ sheet (not the policy template) with a QCR filename.</li>
|
||||
<li>Same editor permissions from <code>rfq_qcr_viewers</code>.</li>
|
||||
<li>Save <code>misc.qcr_sheet_id</code>; set <code>status = qcr_created</code>.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="step-mails">Steps 4–6 — Mail actions</h2>
|
||||
|
||||
<h3 id="mail-template">Load template — <code>getLeadMailTemplate()</code></h3>
|
||||
<p>
|
||||
Called before each mail modal opens. Query params:
|
||||
<code>lead_id</code> + <code>template_type</code> (<code>rfq</code> | <code>qcr</code> | <code>placement</code>).
|
||||
Returns subject, HTML body, and attachment checkbox HTML from <code>lead_files</code>.
|
||||
</p>
|
||||
|
||||
<h3 id="send-mail">Send — <code>sendMailWithAttachement()</code></h3>
|
||||
|
||||
<p>For Non-EB (<code>lead_form_type == 2</code>), the Excel attachment comes from Google Sheets:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>recipient_type</th><th>Sheet used</th><th>Status after send</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>internal</code></td>
|
||||
<td>QCR if status is <code>qcr_created</code>/<code>qcr_sent</code>, else RFQ</td>
|
||||
<td>Unchanged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>insurer</code></td>
|
||||
<td><code>misc.rfq_sheet_id</code></td>
|
||||
<td><code>rfq_sent</code> (unless already past that stage)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>client</code></td>
|
||||
<td><code>misc.qcr_sheet_id</code></td>
|
||||
<td><code>qcr_sent</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>placement</code></td>
|
||||
<td><code>createAndDownloadPlacementSheet()</code> — copies QCR sheet</td>
|
||||
<td><code>won</code> + saves placement/payment/installment fields</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Implementation detail: <code>downloadFileFromGoogleSheet()</code> exports the chosen sheet to a temp
|
||||
<code>.xlsx</code> under <code>writable/tmp/</code> before <code>MailHelper::send_email()</code>.</p>
|
||||
|
||||
<h2 id="step-placement">Step 7 — Placement</h2>
|
||||
|
||||
<p>Opened from list action → <code>openPlacementFromList(leadId)</code>:</p>
|
||||
<ol>
|
||||
<li><code>GET /rfq/placementData/{id}</code> — pre-fills policy dates, premium, CD, installments, contacts.</li>
|
||||
<li><code>GET /leads/mailTemplate?template_type=placement</code> — subject/body/attachments.</li>
|
||||
<li>User fills placement modal: dates, premium/CD/total, installment rows, To/CC, optional external CC.</li>
|
||||
<li><code>POST /leads/sendMail</code> with <code>recipient_type=placement</code>.</li>
|
||||
<li>Backend copies QCR → placement Google Sheet, attaches Excel, updates lead to <code>won</code>,
|
||||
persists <code>placement_date</code>, <code>premium_amount</code>, <code>cd_amount</code>, installments, etc.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="status-lifecycle">Status lifecycle</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Status</th><th>Label</th><th>Set by</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>queued</code></td><td>In-Queued</td><td>Create form (default)</td></tr>
|
||||
<tr><td><code>rfq_created</code></td><td>RFQ Created</td><td><code>createRfqSheet()</code></td></tr>
|
||||
<tr><td><code>rfq_sent</code></td><td>RFQ Sent</td><td>Insurer mail</td></tr>
|
||||
<tr><td><code>qcr_created</code></td><td>QCR Created</td><td><code>createQcrSheet()</code></td></tr>
|
||||
<tr><td><code>qcr_sent</code></td><td>QCR Sent</td><td>Client mail</td></tr>
|
||||
<tr><td><code>won</code></td><td>Won</td><td>Placement mail</td></tr>
|
||||
<tr><td><code>lost</code></td><td>Lost</td><td>User sets on edit form + lost reason</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="misc-json">leads.misc JSON</h2>
|
||||
|
||||
<pre><code>{
|
||||
"rfq_sheet_id": "…",
|
||||
"qcr_sheet_id": "…",
|
||||
"placement_sheet_id": "…"
|
||||
}</code></pre>
|
||||
|
||||
<h2 id="controller-reference">Controller reference (list flow)</h2>
|
||||
|
||||
<p>Methods in <code>LeadsController.php</code> used by the list Non-EB flow:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Method</th><th>Triggered from</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>createLead()</code></td><td>Form submit (create + edit)</td></tr>
|
||||
<tr><td><code>getLeadNonEB()</code></td><td>Add / Edit navigation</td></tr>
|
||||
<tr><td><code>getPolicyTypeFields()</code></td><td>Policy type change on form</td></tr>
|
||||
<tr><td><code>createRfqSheet()</code></td><td>RFQ action</td></tr>
|
||||
<tr><td><code>createQcrSheet()</code></td><td>QCR action</td></tr>
|
||||
<tr><td><code>getLeadMailTemplate()</code></td><td>All mail modals</td></tr>
|
||||
<tr><td><code>getPlacementData()</code></td><td>Placement modal pre-fill</td></tr>
|
||||
<tr><td><code>uploadLeadAttachment()</code></td><td>Attachment upload in mail modals</td></tr>
|
||||
<tr><td><code>sendMailWithAttachement()</code></td><td>All mail sends + placement</td></tr>
|
||||
<tr><td><code>downloadFileFromGoogleSheet()</code></td><td>Mail attachment (internal/insurer/client)</td></tr>
|
||||
<tr><td><code>createAndDownloadPlacementSheet()</code></td><td>Placement mail attachment</td></tr>
|
||||
<tr><td><code>getLeadEmailHistory()</code></td><td>Email History modal</td></tr>
|
||||
<tr><td><code>buildRfqPlaceholderData()</code></td><td>RFQ sheet placeholder fill (helper)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer checklist</h2>
|
||||
|
||||
<ol>
|
||||
<li>Complete <a href="#google-sheet-config">Google Sheet config</a> for each Non-EB/Marine <code>policy_type</code> before first RFQ.</li>
|
||||
<li>Set <code>policy_type.misc.rfq_template_sheet_id</code> per product (see <a href="#gsheet-product-config">sheet ID per product</a>).</li>
|
||||
<li>Configure <code>RFQ_PARENT_FOLDER_ID</code> in <code>.env</code> and share folder/templates with the service account.</li>
|
||||
<li>Ensure placeholders in master templates match <code>Config\RfqConfig::$rfqPlaceholders</code>.</li>
|
||||
<li>Gate new list actions with <code>$isNonEb</code> in <code>leads_list.php</code>.</li>
|
||||
<li>Respect sheet order: RFQ → QCR → mails → placement.</li>
|
||||
<li>QCR button: roles <code>[1, 5, 2, 3]</code> or <code>BUSINESS_SUPPORT_TEAM_ID</code> in user team.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>QCR before RFQ:</strong> <code>createQcrSheet</code> fails if <code>rfq_sheet_id</code> is missing.</li>
|
||||
<li><strong>Queued status:</strong> Mail and placement actions are hidden until status moves past <code>queued</code>.</li>
|
||||
<li><strong>Insurer mail needs RFQ sheet;</strong> client mail needs QCR sheet — create sheets before sending.</li>
|
||||
<li><strong>Internal mail attachment</strong> picks RFQ or QCR based on current lead status.</li>
|
||||
<li><strong>Edit vs create:</strong> same <code>POST /leads/create</code>; presence of hidden <code>id</code> triggers update.</li>
|
||||
</ul>
|
||||
@ -39,6 +39,7 @@ $base = base_url();
|
||||
/* ─── TOKENS ─────────────────────────────── */
|
||||
:root {
|
||||
--sidebar-w : 260px;
|
||||
--toc-w : 200px;
|
||||
--topbar-h : 56px;
|
||||
--bg : #ffffff;
|
||||
--bg2 : #f7f8fa;
|
||||
@ -143,6 +144,8 @@ $base = base_url();
|
||||
/* ─── LAYOUT WRAPPER ─────────────────────── */
|
||||
.docs-layout {
|
||||
display : flex;
|
||||
width : 100%;
|
||||
align-items: flex-start;
|
||||
margin-top: var(--topbar-h);
|
||||
min-height: calc(100vh - var(--topbar-h));
|
||||
}
|
||||
@ -208,6 +211,15 @@ $base = base_url();
|
||||
.callout.success { background: #f0fdf4; border-color: #4ade80; color: #15803d; }
|
||||
|
||||
/* ─── TABLES ─────────────────────────────── */
|
||||
.docs-table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
margin: 20px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.docs-table-wrap table { margin: 0; min-width: 640px; }
|
||||
.docs-table-wrap--fluid table { min-width: 0; width: 100%; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 13.5px; }
|
||||
th {
|
||||
background : var(--bg2);
|
||||
|
||||
@ -27,10 +27,11 @@ $toc = $toc ?? [];
|
||||
<style>
|
||||
/* ─── MAIN CONTENT ───────────────────────── */
|
||||
.docs-main {
|
||||
flex : 1;
|
||||
max-width: 760px;
|
||||
padding : 48px 56px 80px;
|
||||
min-width: 0;
|
||||
flex : 1 1 auto;
|
||||
min-width : 0;
|
||||
max-width : none;
|
||||
width : 0; /* grow to fill space between sidebar and right TOC */
|
||||
padding : 48px 40px 80px 48px;
|
||||
}
|
||||
|
||||
.docs-main__breadcrumb {
|
||||
@ -62,13 +63,18 @@ $toc = $toc ?? [];
|
||||
|
||||
/* ─── RIGHT TOC ──────────────────────────── */
|
||||
.docs-toc {
|
||||
width : 200px;
|
||||
min-width: 200px;
|
||||
padding : 56px 20px 0;
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
align-self: flex-start;
|
||||
width : var(--toc-w);
|
||||
min-width : var(--toc-w);
|
||||
max-width : var(--toc-w);
|
||||
padding : 56px 20px 48px 16px;
|
||||
border-left: 1px solid var(--border);
|
||||
background : var(--bg);
|
||||
position : sticky;
|
||||
top : calc(var(--topbar-h) + 32px);
|
||||
height : fit-content;
|
||||
max-height : calc(100vh - var(--topbar-h) - 48px);
|
||||
overflow-y : auto;
|
||||
align-self : flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -109,6 +115,14 @@ $toc = $toc ?? [];
|
||||
border-radius : 999px;
|
||||
padding : 3px 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.docs-toc { display: none; }
|
||||
.docs-main {
|
||||
width: auto;
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
|
||||
259
app/Views/docs/si-enhancement.php
Normal file
259
app/Views/docs/si-enhancement.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* SI Enhancement (employee Excel upload) — content only
|
||||
* app/Views/docs/si-enhancement.php
|
||||
*
|
||||
* Based on:
|
||||
* - app/Views/employee_upload.php
|
||||
* - app/Controllers/EmployeeServiceController.php
|
||||
* (excelFileFormatValidation, excelFileDataValidation, employeesSIEnhanceProcess)
|
||||
* - app/Controllers/EmployeeController.php (employeesUplodWithEvents)
|
||||
* - app/Controllers/JobWorker.php
|
||||
*/
|
||||
?>
|
||||
|
||||
<p>
|
||||
<strong>SI Enhancement</strong> increases sum insured for active members on a policy via Excel upload
|
||||
(<code>files.action = si_enhancement</code>). The process recalculates premium from slab/rack rates and
|
||||
creates <strong>pending SI endorsements</strong> on <code>employee_polices</code> — SI and premium are not
|
||||
updated in place until endorsements are applied downstream.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Core logic: <code>EmployeeServiceController::employeesSIEnhanceProcess</code>.
|
||||
Premium math uses <code>transform_si_excel_row_to_calculatable_format</code> and
|
||||
<code>premium_calculation_manager</code> (see
|
||||
<a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a>).
|
||||
</p>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart LR
|
||||
A["Upload Excel action si_enhancement"] --> B["excelFileFormatValidation"]
|
||||
B --> C["excelFileDataValidation"]
|
||||
C --> D["employeesSIEnhanceProcess job"]
|
||||
D --> E["Recalc premium plus pending SI endorsements"]
|
||||
B -->|errors| F["files.status failed"]
|
||||
C -->|errors| F
|
||||
D -->|loop done| G["files.status success"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>In short:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Step 1 — Format:</strong> 5 columns (A–E); <strong>Augmented SI</strong> validated against slabs (<code>check_si</code>).</li>
|
||||
<li><strong>Step 2 — Data:</strong> Member must exist on active policy; code <strong>10</strong> if not found.</li>
|
||||
<li><strong>Step 3 — SI enhance:</strong> Picks rack rate by relationship, recalculates premium, inserts 3 pending endorsements per member.</li>
|
||||
<li>One Excel row = one member SI change (not whole-family in one row).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="key-files-routes">Key files and routes</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Area</th><th>Location</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UI</td>
|
||||
<td><code>app/Views/employee_upload.php</code> — action <strong>SI Enhancement</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td><code>EmployeeController::employeesUplodWithEvents</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validation</td>
|
||||
<td><code>excelFileFormatValidation</code>, <code>excelFileDataValidation</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SI process</td>
|
||||
<td><code>employeesSIEnhanceProcess</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Onboard SI path</td>
|
||||
<td><code>employeesSIEnhanceProcessWhileOnbboard</code> — SI during dependent addition onboard (not Excel)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Job</td>
|
||||
<td><code>employeesSIEnhanceProcess</code> in <code>JobWorker.php</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Helpers</td>
|
||||
<td><code>excel_util_helper.php</code> — <code>transform_si_excel_row_to_calculatable_format</code>, <code>premium_calculation_manager</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Routes</strong> (group <code>/employee</code>, <code>authMVC</code>):</p>
|
||||
<ul>
|
||||
<li><code>GET /employee/upload</code> — upload screen</li>
|
||||
<li><code>POST /employee/upload</code> — <code>upload-action-type=si_enhancement</code></li>
|
||||
<li><code>GET /employee/excel_error/{file_id}</code> — validation errors</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout info">
|
||||
<span>i</span>
|
||||
<div>
|
||||
<strong><code>files.policy_id</code></strong> is the client policy id. Slab rates load via
|
||||
<code>getPolicySlabRatesForEmpOnboard(policy_id, client_id)</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="sync-vs-jobs">Sync vs background jobs</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Step</th><th>< 1 MB</th><th>≥ 1 MB</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Format validation</td><td>Inline on upload</td><td>Job <code>excelFileFormatValidation</code></td></tr>
|
||||
<tr><td>Data validation</td><td>Job <code>excelFileDataValidation</code></td><td>Same</td></tr>
|
||||
<tr><td>SI enhance</td><td>Job <code>employeesSIEnhanceProcess</code></td><td>Same</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="format-validation">Step 1: excelFileFormatValidation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Uses <code>$si_enhance_excel_columns</code> — <strong>5 columns (A–E)</strong>.</li>
|
||||
<li>Action code <code>SI</code> for mandatory rules.</li>
|
||||
<li><strong>Augmented SI</strong> (D): custom <code>check_si</code> against policy slabs (same helper as inception SI column).</li>
|
||||
<li><strong>Date of SI Enhancement</strong> (E): <code>d-M-Y</code>.</li>
|
||||
<li>Policy must have slab/rack configuration or format step fails early (code 5).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="format-errors">Format error codes</h3>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Code</th><th>Meaning</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td>Mandatory missing</td></tr>
|
||||
<tr><td>2</td><td>Wrong format</td></tr>
|
||||
<tr><td>3</td><td>Not in allowed list</td></tr>
|
||||
<tr><td>4</td><td>Custom validation failed (e.g. invalid Augmented SI for slab)</td></tr>
|
||||
<tr><td>5</td><td>File / policy / slab problem</td></tr>
|
||||
<tr><td>6</td><td>Column headers wrong</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="data-validation">Step 2: excelFileDataValidation</h2>
|
||||
|
||||
<p><code>name_and_empid_check_in_db</code> for <code>si_enhancement</code>:</p>
|
||||
<ul>
|
||||
<li>Row must match active employee + active <code>employee_polices</code> on the policy.</li>
|
||||
<li>Not found → error code <strong>10</strong> (“Record Not found”).</li>
|
||||
</ul>
|
||||
|
||||
<p>On success → queues <code>employeesSIEnhanceProcess</code>.</p>
|
||||
|
||||
<h2 id="si-process">Step 3: employeesSIEnhanceProcess</h2>
|
||||
|
||||
<div class="mermaid-wrapper">
|
||||
<div class="mermaid">
|
||||
flowchart TD
|
||||
A["Read row Augmented SI and date"] --> B["Match active employee and policy"]
|
||||
B --> C{"Pending SI on basic_cover_si?"}
|
||||
C -->|Yes| D["Skip row log error"]
|
||||
C -->|No| E["Resolve rack rate by relationship"]
|
||||
E --> F["premium_calculation_manager"]
|
||||
F --> G["Insert 3 pending endorsements actions si"]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Per row:</strong></p>
|
||||
<ol>
|
||||
<li>Strip number formatting from Augmented SI (<code>removeNumberFormatting</code>).</li>
|
||||
<li>Load employee (emp code + name + branch) and active <code>employee_polices</code> row.</li>
|
||||
<li>Find applicable slab/rack rate from member <strong>relationship</strong> (Son/Daughter → childrens, parents, etc.).</li>
|
||||
<li>Build calculatable member payload via <code>transform_si_excel_row_to_calculatable_format</code> (uses family max age/count).</li>
|
||||
<li><code>premium_calculation_manager</code> → new premium for the augmented SI.</li>
|
||||
<li>Insert pending endorsements on <code>employee_polices</code> (<code>actions = si</code>):</li>
|
||||
</ol>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>field_name</th><th>new_value source</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>basic_cover_si</code></td><td>Excel Augmented SI (column D)</td></tr>
|
||||
<tr><td><code>premium</code></td><td>Recalculated premium from rack logic</td></tr>
|
||||
<tr><td><code>si_enhancement_date</code></td><td>Excel Date of SI Enhancement (column E)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
Sets <code>files.status = success</code> when the loop completes. Skipped rows (duplicate pending SI, missing member)
|
||||
are logged only — the file still succeeds.
|
||||
</p>
|
||||
|
||||
<h2 id="excel-columns">SI Enhancement Excel columns</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Col</th><th>Header</th><th>Required</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>A</td><td>S.No</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>B</td><td>EMP ID</td><td>Yes</td><td></td></tr>
|
||||
<tr><td>C</td><td>NAME OF EMP/DEP</td><td>Yes</td><td>Must match DB</td></tr>
|
||||
<tr><td>D</td><td>Augmented SI</td><td>Yes</td><td>New SI; validated via <code>check_si</code></td></tr>
|
||||
<tr><td>E</td><td>Date of SI Enhancement</td><td>Yes</td><td><code>d-M-Y</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="row-example">Row layout examples</h2>
|
||||
|
||||
<p><strong>Enhance Self SI</strong> — one row:</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Augmented SI</th><th>Date of SI Enhancement</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Raj Kumar</td><td>1000000</td><td>19-May-2026</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>Enhance spouse only</strong> — separate row (premium uses that member’s relationship for rack selection):</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>EMP ID</th><th>NAME</th><th>Augmented SI</th><th>Date of SI Enhancement</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>EMP001</td><td>Priya Kumar</td><td>500000</td><td>19-May-2026</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="developer-steps">Developer steps</h2>
|
||||
|
||||
<ol>
|
||||
<li>Confirm slab/rack rates exist for the client policy (same as inception).</li>
|
||||
<li>Upload with <code>upload-action-type=si_enhancement</code>; note <code>file_id</code>.</li>
|
||||
<li>On validation failure, check <code>/employee/excel_error/{file_id}</code> — codes <strong>4</strong> (SI/slab) or <strong>10</strong> (member missing).</li>
|
||||
<li>After success, query <code>emp_endorsement</code> where <code>file_id</code> = upload id, <code>actions = 'si'</code>, <code>status = 'pending'</code> — expect up to 3 rows per member (same <code>group_key</code>).</li>
|
||||
<li>Compare <code>new_value</code> on <code>basic_cover_si</code> and <code>premium</code> to Excel and rack expectations.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="pitfalls">Common pitfalls</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong>Slab / rack not configured</strong> — format step or premium calc fails; relationship must map to a non-zero rack key.</li>
|
||||
<li><strong>Augmented SI vs slab</strong> — <code>check_si</code> in step 1 must pass before the job runs.</li>
|
||||
<li><strong>Pending SI already exists</strong> — duplicate upload for same member skipped until prior endorsement cleared.</li>
|
||||
<li><strong>Per-member rows</strong> — enhancing whole family requires one row per member (unlike deletion Self = whole family).</li>
|
||||
<li><strong>File success vs rows</strong> — <code>files.status = success</code> does not guarantee every row created endorsements.</li>
|
||||
<li><strong>Not live SI update</strong> — <code>employee_polices.basic_cover_si</code> changes after endorsement processing.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Related:
|
||||
<a href="<?= base_url('docs/inception') ?>">Inception</a>,
|
||||
<a href="<?= base_url('docs/correction') ?>">Correction</a>,
|
||||
<a href="<?= base_url('docs/eb-rack-rate-calculation') ?>">EB rack rate calculation</a>.
|
||||
</p>
|
||||
Loading…
Reference in New Issue
Block a user