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 200 and a JSON body where message is success, Nhance appends _DEL to employee_polices.wellness_onboard for the affected rows.

i
Core files API and DB update logic live in app/Controllers/EmployeeController.php (visitOffBoard(), updateVisitoffboardStatus()). The insurer deletion flow importDeletionUpdateEndorsementID() enqueues the job from app/Controllers/EmpDataServiceController.php. The worker maps the job name in app/Controllers/JobWorker.php.

Overview

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]

When it is queued

After importDeletionUpdateEndorsementID() applies policy and endorsement updates from the deletion Excel, when $file['insurer_or_tpa'] == 'insurer', the controller enqueues visitOffBoard alongside cash-deposit and BDS jobs. The payload carries the list of employee_polices.id values processed in that batch, the client policy number, and a fixed source string.

$r = Jobs::addJob(['job_name' => 'visitOffBoard', 'payload' => [
    'memberIds'    => $employee_policy_table_primaryKey ?? [],
    'policyNumber' => $policy_name['policy_no'] ?? null,
    'source'       => 'NHANCE',
]]);
Payload keyMeaning
memberIds Array of employee_polices.id primary keys collected from the deletion Excel flow (emp_policy_primarykey per row).
policyNumber Policy number from getPolicyNameUsingClientPolicyId() for the batch client policy.
source Always NHANCE in the enqueue; visitOffBoard() also forces source to NHANCE before the HTTP call.
'visitOffBoard' => [
    'type'    => 'CC',
    'handler' => 'App\Controllers\EmployeeController',
],

visitOffBoard()

The handler builds the Visit URL from environment configuration, appends the path delete-policy-with-dependents, and POSTs JSON with memberIds, policyNumber, and source. Authorization uses a JWT prefix (this differs from the Visit onboard upload path, which uses Basic auth in sendFamiliesToWellnessApi()).

$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,
]);
OutcomeBehavior
HTTP status 200 Decodes JSON, calls updateVisitoffboardStatus() with memberIds and api_result, returns that array shape to the worker.
Other HTTP status Logs failure; returns a structured error array; DB is not updated here.
Network or client exception Logs exception; returns error array with error message.

updateVisitoffboardStatus()

This method is only invoked from visitOffBoard() after a 200 response. It validates the payload, requires $data['api_result']['message'] === 'success', then updates all listed employee policy rows in one statement.

  1. Validate structure

    Empty or non-array input logs and returns false.

  2. Require API result

    Missing api_result logs and returns false.

  3. Require success message

    If message is not exactly success, logs and returns false (no DB update).

  4. Optional warning for missingMemberIds

    If the API returns missingMemberIds, it is logged but processing continues.

  5. Append offboard marker

    Runs whereIn('id', $memberIds) and sets wellness_onboard to CONCAT(wellness_onboard, '_DEL') so existing reference ids stay traceable.

$this->employeePolicyModel
    ->whereIn('id', $memberIds)
    ->set('wellness_onboard', "CONCAT(wellness_onboard, '_DEL')", false)
    ->update();
!
HTTP 200 alone is not enough The DB update runs only when the decoded body has message === 'success'. A 200 with a failed business payload will not append _DEL.

Manual test route

visitOffBoardCheck() is a thin admin helper that builds a hardcoded sample $params array and prints visitOffBoard($params). It is not part of the production deletion pipeline; use it only for targeted debugging in non-production environments.

$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');

ACL restricts this path to admin role in app/Config/Acl.php (#^/visitOffBoardCheck#).

Developer steps

  1. Keep payload keys aligned with the Visit API contract

    The job must supply memberIds, policyNumber, and source in the shape the delete endpoint expects.

  2. Confirm environment variables

    WELLNESS_ONBOARD_ENDPOINT_URL must include the correct base (trailing slash behavior matters when concatenating delete-policy-with-dependents). WELLNESS_ONBOARD_AUTHORIZATION must be the token value expected after JWT .

  3. Do not call updateVisitoffboardStatus() directly for partial failures

    It trusts api_result['message']; wire new callers through visitOffBoard() or replicate its guards.

  4. Queue dependency

    As with other jobs, the worker must be running; otherwise the offboard job stays queued after deletion processing.

Common pitfalls

PitfallWhy it happens
wellness_onboard never gets _DEL HTTP not 200, or JSON message is not success, or memberIds empty / wrong type.
Visit receives wrong identifiers memberIds must match what the delete API expects (same identifiers family as onboard where applicable).
Auth works for onboard but not offboard Offboard uses JWT header construction; onboard upload uses Basic in a different helper.
Job never enqueues The enqueue block runs only when insurer_or_tpa == 'insurer' on that deletion file path.