390 lines
16 KiB
PHP
390 lines
16 KiB
PHP
<?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>
|