nhance/app/Views/docs/visit-offboard.php
2026-05-18 12:28:19 +05:30

250 lines
9.2 KiB
PHP

<?php
/**
* Visit offboard - content only
* app/Views/docs/visit-offboard.php
*
* Based on:
* - app/Controllers/EmployeeController.php (visitOffBoard, updateVisitoffboardStatus)
* - app/Controllers/EmpDataServiceController.php (deletion / endorsement job enqueue)
* - app/Controllers/JobWorker.php
* - app/Config/Routes.php, app/Config/Acl.php
*/
?>
<p>
Visit offboard removes members from the Visit wellness side after they are
deleted or exited in Nhance. A background job calls the Visit delete-policy
API; on HTTP <code>200</code> and a JSON body where <code>message</code> is
<code>success</code>, Nhance appends <code>_DEL</code> to
<code>employee_polices.wellness_onboard</code> for the affected rows.
</p>
<div class="callout info">
<span>i</span>
<div>
<strong>Core files</strong>
API and DB update logic live in
<code>app/Controllers/EmployeeController.php</code>
(<code>visitOffBoard()</code>, <code>updateVisitoffboardStatus()</code>).
The insurer deletion flow <code>importDeletionUpdateEndorsementID()</code>
enqueues the job from
<code>app/Controllers/EmpDataServiceController.php</code>. The worker maps
the job name in <code>app/Controllers/JobWorker.php</code>.
</div>
</div>
<h2 id="overview">Overview</h2>
<div class="mermaid-wrapper">
<div class="mermaid">
flowchart TD
A[Deletion batch completes for insurer file] --> B[Jobs::addJob visitOffBoard]
B --> C[JobWorker runs EmployeeController::visitOffBoard]
C --> D[POST delete-policy-with-dependents]
D --> E{HTTP 200 and JSON message success}
E -->|Yes| F[updateVisitoffboardStatus]
F --> G[CONCAT wellness_onboard with _DEL]
E -->|No| H[Log failure, no DB marker]
</div>
</div>
<h2 id="when-it-is-queued">When it is queued</h2>
<p>
After <code>importDeletionUpdateEndorsementID()</code> applies policy and
endorsement updates from the deletion Excel, when
<code>$file['insurer_or_tpa'] == 'insurer'</code>, the controller enqueues
<code>visitOffBoard</code> alongside cash-deposit and BDS jobs. The payload
carries the list of <code>employee_polices.id</code> values processed in that
batch, the client policy number, and a fixed source string.
</p>
<pre><code class="language-php">$r = Jobs::addJob(['job_name' => 'visitOffBoard', 'payload' => [
'memberIds' => $employee_policy_table_primaryKey ?? [],
'policyNumber' => $policy_name['policy_no'] ?? null,
'source' => 'NHANCE',
]]);</code></pre>
<table>
<thead>
<tr><th>Payload key</th><th>Meaning</th></tr>
</thead>
<tbody>
<tr>
<td><code>memberIds</code></td>
<td>Array of <code>employee_polices.id</code> primary keys collected from the deletion Excel flow (<code>emp_policy_primarykey</code> per row).</td>
</tr>
<tr>
<td><code>policyNumber</code></td>
<td>Policy number from <code>getPolicyNameUsingClientPolicyId()</code> for the batch client policy.</td>
</tr>
<tr>
<td><code>source</code></td>
<td>Always <code>NHANCE</code> in the enqueue; <code>visitOffBoard()</code> also forces <code>source</code> to <code>NHANCE</code> before the HTTP call.</td>
</tr>
</tbody>
</table>
<pre><code class="language-php">'visitOffBoard' => [
'type' => 'CC',
'handler' => 'App\Controllers\EmployeeController',
],</code></pre>
<h2 id="visit-off-board-api">visitOffBoard()</h2>
<p>
The handler builds the Visit URL from environment configuration, appends the
path <code>delete-policy-with-dependents</code>, and POSTs JSON with
<code>memberIds</code>, <code>policyNumber</code>, and <code>source</code>.
Authorization uses a <code>JWT</code> prefix (this differs from the Visit
onboard upload path, which uses Basic auth in
<code>sendFamiliesToWellnessApi()</code>).
</p>
<pre><code class="language-php">$apiUrl = env('WELLNESS_ONBOARD_ENDPOINT_URL') . 'delete-policy-with-dependents';
$apiToken = env('WELLNESS_ONBOARD_AUTHORIZATION');
$response = $client->request('POST', '', [
'headers' => [
'Authorization' => 'JWT ' . $apiToken,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'memberIds' => $params['memberIds'] ?? [],
'policyNumber' => $params['policyNumber'] ?? '',
'source' => $params['source'] ?? '',
],
'http_errors' => false,
]);</code></pre>
<table>
<thead>
<tr><th>Outcome</th><th>Behavior</th></tr>
</thead>
<tbody>
<tr>
<td>HTTP status <code>200</code></td>
<td>Decodes JSON, calls <code>updateVisitoffboardStatus()</code> with <code>memberIds</code> and <code>api_result</code>, returns that array shape to the worker.</td>
</tr>
<tr>
<td>Other HTTP status</td>
<td>Logs failure; returns a structured error array; DB is not updated here.</td>
</tr>
<tr>
<td>Network or client exception</td>
<td>Logs exception; returns error array with <code>error</code> message.</td>
</tr>
</tbody>
</table>
<h2 id="update-status">updateVisitoffboardStatus()</h2>
<p>
This method is only invoked from <code>visitOffBoard()</code> after a
<code>200</code> response. It validates the payload, requires
<code>$data['api_result']['message'] === 'success'</code>, then updates all
listed employee policy rows in one statement.
</p>
<ol class="steps">
<li>
<strong>Validate structure</strong>
<p>Empty or non-array input logs and returns <code>false</code>.</p>
</li>
<li>
<strong>Require API result</strong>
<p>Missing <code>api_result</code> logs and returns <code>false</code>.</p>
</li>
<li>
<strong>Require success message</strong>
<p>If <code>message</code> is not exactly <code>success</code>, logs and returns <code>false</code> (no DB update).</p>
</li>
<li>
<strong>Optional warning for missingMemberIds</strong>
<p>If the API returns <code>missingMemberIds</code>, it is logged but processing continues.</p>
</li>
<li>
<strong>Append offboard marker</strong>
<p>Runs <code>whereIn('id', $memberIds)</code> and sets <code>wellness_onboard</code> to <code>CONCAT(wellness_onboard, '_DEL')</code> so existing reference ids stay traceable.</p>
</li>
</ol>
<pre><code class="language-php">$this->employeePolicyModel
->whereIn('id', $memberIds)
->set('wellness_onboard', "CONCAT(wellness_onboard, '_DEL')", false)
->update();</code></pre>
<div class="callout warning">
<span>!</span>
<div>
<strong>HTTP 200 alone is not enough</strong>
The DB update runs only when the decoded body has
<code>message === 'success'</code>. A 200 with a failed business payload will
not append <code>_DEL</code>.
</div>
</div>
<h2 id="manual-test-route">Manual test route</h2>
<p>
<code>visitOffBoardCheck()</code> is a thin admin helper that builds a hardcoded
sample <code>$params</code> array and prints <code>visitOffBoard($params)</code>.
It is not part of the production deletion pipeline; use it only for targeted
debugging in non-production environments.
</p>
<pre><code class="language-php">$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');</code></pre>
<p>
ACL restricts this path to admin role in <code>app/Config/Acl.php</code>
(<code>#^/visitOffBoardCheck#</code>).
</p>
<h2 id="developer-steps">Developer steps</h2>
<ol class="steps">
<li>
<strong>Keep payload keys aligned with the Visit API contract</strong>
<p>The job must supply <code>memberIds</code>, <code>policyNumber</code>, and <code>source</code> in the shape the delete endpoint expects.</p>
</li>
<li>
<strong>Confirm environment variables</strong>
<p><code>WELLNESS_ONBOARD_ENDPOINT_URL</code> must include the correct base (trailing slash behavior matters when concatenating <code>delete-policy-with-dependents</code>). <code>WELLNESS_ONBOARD_AUTHORIZATION</code> must be the token value expected after <code>JWT </code>.</p>
</li>
<li>
<strong>Do not call <code>updateVisitoffboardStatus()</code> directly for partial failures</strong>
<p>It trusts <code>api_result['message']</code>; wire new callers through <code>visitOffBoard()</code> or replicate its guards.</p>
</li>
<li>
<strong>Queue dependency</strong>
<p>As with other jobs, the worker must be running; otherwise the offboard job stays queued after deletion processing.</p>
</li>
</ol>
<h2 id="common-pitfalls">Common pitfalls</h2>
<table>
<thead>
<tr><th>Pitfall</th><th>Why it happens</th></tr>
</thead>
<tbody>
<tr>
<td><code>wellness_onboard</code> never gets <code>_DEL</code></td>
<td>HTTP not 200, or JSON <code>message</code> is not <code>success</code>, or <code>memberIds</code> empty / wrong type.</td>
</tr>
<tr>
<td>Visit receives wrong identifiers</td>
<td><code>memberIds</code> must match what the delete API expects (same identifiers family as onboard where applicable).</td>
</tr>
<tr>
<td>Auth works for onboard but not offboard</td>
<td>Offboard uses <code>JWT</code> header construction; onboard upload uses <code>Basic</code> in a different helper.</td>
</tr>
<tr>
<td>Job never enqueues</td>
<td>The enqueue block runs only when <code>insurer_or_tpa == 'insurer'</code> on that deletion file path.</td>
</tr>
</tbody>
</table>