TPA Recon (reconciliation) compares Nhance enrolment data with a
TPA API dump stored in tpa_api_data for a given batch file. HR can see
who exists only in Nhance, only in the TPA file, or in both with field differences, then take actions:
inception upload for missing members, direct DB updates for mismatches, and a deletion pipeline when
the TPA marks rows for deletion.
The flow is intentionally split across EmployeeController (report, proceed, heavy logic),
EmployeePolicyModel::getTPADataVariationReport (SQL slices), and
EmployeeServiceController (Excel pipelines and queued follow-up jobs). Read this page top
to bottom once; use the checklist at the end when you touch production data.
| Term | Meaning here |
|---|---|
batch_files.id (often called file_id in code) |
The batch row for the TPA import / variation context. tpa_api_data.file_id points at the same id. |
tpa_api_data |
One row per TPA member line for that file: emp_code, name, dob, gender, relation, optional ref (FK to employee_polices.id), rec_type, action_flag_status (e.g. D for deletion intent), is_active. |
rec_type |
Snapshot classification on tpa_api_data: matched, need_to_review, or not_in_nhance. Used to speed up repeat UI loads after the first full compute. |
ref |
When set, links a TPA row to employee_polices.id after strict matching in reconTpaApiDataWithEmployeepolicies. |
| Not in TPA | Nhance has an active policy member for the client/policy, but no TPA row with that emp_code in the dump. |
| Not in Nhance | TPA has an emp_code not present in the Nhance “master” list for that client/policy (see model method with $all = true). |
| Need to review / mismatch | Same emp_code and same relation line can be paired, but name, DOB, or gender differ; or relation-level pairing failed and TPA rows for that code need manual attention. |
app/Controllers/EmployeeController.php
getTPADataVariationReport($file_id, $type)proceedTPADataVariationNextStep($file_id)generateEmployeeUploadFromNotInNhance(...) (protected)initializeDeletionProcessForTpaApiData($file_id) — expects ['file_id' => batch_file_id]reconcileDbWithTpa, reconTpaApiDataWithEmployeepolicies, updateEmployeeDataFromTpa, exportVariationReportExcelapp/Models/EmployeePolicyModel.php → getTPADataVariationReport($client_id, $client_policy_id, $file_id, $emp_codes = [], $all = false)app/Controllers/EmployeeServiceController.php (inception, correction, disembark; queues jobs listed below)app/Controllers/JobWorker.php maps job names to EmployeeController handlersapp/Config/Routes.php):
GET employee/getTPADataVariationReport/(:num) — default second segment resolves to download-style behaviourGET employee/getTPADataVariationReportView/(:num) — same action with view type (JSON for UI)GET employee/proceedTPADataVariationNextStep/(:num) — query string ?tab=... (see Proceed section)app/Views/batch_list.php — download / view variation report links call the routes above
“Proceed” runs immediate actions; ref sync and deletion initialization also run as queued jobs
after inception or correction Excel workflows complete in EmployeeServiceController.
getTPADataVariationReportInputs: $file_id (batch file id), $type:
view — JSON API response with structured data (or empty array if nothing).download — streams Excel via exportVariationReportExcel (three sheets).
The controller loads batch_files for client_id, client_policy_id, then decides
compute vs cached mode:
rec_type snapshot on active tpa_api_data for this file (non-empty rec_type on any row).rec_type on every page load.Compute path — high level:
tpa_api_data for file_id; index by emp_code.rec_type = matched as a default.EmployeePolicyModel::getTPADataVariationReport($client_id, $client_policy_id, $file_id) (default branch: members with tpa_id null — the “not yet linked / review” slice).reconcileDbWithTpa (see next section). Update rec_type on the matched TPA id(s) accordingly.emp_code list with getTPADataVariationReport(..., [], true). Any TPA row whose code is not in that list → not_in_nhance.updateBatch all rec_type values in a transaction.reconTpaApiDataWithEmployeepolicies(['file_id' => $file_id]) to populate ref where possible.
Cached path: Reads not_in_nhance and need_to_review rows from tpa_api_data by rec_type,
then rebuilds mismatch_data for the UI without re-running the full reconciliation loop.
not_in_tpa (always “live”): Built from TPA emp_codes for the file and
getTPADataVariationReport(..., $tpa_emp_codes) — Nhance members whose emp_code is not in the TPA set.
This list is not driven from the rec_type snapshot by design.
The response also includes counts and button flags for the “Not in Nhance” proceed action (inception vs
deletion tallies based on empty ref and action_flag_status === 'D').
rec_type — reconcileDbWithTpa
Given one Nhance row ($db) and all TPA rows for the same emp_code ($tpaRows), the controller walks TPA rows in order:
strtolower($db['relationship']) === strtolower($tpa['relation']). If it does not match, that TPA row is skipped.name, dob, gender (case-insensitive for gender).status = matched with tpa_record and not_matching as the list of differing field names (may be empty = perfect match).status = no_match.
The report layer uses that result to set rec_type on TPA ids: perfect match → matched;
matched with diffs → need_to_review; no relation-level match → mark candidate TPA rows for that
emp_code as need_to_review.
ref — reconTpaApiDataWithEmployeepolicies
Parameter shape: ['file_id' => $batchFileId] (same id as the TPA batch).
tpa_api_data for the file. Rows that already have a non-empty ref are skipped.employee_polices joined to employees for the same client_id / client_policy_id, keyed by emp_code.ref, finds a candidate where all match exactly after normalization:
name, relationship vs relation, dob, gender.tpa_api_data.ref to the chosen employee_policies.id inside a transaction.
This is stricter than reconcileDbWithTpa (which only compares three fields after relation match)
because it must pick a single policy row to link.
proceedTPADataVariationNextStepRoute: GET employee/proceedTPADataVariationNextStep/{file_id}?tab=...
tab | Behaviour |
|---|---|
not_in_nhance |
Calls generateEmployeeUploadFromNotInNhance. On success, an inception-style file exists in files and format validation has run. Follow-up ref/deletion jobs are not invoked inline here (see background jobs). |
need_to_review |
Calls updateEmployeeDataFromTpa(['batch_file_id' => (int) $file_id]). This applies TPA-sourced values onto employees for reconciled mismatches (no correction Excel in this path). The handler checks $generationResult['success'] (not status). |
| other / missing | Still logs “proceed” and returns a generic success message. |
need_to_review branch previously passed the wrong parameter shape
to updateEmployeeDataFromTpa and read a non-existent status key. It now passes
batch_file_id and honours success, matching how queued jobs and QA utilities call the same method.
generateEmployeeUploadFromNotInNhancePurpose: Turn TPA-only members into an Employee Upload with Events Excel so the normal onboarding pipeline can create them in Nhance.
client_id, client_policy_id, client_branch_id on the batch file.getTPADataVariationReport(..., [], true) → emp_code list.tpa_api_data for this file_id whose emp_code is not in the master list.EmployeeServiceController::getInceptionExcelColumns(); map each TPA row (relation synonyms, change_event = addition, dates as d-M-Y, etc.).WRITEPATH/uploads/excel/, insert files row (action = addition, status = inprogress).excelFileFormatValidation with ['file_id' => newFileId, 'batch_file_id' => batchFileId].updateEmployeeDataFromTpa
Expects ['batch_file_id' => int]. Loads the same Nhance slice as the report, loads TPA rows per
emp_code, reuses reconcileDbWithTpa. When status is matched and not_matching is non-empty,
writes allowed fields on employees (name, dob, gender, relationship from TPA relation, corporate email when present).
Returns success, message, and counts in data.
initializeDeletionProcessForTpaApiData
Parameter: ['file_id' => $batchFileId] (same batch / TPA file id).
Business logic (as implemented):
tpa_api_data, select distinct non-empty ref values where file_id matches, rows are active, and action_flag_status = 'D' (deletion intent from TPA).ref values are employee_polices.id values already linked to TPA.employee_polices.id is in that set (whereIn on policy id). These are the rows that will appear on the generated deletion sheet..xls, create a files row with action = deletion, run EmployeeServiceController::employeeDisembark to create endorsements from the sheet.batch_files row, run EmpDataServiceController::importDeletionValidation for the batch import path.
A commented rec_type = matched filter exists in the query; it is intentionally not applied — do not assume
rec_type gates deletion eligibility unless you change the code deliberately.
After a successful inception-style onboarding from grouped family data
(employeesOnboardProcess path in EmployeeServiceController) when batch_file_id is present in params,
three jobs are enqueued in order:
updateEmployeeDataFromTpa — payload includes file_id (newly created processing file where applicable) and batch_file_idreconTpaApiDataWithEmployeepolicies — payload ['file_id' => batch_file_id]initializeDeletionProcessForTpaApiData — payload ['file_id' => batch_file_id]
A similar trio is queued after employeesCorrectionProcess when batch_file_id is passed. This is how
ref linking and deletion initialization catch up after Excel-driven workflows finish, even when the
“Proceed” controller path does not call them inline.
tpa_api_data with the same file_id.not_in_tpa, not_in_nhance, mismatch_data separately.rec_type looks stale, remember the controller only recomputes when no snapshot exists unless you clear or adjust rec_type in DB (there is no public “job” route in production docs).reconcileDbWithTpa and reconTpaApiDataWithEmployeepolicies if they must stay aligned, or document intentional differences.action_flag_status and ref on TPA rows — deletion candidates are rows explicitly flagged D with a populated ref.myLogger entries prefixed with TPA / TPA RECON for operational breadcrumbs.
For QA-only utilities (guarded in production), see internal notes such as
public/dev_logs/2026-04-03.md for updateEmployeeDataFromTpa and related routes.