BDS Insurer statement lets finance users upload an insurer-provided Excel statement,
validate each row against NHance policy transactions (pt_co_share_details +
policy_transaction), and persist matched brokerage amounts into
co_share_stmt_details. The admin UI is
app/Views/insurer_statement_list.php; all server logic lives in
PolicyTransactionController under the policy_tranction/statement route group.
authMVC filter. Upload and list are browser AJAX/form calls from an authenticated session, not public API endpoints.
validateInsurerStatement() checks that every Excel line maps to a real NHance transaction
for the selected insurer branch. Each row is matched on policy number (column B) and
endorsement number (column C).
In short:
failed and errors are shown per row in the UI.| Area | Location |
|---|---|
| UI | app/Views/insurer_statement_list.php — DataTable list, upload modal, validation error modal, invoice modal |
| Controller | app/Controllers/PolicyTransactionController.php |
| Statement header model | app/Models/InsurerStatements.php → insurer_statements |
| Line-item model | app/Models/COShareStmtDetailsModel.php → co_share_stmt_details |
| NHance source rows | app/Models/PTCOShareDetailsModel.php → getNonReconcileredPolicyTransactionByPolicyAndEndorsement() |
| Sample Excel | public/sample_excel/insurer_stament_sample.xlsx |
Routes (prefix policy_tranction/statement, filter authMVC):
| Method | Route | Handler |
|---|---|---|
| GET | list | statementList |
| POST | upload | uploadInsurerStatement |
| GET | downloadSampleInsurerStatement | Sample file download |
| GET | downloadInsurerStatement/(:num) | Uploaded file download |
| GET | getFileErr/(:any) | Validation failure JSON for modal |
| GET | getInsurerStatementMonth | Used to disable already-used statement numbers |
| GET | deleteStatement/(:any) | Soft-delete statement + related rows |
| GET | getPaymentDetails/(:any) | Invoice modal data |
| POST | saveInvoicePaymentDetails | Invoice / payment save |
$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');
// ...
});
});
statementList() loads insurers/branches via
insurerBranchModel::getInsurerBranchesWithInsurerNames(), invoice status labels, and
statements from the last 180 days (is_active = 1). The view shows:
file_status — success or failed (failed rows show an alert icon → error modal)invoice_status — pending / generated / sent / payment received
Upload form fields (#insurer_statement_upload_form): insurer
(insurer_id-branch_id), statement month (flatpickr), statement no (1–7), Excel file.
Submit is AJAX POST to relative upload. On success the page reloads; on validation failure
the API still returns HTTP 200 with dataStatus: false and error_data.
max_size[statement,16384] KB).WRITEPATH . 'uploads/statements/' (see createStatementFolder() for folder creation).insurer as {insurer_id}-{branch_id}, statement_month (converted to first-of-month Y-m-d), statement_no → stmt_sno.insurer_statements row via InsurerStatements model.validateInsurerStatement(['file_id' => $file_id]).updateInsurerStatement(['file_id' => $file_id]).dataStatus: false, error_data, error_code (HTTP 200).invoice_status = 'pending' and returns dataStatus: true.Runs immediately after upload (and can be re-run manually in dev with a hard-coded file_id in statementList() comment).
insurer_statements by file_id; fail if missing or physical file absent under writable/uploads/statements/.ExcelSanitizeHelper::sanitizeArrayData().1), skipping empty rows via check_row_is_empty_or_null().PTCOShareDetailsModel::getNonReconcileredPolicyTransactionByPolicyAndEndorsement(insurer_id, branch_id, policy_no[])
— matches pt.policy_no for completed transactions on that insurer branch.policy_no|endorsement_no (sanitized) for source and Excel rows.$matched_entry.error_data[row_index].insurer_statements.line_items, file_status (success / failed), reason (JSON).Private helper trims Unicode spaces and strips zero-width / BOM characters from policy and endorsement values before comparison — avoids “looks equal” mismatches in Excel.
| error_code | Meaning | UI |
|---|---|---|
0 | DB row missing or physical file not found | Plain message in modal |
1 | Legacy: list of row numbers (old format) | Comma-separated row list |
2 | Row-wise validation (current) | Modal lists each row with policy / endorsement / duplicate messages |
Row keys in error_data are the array index from the Excel loop (first data row is typically 1 after header removal), not necessarily the Excel row number on sheet.
Runs only when validation returned status: true.
pt_co_share_details rows).3, brokerage col 54, brokerage col 60 in current codereward from col 7variance = exp_amt - total_amt (from source exp_amt)COShareStmtDetailsModel::insertBatch($data_to_update) — one row per matched Excel line.file_status = success, invoice_status = pending, clears/sets reason.Header row is removed; data columns used by validation/update:
| Index | Column | Use |
|---|---|---|
1 | B | Policy number (required for matching) |
2 | C | Endorsement number |
3 | D | Actual BP amount |
4 | E | Actual TP amount |
5 | F | Actual BP brokerage |
6 | G | Actual TP brokerage |
7 | H | Reward |
Download the canonical layout from the list page link → downloadSampleInsurerStatement.
getNonReconcileredPolicyTransactionByPolicyAndEndorsement() joins:
pt_co_share_details (active) → policy_transaction (active, status = completed)insurer_id, insurer_branch_id, and pt.policy_no IN (...)
Matching is on sanitized policy_no + endorsement_no. The method name suggests
“non-reconciled” but the current query does not filter statement_id IS NULL;
be aware when re-uploading or debugging duplicate reconciliation.
After a successful upload, users manage invoice lifecycle from the list (separate from upload/validate):
invoice_status: pending, generated, sent, payment_receivedsaveInvoicePaymentDetails — JSON POST from invoice modaldeleteStatement($id) — soft-deletes co_share_stmt_details, inv_payment_details, and insurer_statements for that id
getInsurerStatementMonth returns successful statements for insurer+month so the UI can
disable statement numbers already used (disableStatementNo() in the view).
writable/uploads/statements/ exists and is writable (or call createStatementFolder() once).policy_tranction/statement/list in a logged-in session.getFileErr/{id} and renders reason JSON.validateInsurerStatement / updateInsurerStatement one-liner in statementList() with a known file_id.sanitizeStatementLookupValue(); re-type values if NHance shows a match but upload fails.error_code 2, duplicate message on second row.insurer_statements row remains; user sees failed status; re-upload needs a new statement or delete the failed row.getInsurerStatementMonth.dataStatus, not status code alone.validateInsurerStatementOld, updateInsurerStatementOLD remain in the controller; production path is the non-Old methods documented here.policy_tranction/report/list, variance, finance, outstanding listscronDailyBDSReport (separate from statement upload)