feat(abdm-m2): enable care context discovery and direct linking for invoice records

This commit is contained in:
DevSoftTech Server 2026-09-30 12:25:01 +05:30
parent ccd5909fc6
commit f7c30529a3
5 changed files with 324 additions and 21 deletions

View File

@ -6009,7 +6009,8 @@ class AbdmGateway extends BaseController
string $patientName,
string $taskType = '',
string $entityId = '',
int $taskId = 0
int $taskId = 0,
string $source = ''
): array {
$this->db = $this->db ?? \Config\Database::connect();
$careContextsV3 = [];
@ -6113,6 +6114,36 @@ class AbdmGateway extends BaseController
'is_primary' => true,
]);
}
} elseif (in_array($taskType, ['invoice_publish', 'invoice_record_publish'], true)) {
$invoicePayload = null;
$candidateSources = [];
if ($source !== '' && in_array($source, ['opd_invoice', 'charges_invoice', 'ipd_invoice'], true)) {
$candidateSources[] = $source;
}
$candidateSources = array_values(array_unique(array_merge($candidateSources, ['opd_invoice', 'charges_invoice', 'ipd_invoice'])));
foreach ($candidateSources as $candSource) {
try {
$invoicePayload = $this->buildInvoiceSourceRecordPayload($candSource, $taskEntityId, $patientId, '');
if ($invoicePayload !== null) {
break;
}
} catch (\Throwable $e) {
// ignore and try next candidate source
}
}
if ($invoicePayload !== null) {
$addContext([
'careContextId' => (string) $invoicePayload['care_context_reference'],
'referenceNumber' => (string) $invoicePayload['care_context_reference'],
'display' => (string) $invoicePayload['care_context_display'],
'record_type' => 'InvoiceRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => true,
]);
}
}
}
@ -6344,6 +6375,98 @@ class AbdmGateway extends BaseController
}
}
// 6b. Query recent invoices / billing records for this patient
if ($this->db->tableExists('opd_master')) {
$opdFields = $this->db->getFieldNames('opd_master') ?? [];
if (in_array('opd_fee_amount', $opdFields, true)) {
$codeCol = in_array('opd_code', $opdFields, true) ? 'opd_code' : 'opd_id';
$opdInvoices = $this->db->table('opd_master')
->select('opd_id, ' . $codeCol . ' as opd_code, p_id, apointment_date, opd_book_date, opd_fee_amount')
->where('p_id', $patientId)
->where('opd_fee_amount >', 0)
->orderBy('opd_id', 'DESC')
->limit(10)
->get()
->getResultArray();
foreach ($opdInvoices as $oi) {
$rawDate = ! empty($oi['apointment_date']) && $oi['apointment_date'] !== '0000-00-00 00:00:00'
? $oi['apointment_date']
: (! empty($oi['opd_book_date']) ? $oi['opd_book_date'] : date('Y-m-d'));
$visitDate = date('Y-m-d', strtotime((string) $rawDate));
$ccRef = 'INVOICE-OPD-' . $oi['opd_id'] . '-' . $visitDate;
$display = 'Invoice ' . ((string) ($oi['opd_code'] ?: $oi['opd_id'])) . ' (OPD - Rs ' . number_format((float) $oi['opd_fee_amount'], 2) . ')';
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'display' => $display,
'record_type' => 'InvoiceRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
}
}
if ($this->db->tableExists('invoice_master')) {
$chgFields = $this->db->getFieldNames('invoice_master') ?? [];
if (in_array('net_amount', $chgFields, true) && in_array('attach_id', $chgFields, true)) {
$invCodeCol = in_array('invoice_code', $chgFields, true) ? 'invoice_code' : 'id';
$dateCol = in_array('inv_date', $chgFields, true) ? 'inv_date' : 'id';
$attachTypeClause = in_array('attach_type', $chgFields, true);
$builder = $this->db->table('invoice_master')
->select('id, ' . $invCodeCol . ' as invoice_code, attach_id, ' . $dateCol . ' as inv_date, net_amount')
->where('attach_id', $patientId);
if ($attachTypeClause) {
$builder->where('attach_type', 0);
}
$chgInvoices = $builder->orderBy('id', 'DESC')->limit(10)->get()->getResultArray();
foreach ($chgInvoices as $ci) {
$rawDate = ! empty($ci['inv_date']) ? $ci['inv_date'] : date('Y-m-d');
$visitDate = date('Y-m-d', strtotime((string) $rawDate));
$ccRef = 'INVOICE-CHG-' . $ci['id'] . '-' . $visitDate;
$display = 'Invoice ' . ((string) ($ci['invoice_code'] ?: $ci['id'])) . ' (Charges - Rs ' . number_format((float) $ci['net_amount'], 2) . ')';
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'display' => $display,
'record_type' => 'InvoiceRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
}
}
if ($this->db->tableExists('ipd_master')) {
$ipdFields = $this->db->getFieldNames('ipd_master') ?? [];
if (in_array('net_amount', $ipdFields, true)) {
$ipdCodeCol = in_array('ipd_code', $ipdFields, true) ? 'ipd_code' : 'id';
$ipdInvoices = $this->db->table('ipd_master')
->select('id, ' . $ipdCodeCol . ' as ipd_code, p_id, discharge_date, register_date, net_amount')
->where('p_id', $patientId)
->where('net_amount >', 0)
->orderBy('id', 'DESC')
->limit(5)
->get()
->getResultArray();
foreach ($ipdInvoices as $ii) {
$rawDate = ! empty($ii['discharge_date']) ? $ii['discharge_date'] : (! empty($ii['register_date']) ? $ii['register_date'] : date('Y-m-d'));
$visitDate = date('Y-m-d', strtotime((string) $rawDate));
$ccRef = 'INVOICE-IPD-' . $ii['id'] . '-' . $visitDate;
$display = 'Invoice ' . ((string) ($ii['ipd_code'] ?: $ii['id'])) . ' (IPD - Rs ' . number_format((float) $ii['net_amount'], 2) . ')';
$addContext([
'careContextId' => $ccRef,
'referenceNumber' => $ccRef,
'display' => $display,
'record_type' => 'InvoiceRecord',
'patient_id' => $patientId,
'is_fhir_ready' => true,
'is_primary' => false,
]);
}
}
}
// 7. Fallback: default registration file care context
if (empty($careContextsV3)) {
$ccRef = 'REG-' . $patientRef;
@ -10913,6 +11036,7 @@ class AbdmGateway extends BaseController
$taskType = '';
$entityId = '';
$taskId = 0;
$source = '';
if (method_exists($this->request, 'getVar')) {
$patientId = (int) ($this->request->getVar('patient_id') ?? 0);
$abhaAddress = trim((string) ($this->request->getVar('abha_address') ?? ''));
@ -10920,6 +11044,7 @@ class AbdmGateway extends BaseController
$taskType = trim((string) ($this->request->getVar('task_type') ?? ''));
$entityId = trim((string) ($this->request->getVar('entity_id') ?? ''));
$taskId = (int) ($this->request->getVar('task_id') ?? 0);
$source = trim((string) ($this->request->getVar('source') ?? ''));
}
if ($patientId <= 0 && ! empty($_REQUEST['patient_id'])) {
$patientId = (int) $_REQUEST['patient_id'];
@ -10939,6 +11064,9 @@ class AbdmGateway extends BaseController
if ($taskId <= 0 && ! empty($_REQUEST['task_id'])) {
$taskId = (int) $_REQUEST['task_id'];
}
if ($source === '' && ! empty($_REQUEST['source'])) {
$source = trim((string) $_REQUEST['source']);
}
if ($patientId <= 0 && $abhaAddress === '' && $pCode === '') {
return $this->response->setJSON(['ok' => 0, 'error_text' => 'patient_id, abha_address, or p_code is required']);
@ -11000,7 +11128,7 @@ class AbdmGateway extends BaseController
$rawPhone = trim((string) ($patient['mphone1'] ?? $patient['mphone2'] ?? ''));
$cleanPhone = substr(preg_replace('/\D/', '', $rawPhone), -10);
[$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientName, $taskType, $entityId, $taskId);
[$careContextsV3, $careContextsFull] = $this->findCareContextsForPatient($patientId, $patientRef, $patientName, $taskType, $entityId, $taskId, $source);
// Fetch already-linked care contexts from Bridge if ABHA address is present
$linkedRefs = [];

View File

@ -695,9 +695,13 @@ class AbdmTaskBoard extends BaseController
$rows = [];
if ($this->db->tableExists('opd_master')) {
foreach ($this->db->table('opd_master')
->select('opd_id AS bill_id, opd_code AS bill_code, p_id AS patient_id, P_name AS patient_name, apointment_date AS bill_date, opd_fee_amount AS amount')
->orderBy('opd_id', 'DESC')
$builder = $this->db->table('opd_master o')
->select("o.opd_id AS bill_id, o.opd_code AS bill_code, o.p_id AS patient_id, COALESCE(NULLIF(o.P_name, ''), p.p_fname, '') AS patient_name, o.apointment_date AS bill_date, o.opd_fee_amount AS amount, COALESCE(NULLIF(p.abha_address, ''), NULLIF(p.abha_id, ''), '') AS abha_id", false);
if ($this->db->tableExists('patient_master')) {
$builder->join('patient_master p', 'p.id = o.p_id', 'left');
}
foreach ($builder
->orderBy('o.opd_id', 'DESC')
->limit(100)
->get()
->getResultArray() as $row) {
@ -706,9 +710,12 @@ class AbdmTaskBoard extends BaseController
}
if ($this->db->tableExists('invoice_master')) {
foreach ($this->db->table('invoice_master i')
->select('i.id AS bill_id, i.invoice_code AS bill_code, i.attach_id AS patient_id, COALESCE(NULLIF(i.inv_name, ""), p.p_fname) AS patient_name, i.inv_date AS bill_date, i.net_amount AS amount')
->join('patient_master p', 'p.id = i.attach_id AND i.attach_type = 0', 'left')
$builder = $this->db->table('invoice_master i')
->select("i.id AS bill_id, i.invoice_code AS bill_code, i.attach_id AS patient_id, COALESCE(NULLIF(i.inv_name, ''), p.p_fname, '') AS patient_name, i.inv_date AS bill_date, i.net_amount AS amount, COALESCE(NULLIF(p.abha_address, ''), NULLIF(p.abha_id, ''), '') AS abha_id", false);
if ($this->db->tableExists('patient_master')) {
$builder->join('patient_master p', 'p.id = i.attach_id AND i.attach_type = 0', 'left');
}
foreach ($builder
->orderBy('i.id', 'DESC')
->limit(100)
->get()
@ -718,9 +725,12 @@ class AbdmTaskBoard extends BaseController
}
if ($this->db->tableExists('ipd_master')) {
foreach ($this->db->table('ipd_master i')
->select("i.id AS bill_id, i.ipd_code AS bill_code, i.p_id AS patient_id, COALESCE(NULLIF(NULLIF(TRIM(i.P_name), ''), '0'), NULLIF(TRIM(p.p_fname), '')) AS patient_name, COALESCE(i.discharge_date, i.register_date) AS bill_date, i.net_amount AS amount", false)
->join('patient_master p', 'p.id = i.p_id', 'left')
$builder = $this->db->table('ipd_master i')
->select("i.id AS bill_id, i.ipd_code AS bill_code, i.p_id AS patient_id, COALESCE(NULLIF(NULLIF(TRIM(i.P_name), ''), '0'), NULLIF(TRIM(p.p_fname), ''), '') AS patient_name, COALESCE(i.discharge_date, i.register_date) AS bill_date, i.net_amount AS amount, COALESCE(NULLIF(p.abha_address, ''), NULLIF(p.abha_id, ''), '') AS abha_id", false);
if ($this->db->tableExists('patient_master')) {
$builder->join('patient_master p', 'p.id = i.p_id', 'left');
}
foreach ($builder
->orderBy('i.id', 'DESC')
->limit(100)
->get()
@ -819,13 +829,17 @@ class AbdmTaskBoard extends BaseController
$statusTone = 'danger';
}
$billDate = ! empty($row['bill_date']) ? date('Y-m-d', strtotime((string) $row['bill_date'])) : date('Y-m-d');
$sourcePrefix = $row['source_key'] === 'opd_invoice' ? 'OPD' : ($row['source_key'] === 'ipd_invoice' ? 'IPD' : 'CHG');
$defaultCareContext = 'INVOICE-' . $sourcePrefix . '-' . $billId . '-' . $billDate;
$row['record_status_label'] = $statusLabel;
$row['record_status_tone'] = $statusTone;
$row['push_status'] = $pushStatus;
$row['link_status'] = $linkStatus;
$row['queue_id'] = trim((string) ($hr['abdm_txn_id'] ?? ''));
$row['bridge_record_id'] = (int) ($hr['bridge_record_id'] ?? 0);
$row['care_context_reference'] = trim((string) ($hr['care_context_reference'] ?? ''));
$row['care_context_reference'] = trim((string) ($hr['care_context_reference'] ?? '')) ?: $defaultCareContext;
}
unset($row);

View File

@ -474,6 +474,8 @@
data-invoice-source="<?= esc((string) ($invoice['source_key'] ?? '')) ?>"
data-bill-id="<?= (int) ($invoice['bill_id'] ?? 0) ?>"
data-patient-id="<?= (int) ($invoice['patient_id'] ?? 0) ?>"
data-abha-id="<?= esc((string) ($invoice['abha_id'] ?? '')) ?>"
data-care-context="<?= esc((string) ($invoice['care_context_reference'] ?? '')) ?>"
data-push-status="<?= esc($pushStatus) ?>"
>
<td><?= esc((string) ($invoice['source'] ?? '')) ?></td>
@ -481,6 +483,9 @@
<td>
<div><?= esc((string) ($invoice['patient_name'] ?? '')) ?></div>
<small class="text-muted">#<?= (int) ($invoice['patient_id'] ?? 0) ?></small>
<?php if (! empty($invoice['abha_id'])): ?>
<div><span class="text-primary small" title="ABHA"><i class="bi bi-person-check me-1"></i><?= esc((string) $invoice['abha_id']) ?></span></div>
<?php endif; ?>
</td>
<td><?= esc(substr((string) ($invoice['bill_date'] ?? ''), 0, 16)) ?></td>
<td class="text-end"><?= number_format((float) ($invoice['amount'] ?? 0), 2) ?></td>
@ -497,7 +502,17 @@
<?php if ($alreadySubmitted): ?>
<span class="badge bg-success ms-1"><i class="bi bi-check-circle"></i> Linked</span>
<?php else: ?>
<button type="button" class="btn btn-sm btn-outline-success btn-invoice-care-link ms-1" data-patient-id="<?= (int) ($invoice['patient_id'] ?? 0) ?>" data-abha-id="<?= esc((string) ($invoice['abha_id'] ?? '')) ?>" data-patient-name="<?= esc((string) ($invoice['patient_name'] ?? '')) ?>" title="Link Care Context with patient ABHA"><i class="bi bi-link-45deg"></i> Link Context</button>
<button
type="button"
class="btn btn-sm btn-outline-success btn-invoice-care-link ms-1"
data-bill-id="<?= (int) ($invoice['bill_id'] ?? 0) ?>"
data-invoice-source="<?= esc((string) ($invoice['source_key'] ?? '')) ?>"
data-patient-id="<?= (int) ($invoice['patient_id'] ?? 0) ?>"
data-abha-id="<?= esc((string) ($invoice['abha_id'] ?? '')) ?>"
data-patient-name="<?= esc((string) ($invoice['patient_name'] ?? '')) ?>"
data-care-context="<?= esc((string) ($invoice['care_context_reference'] ?? '')) ?>"
title="Link Care Context with patient ABHA"
><i class="bi bi-link-45deg"></i> Link Context</button>
<?php endif; ?>
</td>
</tr>
@ -3512,19 +3527,82 @@
});
});
document.querySelectorAll('.btn-opd-care-context-link, .btn-invoice-care-link').forEach(function (btn) {
document.querySelectorAll('.btn-opd-care-context-link').forEach(function (btn) {
btn.addEventListener('click', function () {
var row = btn.closest('tr');
var patientId = parseInt(btn.getAttribute('data-patient-id') || '0', 10);
var abhaId = (btn.getAttribute('data-abha-id') || '').trim();
var patientName = (btn.getAttribute('data-patient-name') || '').trim();
var opdId = (btn.getAttribute('data-opd-id') || '').trim();
var taskType = opdId ? 'opd_prescription_publish' : '';
var entityId = opdId || '';
if (typeof window.openAbdmHipLinkModal === 'function') {
window.openAbdmHipLinkModal(patientId, abhaId, {
patient_name: patientName,
task_type: taskType,
entity_id: entityId
task_type: 'opd_prescription_publish',
entity_id: opdId,
onLinked: function (res) {
if (!row) return;
var actionCell = btn.parentNode;
if (actionCell) {
btn.outerHTML = '<span class="badge bg-success ms-1"><i class="bi bi-check-circle"></i> Linked</span>';
}
var statusBadge = row.querySelector('.opd-consult-status-badge');
if (statusBadge) {
statusBadge.className = 'badge bg-success opd-consult-status-badge';
statusBadge.textContent = 'Linked';
}
}
});
}
});
});
document.querySelectorAll('.btn-invoice-care-link').forEach(function (btn) {
btn.addEventListener('click', function () {
var row = btn.closest('tr');
var patientId = parseInt(btn.getAttribute('data-patient-id') || (row ? row.getAttribute('data-patient-id') : '0') || '0', 10);
var abhaId = (btn.getAttribute('data-abha-id') || (row ? row.getAttribute('data-abha-id') : '') || '').trim();
var patientName = (btn.getAttribute('data-patient-name') || '').trim();
var billId = (btn.getAttribute('data-bill-id') || (row ? row.getAttribute('data-bill-id') : '') || '').trim();
var invoiceSource = (btn.getAttribute('data-invoice-source') || (row ? row.getAttribute('data-invoice-source') : '') || '').trim();
var careContextRef = (btn.getAttribute('data-care-context') || (row ? row.getAttribute('data-care-context') : '') || '').trim();
if (typeof window.openAbdmHipLinkModal === 'function') {
window.openAbdmHipLinkModal(patientId, abhaId, {
patient_name: patientName,
task_type: 'invoice_publish',
entity_id: billId,
source: invoiceSource,
care_context: careContextRef,
onLinked: function (res) {
if (!row) return;
// Update Link Status cell (cell 6)
var linkStatusCell = row.children[6];
if (linkStatusCell) {
linkStatusCell.innerHTML = '<span class="badge bg-success">LINKED</span>';
}
// Update Status badge (cell 5)
var pushStatusBadge = row.querySelector('.invoice-status-badge');
if (pushStatusBadge) {
pushStatusBadge.className = 'badge bg-success invoice-status-badge';
pushStatusBadge.textContent = 'Linked';
}
// Update Gateway Reference cell (cell 7)
var gatewayCell = row.querySelector('.invoice-details');
if (gatewayCell && res && res.care_contexts && res.care_contexts.length > 0) {
var firstCc = res.care_contexts[0].ref || '';
if (firstCc) {
gatewayCell.innerHTML = '<div class="text-truncate" title="' + firstCc + '"><strong>CC:</strong> ' + firstCc + '</div>';
}
}
// Update Action button in Action cell (cell 8)
var actionCell = row.children[8];
if (actionCell) {
var linkBtn = actionCell.querySelector('.btn-invoice-care-link');
if (linkBtn) {
linkBtn.outerHTML = '<span class="badge bg-success ms-1"><i class="bi bi-check-circle"></i> Linked</span>';
}
}
}
});
}
});

View File

@ -406,7 +406,7 @@
/**
* Load patient demographics and care contexts from server.
*/
window.loadPatientCareContexts = function(patientId, abhaAddress, taskType, entityId, taskId) {
window.loadPatientCareContexts = function(patientId, abhaAddress, taskType, entityId, taskId, source) {
var loading = document.getElementById('hipCareContextsLoading');
var container = document.getElementById('hipCareContextsContainer');
if (loading) loading.classList.remove('d-none');
@ -415,6 +415,7 @@
taskType = taskType || (window.currentHipTaskContext ? window.currentHipTaskContext.taskType : '');
entityId = entityId || (window.currentHipTaskContext ? window.currentHipTaskContext.entityId : '');
taskId = taskId || (window.currentHipTaskContext ? window.currentHipTaskContext.taskId : 0);
source = source || (window.currentHipTaskContext ? window.currentHipTaskContext.source : '');
var csrf = getCsrfData();
var url = '<?= base_url('AbdmGateway/hip_patient_care_contexts') ?>'
@ -422,7 +423,8 @@
+ '&abha_address=' + encodeURIComponent(abhaAddress || '')
+ '&task_type=' + encodeURIComponent(taskType || '')
+ '&entity_id=' + encodeURIComponent(entityId || '')
+ '&task_id=' + encodeURIComponent(taskId || 0);
+ '&task_id=' + encodeURIComponent(taskId || 0)
+ '&source=' + encodeURIComponent(source || '');
fetch(url, {
method: 'GET',
@ -531,6 +533,8 @@
taskId: prefill.task_id || 0,
taskType: prefill.task_type || '',
entityId: prefill.entity_id || '',
source: prefill.source || '',
careContext: prefill.care_context || '',
onLinked: prefill.onLinked || null
};
@ -567,7 +571,7 @@
// Load visits
if (patientId > 0 || (abhaAddress && abhaAddress.length > 3)) {
loadPatientCareContexts(patientId, abhaAddress, prefill.task_type, prefill.entity_id, prefill.task_id);
loadPatientCareContexts(patientId, abhaAddress, prefill.task_type, prefill.entity_id, prefill.task_id, prefill.source);
} else {
var container = document.getElementById('hipCareContextsContainer');
if (container) {
@ -651,6 +655,29 @@
alertBox.innerHTML += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Consultation bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) &rarr; tap <em>Linked Facilities</em> &rarr; tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
}
}).catch(function() {});
} else if (ctxTaskType === 'invoice_publish') {
var pushData = new URLSearchParams();
pushData.append('source', (window.currentHipTaskContext ? window.currentHipTaskContext.source : '') || '');
pushData.append('bill_id', ctxEntityId);
pushData.append('patient_id', ctxPatientId);
pushData.append('abha_id', abhaAddress);
if (firstRef) pushData.append('care_context_reference', firstRef);
pushData.append(csrf.name || 'csrf_hms', csrf.hash);
fetch('<?= base_url('AbdmGateway/share_invoice_source_bundle') ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
},
body: pushData.toString()
}).then(function(r) { return r.json(); }).then(function(pRes) {
updateCsrf(pRes);
if (pRes && pRes.ok === 1 && alertBox) {
alertBox.innerHTML += '<div class="mt-2 pt-2 border-top border-success-subtle text-dark small"><i class="bi bi-file-earmark-medical text-success me-1"></i><strong>FHIR Document Stored on Bridge:</strong> Invoice bundle is active on Bridge for Care Context <code>' + (pRes.care_context_reference || firstRef) + '</code>.<br><i class="bi bi-info-circle text-primary me-1"></i><strong>To view in PHR App:</strong> Open your PHR app (e.g. ABHA App) &rarr; tap <em>Linked Facilities</em> &rarr; tap <em>"Fetch Records" / "Pull Records"</em>.</div>';
}
}).catch(function() {});
}
}

View File

@ -97,6 +97,17 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
abdm_care_context_reference TEXT
)");
$db->query("CREATE TABLE IF NOT EXISTS " . $db->prefixTable('invoice_master') . " (
id INTEGER PRIMARY KEY,
invoice_code TEXT,
attach_id INTEGER,
attach_type INTEGER,
inv_date TEXT,
net_amount REAL
)");
$db->resetDataCache();
$db->table('charge_master')->emptyTable();
$db->table('lab_request')->emptyTable();
$db->table('opd_prescription')->emptyTable();
@ -105,6 +116,7 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
$db->table('patient_master')->emptyTable();
$db->table('ipd_master')->emptyTable();
$db->table('immunization_records')->emptyTable();
$db->table('invoice_master')->emptyTable();
$db->table('patient_master')->insert([
'id' => 12,
@ -353,5 +365,49 @@ final class CareContextDiscoveryTest extends CIUnitTestCase
$refs = array_column($v3, 'referenceNumber');
$this->assertContains('OPD-12-S77-20260929', $refs);
}
public function testDiscoversInvoiceCareContextWhenInvoiceTaskRequested(): void
{
$db = \Config\Database::connect();
$db->table('invoice_master')->emptyTable();
$db->table('invoice_master')->insert([
'id' => 88,
'invoice_code' => 'INV-88',
'attach_id' => 12,
'attach_type' => 0,
'inv_date' => '2026-09-30',
'net_amount' => 450.00,
]);
$refGateway = new ReflectionClass($this->gateway);
$method = $refGateway->getMethod('findCareContextsForPatient');
$method->setAccessible(true);
[$v3, $full] = $method->invoke(
$this->gateway,
12,
'P26071000012',
'KESHAV SINGH',
'invoice_publish',
'88',
0,
'charges_invoice'
);
$refs = array_column($v3, 'referenceNumber');
$this->assertContains('INVOICE-CHG-88-2026-09-30', $refs);
$invoiceCtx = null;
foreach ($full as $ctx) {
if ($ctx['careContextId'] === 'INVOICE-CHG-88-2026-09-30') {
$invoiceCtx = $ctx;
break;
}
}
$this->assertNotNull($invoiceCtx);
$this->assertSame('InvoiceRecord', $invoiceCtx['record_type']);
$this->assertTrue($invoiceCtx['is_primary']);
$this->assertTrue($invoiceCtx['is_fhir_ready']);
}
}