fix(abdm): render invoice line items, rates, and net/gross totals in fetched records modal

This commit is contained in:
DevSoftTech Server 2026-10-08 11:56:27 +05:30
parent 9cb4ccd701
commit a7d83551fe
2 changed files with 145 additions and 3 deletions

View File

@ -285,10 +285,14 @@ class M3HiuDocumentRepository
$resourceId = trim((string) ($resource['id'] ?? ''));
if ($resourceId !== '') {
$resourcesByRef[$type . '/' . $resourceId] = $resource;
$resourcesByRef[$resourceId] = $resource;
}
$fullUrl = trim((string) ($entry['fullUrl'] ?? ''));
if ($fullUrl !== '') {
$resourcesByRef[$fullUrl] = $resource;
if (str_starts_with($fullUrl, 'urn:uuid:')) {
$resourcesByRef[substr($fullUrl, 9)] = $resource;
}
}
if ($type === 'Patient' && empty($patient)) {
@ -388,10 +392,16 @@ class M3HiuDocumentRepository
continue;
}
$refStr = trim((string) ($ref['reference'] ?? ''));
if ($refStr === '' || ! isset($resourcesByRef[$refStr])) {
if ($refStr === '') {
continue;
}
foreach ($this->describeResourceForSection((array) $resourcesByRef[$refStr], $resourcesByRef) as $label) {
$matchedResource = $resourcesByRef[$refStr]
?? $resourcesByRef['urn:uuid:' . $refStr]
?? (str_starts_with($refStr, 'urn:uuid:') ? ($resourcesByRef[substr($refStr, 9)] ?? null) : null);
if (! is_array($matchedResource)) {
continue;
}
foreach ($this->describeResourceForSection($matchedResource, $resourcesByRef) as $label) {
if ($label !== '') {
$items[] = $label;
}
@ -484,11 +494,137 @@ class M3HiuDocumentRepository
$label = trim((string) (($resource['description'] ?? '') ?: ($resource['type']['text'] ?? '') ?: ($resource['type']['coding'][0]['display'] ?? '')));
return $label !== '' ? [$label] : [];
case 'Invoice':
return $this->describeInvoice($resource, $resourcesByRef);
case 'ChargeItem':
$itemName = trim((string) (
($resource['code']['text'] ?? '')
?: ($resource['productCodeableConcept']['text'] ?? '')
?: ($resource['code']['coding'][0]['display'] ?? '')
));
$qtyVal = $resource['quantity']['value'] ?? null;
$qtyUnit = trim((string) ($resource['quantity']['unit'] ?? 'unit'));
$qtyStr = $qtyVal !== null ? ($qtyVal . ' ' . $qtyUnit) : '';
$label = 'Item: ' . $itemName . ($qtyStr !== '' ? (' — Qty: ' . $qtyStr) : '');
return $label !== '' ? [$label] : [];
case 'Binary':
return ['Attached PDF document (available under Attached Files below)'];
default:
return [];
}
}
/**
* Formats an ABDM FHIR Invoice resource and its line items / charge items
* into user-friendly bullet points matching the PHR app and hospital view.
*
* @return array<int, string>
*/
private function describeInvoice(array $resource, array $resourcesByRef = []): array
{
$lines = [];
$currency = trim((string) (
$resource['totalNet']['currency']
?? $resource['totalGross']['currency']
?? 'INR'
));
$lineItems = (array) ($resource['lineItem'] ?? []);
foreach ($lineItems as $idx => $line) {
if (! is_array($line)) {
continue;
}
// 1. Resolve item name
$itemName = '';
$chargeRefStr = trim((string) ($line['chargeItemReference']['reference'] ?? ''));
$linkedCharge = null;
if ($chargeRefStr !== '') {
$linkedCharge = $resourcesByRef[$chargeRefStr]
?? $resourcesByRef['urn:uuid:' . $chargeRefStr]
?? (str_starts_with($chargeRefStr, 'urn:uuid:') ? ($resourcesByRef[substr($chargeRefStr, 9)] ?? null) : null);
}
if (is_array($linkedCharge)) {
$itemName = trim((string) (
($linkedCharge['code']['text'] ?? '')
?: ($linkedCharge['productCodeableConcept']['text'] ?? '')
?: ($linkedCharge['code']['coding'][0]['display'] ?? '')
));
}
if ($itemName === '') {
$itemName = trim((string) (
($line['chargeItemReference']['display'] ?? '')
?: ($line['chargeItemCodeableConcept']['text'] ?? '')
?: ($line['chargeItemCodeableConcept']['coding'][0]['display'] ?? '')
));
}
if ($itemName === '') {
$itemName = 'Item #' . ($idx + 1);
}
// 2. Resolve quantity
$quantityVal = null;
$quantityUnit = '';
if (is_array($linkedCharge) && isset($linkedCharge['quantity']) && is_array($linkedCharge['quantity'])) {
$quantityVal = $linkedCharge['quantity']['value'] ?? null;
$quantityUnit = trim((string) ($linkedCharge['quantity']['unit'] ?? ''));
}
if ($quantityVal === null && isset($line['quantity']) && is_array($line['quantity'])) {
$quantityVal = $line['quantity']['value'] ?? null;
$quantityUnit = trim((string) ($line['quantity']['unit'] ?? ''));
}
if ($quantityUnit === '') {
$quantityUnit = 'unit';
}
$quantityStr = $quantityVal !== null ? ($quantityVal . ' ' . $quantityUnit) : '1 unit';
// 3. Resolve rate / amount
$rateStr = '';
$priceComponents = (array) ($line['priceComponent'] ?? []);
foreach ($priceComponents as $comp) {
if (is_array($comp) && isset($comp['amount']['value'])) {
$amt = $comp['amount']['value'];
$curr = trim((string) ($comp['amount']['currency'] ?? $currency));
$rateStr = $amt . ($curr !== '' ? (' ' . $curr) : '');
break;
}
}
if ($rateStr === '' && is_array($linkedCharge)) {
if (isset($linkedCharge['priceOverride']['value'])) {
$amt = $linkedCharge['priceOverride']['value'];
$curr = trim((string) ($linkedCharge['priceOverride']['currency'] ?? $currency));
$rateStr = $amt . ($curr !== '' ? (' ' . $curr) : '');
} elseif (isset($linkedCharge['net']['value'])) {
$amt = $linkedCharge['net']['value'];
$curr = trim((string) ($linkedCharge['net']['currency'] ?? $currency));
$rateStr = $amt . ($curr !== '' ? (' ' . $curr) : '');
}
}
$part = 'Item: ' . $itemName . ' — Qty: ' . $quantityStr;
if ($rateStr !== '') {
$part .= ' — Rate: ' . $rateStr;
}
$lines[] = $part;
}
// 4. Totals (Total Net / Total Gross)
$net = $resource['totalNet']['value'] ?? null;
$gross = $resource['totalGross']['value'] ?? null;
if ($net !== null) {
$lines[] = 'Total Net: ' . $net . ' ' . $currency;
}
if ($gross !== null) {
$lines[] = 'Total Gross: ' . $gross . ' ' . $currency;
}
return $lines;
}
/**
* Builds a single "Test name: value unit (Flag)" style line for an
* Observation resource, covering the common FHIR value shapes used by

View File

@ -1361,7 +1361,13 @@ $(function() {
sections.forEach(function(sec, si) {
var body = '';
if (sec.items && sec.items.length) {
body = '<ul class="mb-0 small">' + sec.items.map(function(it) { return '<li>' + escHtml(it) + '</li>'; }).join('') + '</ul>';
body = '<ul class="mb-0 small">' + sec.items.map(function(it) {
var text = String(it || '').trim();
if (text.indexOf('Total Net:') === 0 || text.indexOf('Total Gross:') === 0) {
return '<li class="mt-1 fw-bold text-dark list-unstyled"><span class="badge bg-light text-dark border me-1">&bull;</span>' + escHtml(text) + '</li>';
}
return '<li>' + escHtml(text) + '</li>';
}).join('') + '</ul>';
} else if (sec.narrative) {
body = '<div class="small" style="white-space:pre-line;">' + escHtml(sec.narrative) + '</div>';
} else {