Fix radiology editor DICOM gallery context, match related templates, and remove manual ABDM panel

This commit is contained in:
DevSoftTech Server 2026-10-01 12:59:45 +05:30
parent 0b75b52377
commit 38b96a1003
4 changed files with 227 additions and 199 deletions

View File

@ -1046,27 +1046,7 @@ class Diagnosis extends BaseController
return '<div class="alert alert-danger">Report not found</div>';
}
$invoiceItem = $this->db->table('invoice_item')
->select('item_id')
->where('id', (int) ($reportRow->charge_item_id ?? 0))
->get()
->getRow();
$templateBuilder = $this->db->table('radiology_ultrasound_template')
->select('id, template_name, title, keywords, impression_cat')
->where('Modality', (int) ($reportRow->lab_type ?? 0));
if (! empty($invoiceItem->item_id)) {
$templateBuilder->groupStart()
->where('charge_id', (int) $invoiceItem->item_id)
->orWhere('charge_id', 0)
->groupEnd();
}
$templates = $templateBuilder
->orderBy('template_name', 'ASC')
->get()
->getResult();
$templates = $this->getRadiologyTemplatesForReport($reportRow);
return view('diagnosis/lab_final_report_show_xray', [
'report_format' => [$reportRow],
@ -1126,27 +1106,7 @@ class Diagnosis extends BaseController
}
}
$invoiceItem = $this->db->table('invoice_item')
->select('item_id')
->where('id', (int) ($reportRow->charge_item_id ?? 0))
->get()
->getRow();
$templateBuilder = $this->db->table('radiology_ultrasound_template')
->select('id, template_name, title, keywords, impression_cat')
->where('Modality', (int) ($reportRow->lab_type ?? 0));
if (! empty($invoiceItem->item_id)) {
$templateBuilder->groupStart()
->where('charge_id', (int) $invoiceItem->item_id)
->orWhere('charge_id', 0)
->groupEnd();
}
$templates = $templateBuilder
->orderBy('template_name', 'ASC')
->get()
->getResult();
$templates = $this->getRadiologyTemplatesForReport($reportRow);
return view('diagnosis/lab_final_report_editor', [
'report_format' => [$reportRow],
@ -1155,6 +1115,138 @@ class Diagnosis extends BaseController
]);
}
/**
* Fetch and rank radiology templates matching a given report.
*
* @param object|array<string, mixed> $reportRow
* @return array<int, object>
*/
private function getRadiologyTemplatesForReport($reportRow): array
{
$reportObj = is_array($reportRow) ? (object) $reportRow : $reportRow;
$labType = (int) ($reportObj->lab_type ?? 0);
if ($labType <= 0 || ! $this->db->tableExists('radiology_ultrasound_template')) {
return [];
}
$builder = $this->db->table('radiology_ultrasound_template')
->select('id, template_name, title, keywords, impression_cat, charge_id, Modality');
if ($labType === 2) {
$builder->groupStart()
->where('Modality', 2)
->orWhere('Modality IS NULL')
->groupEnd();
} else {
$builder->where('Modality', $labType);
}
$allTemplates = $builder->orderBy('template_name', 'ASC')->get()->getResult();
if (empty($allTemplates)) {
return [];
}
$chargeItemId = (int) ($reportObj->charge_item_id ?? 0);
$reportName = trim((string) ($reportObj->report_name ?? ''));
$invoiceItem = null;
if ($chargeItemId > 0 && $this->db->tableExists('invoice_item')) {
$invoiceItem = $this->db->table('invoice_item')
->select('item_id, item_name')
->where('id', $chargeItemId)
->get()
->getRow();
}
$itemId = (int) ($invoiceItem->item_id ?? 0);
$itemName = trim((string) ($invoiceItem->item_name ?? ''));
// Normalizer helper for clinical test names
$normalizeTest = static function (string $text): string {
$s = strtolower(trim($text));
$s = preg_replace('/\b(x[- ]?ray|xray|mri|ct|scan|usg|ultrasound|view|views|plain|contrast)\b/i', ' ', $s);
$s = preg_replace('/[^a-z0-9]+/i', ' ', $s);
return trim((string) preg_replace('/\s+/', ' ', $s));
};
$normalizedReportName = $normalizeTest($reportName);
$normalizedItemName = $normalizeTest($itemName);
$primaryTarget = $normalizedReportName !== '' ? $normalizedReportName : $normalizedItemName;
// Collect related charge IDs from hc_items
$matchedChargeIds = [];
if ($itemId > 0) {
$matchedChargeIds[] = $itemId;
}
if ($primaryTarget !== '' && $this->db->tableExists('hc_items')) {
$hcRows = $this->db->table('hc_items')
->select('id, idesc')
->where('itype', $labType)
->get()
->getResult();
foreach ($hcRows as $hc) {
$hcClean = $normalizeTest((string) ($hc->idesc ?? ''));
if ($hcClean !== '' && ($hcClean === $primaryTarget || str_contains($hcClean, $primaryTarget) || str_contains($primaryTarget, $hcClean))) {
$matchedChargeIds[] = (int) $hc->id;
}
}
$matchedChargeIds = array_values(array_unique($matchedChargeIds));
}
$targetTokens = array_values(array_filter(explode(' ', $primaryTarget), static fn($t) => strlen($t) >= 3));
foreach ($allTemplates as $tpl) {
$score = 0;
$tplChargeId = (int) ($tpl->charge_id ?? 0);
$tplCleanTitle = $normalizeTest((string) ($tpl->title ?? ''));
$tplCleanName = $normalizeTest((string) ($tpl->template_name ?? ''));
$tplKeywords = strtolower((string) ($tpl->keywords ?? ''));
if ($tplChargeId > 0 && in_array($tplChargeId, $matchedChargeIds, true)) {
$score = max($score, 100);
}
if ($primaryTarget !== '') {
if ($tplCleanTitle === $primaryTarget || $tplCleanName === $primaryTarget) {
$score = max($score, 90);
} elseif (str_contains($tplCleanTitle, $primaryTarget) || str_contains($tplCleanName, $primaryTarget) || str_contains($primaryTarget, $tplCleanTitle)) {
$score = max($score, 75);
}
}
if (! empty($targetTokens)) {
$tokenHits = 0;
$searchCorpus = $tplCleanName . ' ' . $tplCleanTitle . ' ' . $tplKeywords;
foreach ($targetTokens as $token) {
if (str_contains($searchCorpus, $token)) {
$tokenHits++;
}
}
if ($tokenHits === count($targetTokens)) {
$score = max($score, 60);
} elseif ($tokenHits > 0) {
$score = max($score, 30 + ($tokenHits * 10));
}
}
$tpl->match_score = $score;
$tpl->is_related = $score >= 50;
}
usort($allTemplates, static function ($a, $b) {
$scoreA = (int) ($a->match_score ?? 0);
$scoreB = (int) ($b->match_score ?? 0);
if ($scoreA !== $scoreB) {
return $scoreB <=> $scoreA;
}
return strcasecmp((string) ($a->template_name ?? ''), (string) ($b->template_name ?? ''));
});
return $allTemplates;
}
public function getTemplateXray($templateId)
{
$templateId = (int) $templateId;
@ -2994,31 +3086,52 @@ class Diagnosis extends BaseController
$labType = (int) $labType;
$labReqId = (int) $labReqId;
if ($invoiceId <= 0 || $labType <= 0 || ! $this->db->tableExists('file_upload_data')) {
$studyName = '';
if ($labReqId > 0 && $this->db->tableExists('lab_request')) {
$requestRow = $this->db->table('lab_request')
->select('id, charge_id, lab_type, report_name')
->where('id', $labReqId)
->get(1)
->getRowArray();
if ($requestRow) {
$studyName = trim((string) ($requestRow['report_name'] ?? ''));
if ($invoiceId <= 0 || $invoiceId === $labReqId) {
$invoiceId = (int) ($requestRow['charge_id'] ?? $invoiceId);
}
if ($labType <= 0) {
$labType = (int) ($requestRow['lab_type'] ?? $labType);
}
}
}
if (($invoiceId <= 0 && $labReqId <= 0) || ! $this->db->tableExists('file_upload_data')) {
return view('diagnosis/imaging_upload_gallery', [
'files' => [],
'study_name' => '',
]);
}
$studyName = '';
if ($labReqId > 0) {
$requestRow = $this->db->table('lab_request')
->select('id, report_name')
->where('id', $labReqId)
->get(1)
->getRowArray();
$studyName = trim((string) ($requestRow['report_name'] ?? ''));
}
$fields = $this->db->getFieldNames('file_upload_data');
$hasRepoId = in_array('repo_id', $fields, true);
$builder = $this->db->table('file_upload_data')
->where('isdelete', 0)
->where('charge_id', $invoiceId)
->where('charge_type', $labType);
->where('isdelete', 0);
$fields = $this->db->getFieldNames('file_upload_data');
if ($labReqId > 0 && in_array('repo_id', $fields, true)) {
if ($labReqId > 0 && $hasRepoId && $invoiceId > 0) {
$builder->groupStart()
->where('repo_id', $labReqId)
->orGroupStart()
->where('charge_id', $invoiceId)
->where('charge_type', $labType)
->groupEnd()
->groupEnd();
} elseif ($labReqId > 0 && $hasRepoId) {
$builder->where('repo_id', $labReqId);
} elseif ($invoiceId > 0) {
$builder->where('charge_id', $invoiceId);
if ($labType > 0) {
$builder->where('charge_type', $labType);
}
}
$rows = $builder->orderBy('id', 'DESC')->get()->getResultArray();
@ -3677,18 +3790,31 @@ class Diagnosis extends BaseController
*/
private function findImagingFilesForRequest(int $invoiceId, int $labType, int $labReqId): array
{
if ($invoiceId <= 0 || $labType <= 0 || ! $this->db->tableExists('file_upload_data')) {
if (($invoiceId <= 0 && $labReqId <= 0) || ! $this->db->tableExists('file_upload_data')) {
return [];
}
$builder = $this->db->table('file_upload_data')
->where('isdelete', 0)
->where('charge_id', $invoiceId)
->where('charge_type', $labType);
$fields = $this->db->getFieldNames('file_upload_data');
if ($labReqId > 0 && in_array('repo_id', $fields, true)) {
$hasRepoId = in_array('repo_id', $fields, true);
$builder = $this->db->table('file_upload_data')
->where('isdelete', 0);
if ($labReqId > 0 && $hasRepoId && $invoiceId > 0) {
$builder->groupStart()
->where('repo_id', $labReqId)
->orGroupStart()
->where('charge_id', $invoiceId)
->where('charge_type', $labType)
->groupEnd()
->groupEnd();
} elseif ($labReqId > 0 && $hasRepoId) {
$builder->where('repo_id', $labReqId);
} elseif ($invoiceId > 0) {
$builder->where('charge_id', $invoiceId);
if ($labType > 0) {
$builder->where('charge_type', $labType);
}
}
return $builder->orderBy('id', 'DESC')->get()->getResultArray();

View File

@ -41,13 +41,21 @@ $studyName = trim((string) ($study_name ?? ''));
<a href="<?= esc((string) $row['url']) ?>" target="_blank" rel="noopener">
<img src="<?= esc((string) $row['url']) ?>" alt="Uploaded image" class="img-fluid rounded border" style="width:100%; max-height:220px; object-fit:cover;">
</a>
<?php elseif (!empty($row['is_dicom']) && !empty($row['dicom_preview_url'])): ?>
<?php elseif (!empty($row['is_dicom'])): ?>
<div class="border rounded p-2 bg-light">
<img src="<?= esc((string) $row['dicom_preview_url']) ?>" alt="DICOM preview" class="img-fluid rounded border" style="width:100%; max-height:220px; object-fit:cover;">
<?php if (!empty($row['dicom_preview_url'])): ?>
<img src="<?= esc((string) $row['dicom_preview_url']) ?>" alt="DICOM preview" class="img-fluid rounded border mb-2" style="width:100%; max-height:220px; object-fit:cover;" onerror="this.style.display='none'; if(this.nextElementSibling) this.nextElementSibling.style.display='block';">
<?php endif; ?>
<div style="<?= !empty($row['dicom_preview_url']) ? 'display:none;' : '' ?>" class="text-center py-4 bg-white rounded border mb-2 text-muted">
<div class="display-6 text-primary"><i class="bi bi-file-earmark-medical"></i></div>
<div class="small fw-semibold mt-1">DICOM Imaging File</div>
</div>
<div class="d-flex justify-content-between align-items-center mt-2">
<span class="badge bg-primary-subtle text-primary-emphasis border">DICOM Preview</span>
<span class="badge bg-primary-subtle text-primary-emphasis border">DICOM</span>
<?php if (!empty($row['url'])): ?>
<a href="<?= esc((string) $row['url']) ?>" target="_blank" rel="noopener">Open DICOM</a>
<a href="<?= esc((string) $row['url']) ?>" class="btn btn-sm btn-outline-primary" target="_blank" rel="noopener" download>
<i class="bi bi-download"></i> Download / View
</a>
<?php endif; ?>
</div>
</div>

View File

@ -154,11 +154,17 @@ $editReason = trim((string) ($edit_reason ?? ''));
$tplTitle = trim((string) ($tpl->title ?? ''));
$tplKeywords = trim((string) ($tpl->keywords ?? ''));
$tplCategory = trim((string) ($tpl->impression_cat ?? ''));
$searchBlob = strtolower(trim($tplName . ' ' . $tplTitle . ' ' . $tplKeywords . ' ' . $tplCategory));
$isRelated = !empty($tpl->is_related);
$searchBlob = strtolower(trim($tplName . ' ' . $tplTitle . ' ' . $tplKeywords . ' ' . $tplCategory . ($isRelated ? ' related' : '')));
?>
<div class="template-item mb-2" data-search="<?= esc($searchBlob) ?>">
<a href="javascript:set_template(<?= (int) ($tpl->id ?? 0) ?>)" class="d-block p-2 border rounded text-decoration-none">
<div class="fw-semibold"><?= esc($tplName) ?></div>
<a href="javascript:set_template(<?= (int) ($tpl->id ?? 0) ?>)" class="d-block p-2 border rounded text-decoration-none <?= $isRelated ? 'border-primary bg-primary-subtle bg-opacity-10' : '' ?>">
<div class="d-flex justify-content-between align-items-start gap-1">
<div class="fw-semibold <?= $isRelated ? 'text-primary' : '' ?>"><?= esc($tplName) ?></div>
<?php if ($isRelated): ?>
<span class="badge bg-primary text-white" style="font-size:0.68rem;">Related</span>
<?php endif; ?>
</div>
<?php if ($tplTitle !== ''): ?>
<div class="small text-muted"><?= esc($tplTitle) ?></div>
<?php endif; ?>
@ -175,36 +181,6 @@ $editReason = trim((string) ($edit_reason ?? ''));
<?php endforeach; ?>
<div id="no_templates_msg" style="display:none; color:#888; padding:6px;">No templates found</div>
</div>
<hr class="my-3">
<div class="card border-primary-subtle">
<div class="card-header py-1"><strong>ABDM – Share Report</strong></div>
<div class="card-body p-2">
<div class="mb-2">
<label class="form-label mb-1 small">ABHA Address/ID</label>
<input type="text" class="form-control form-control-sm" id="diag_abha_address" value="<?= esc($report->patient_abha ?? '') ?>" placeholder="Patient ABHA">
</div>
<div class="row g-1 mb-2">
<div class="col-7">
<input type="text" class="form-control form-control-sm" id="diag_abdm_purpose_code" value="CAREMGT" placeholder="Purpose (CAREMGT)">
</div>
<div class="col-5">
<input type="datetime-local" class="form-control form-control-sm" id="diag_abdm_consent_expires_at">
</div>
</div>
<div class="d-flex flex-wrap gap-1 mb-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_diag_abdm_validate_abha">Validate ABHA</button>
<button type="button" class="btn btn-outline-success btn-sm" id="btn_diag_abdm_consent_request">Request Consent</button>
</div>
<div class="mb-2">
<label class="form-label mb-1 small">Consent Handle</label>
<input type="text" class="form-control form-control-sm" id="diag_abdm_consent_handle" placeholder="Auto-filled after consent">
</div>
<button type="button" class="btn btn-outline-warning btn-sm w-100 mb-2" id="btn_diag_abdm_share_report">Share Report (FHIR)</button>
<div class="small text-muted" id="diag_abdm_action_status">Ready</div>
</div>
</div>
</div>
</div>
@ -321,15 +297,17 @@ $editReason = trim((string) ($edit_reason ?? ''));
// Show imaging uploads (gallery)
function showImagingUploadsFromEditor() {
const reqId = document.getElementById('hid_value_req_id').value;
const testName = document.getElementById('hid_value_report_name').value;
const reqId = document.getElementById('hid_value_req_id') ? document.getElementById('hid_value_req_id').value : '';
const testName = document.getElementById('hid_value_report_name') ? document.getElementById('hid_value_report_name').value : '';
if (!reqId) { alert('Request ID not found'); return; }
showImagingUploads(reqId, testName);
}
function showImagingUploads(reqId, testName) {
const labType = document.getElementById('lab_type').value;
fetch(baseUrl + 'diagnosis/imaging-upload-gallery/' + reqId + '/' + labType + '/' + reqId, {
const invoiceId = document.getElementById('invoice_id') ? document.getElementById('invoice_id').value : '';
const labType = document.getElementById('lab_type') ? document.getElementById('lab_type').value : '';
const targetInvoice = invoiceId || reqId;
fetch(baseUrl + 'diagnosis/imaging-upload-gallery/' + targetInvoice + '/' + (labType || '0') + '/' + reqId, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(r => r.text())
@ -534,97 +512,7 @@ $editReason = trim((string) ($edit_reason ?? ''));
}
}
// ─── ABDM Lab/Radiology Report Share ──────────────────────────────
(function initDiagAbdmActions() {
var csrfName = '<?= csrf_token() ?>';
var csrfHash = '<?= csrf_hash() ?>';
var labReqId = <?= (int) ($report->id ?? 0) ?>;
var patientId = <?= (int) ($report->patient_id ?? 0) ?>;
function setStatus(msg, isError) {
var el = document.getElementById('diag_abdm_action_status');
if (!el) { return; }
el.textContent = msg;
el.style.color = isError ? '#dc3545' : '#6c757d';
}
function abdmPost(url, payload, onSuccess) {
setStatus('Sending…');
var data = Object.assign({}, payload);
data[csrfName] = csrfHash;
fetch(url, {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(data).toString()
})
.then(function(r) { return r.json(); })
.then(function(resp) {
if (resp && resp.csrf_hash) { csrfHash = resp.csrf_hash; }
if (resp && resp.csrf_token) { csrfName = resp.csrf_token; }
onSuccess(resp);
})
.catch(function(e) { setStatus('Request failed: ' + e.message, true); });
}
var btnValidate = document.getElementById('btn_diag_abdm_validate_abha');
if (btnValidate) {
btnValidate.addEventListener('click', function() {
var abha = (document.getElementById('diag_abha_address') || {}).value || '';
if (!abha.trim()) { setStatus('Enter ABHA Address first.', true); return; }
abdmPost(baseUrl + 'AbdmGateway/abha_validate', { abha_address: abha, patient_id: patientId }, function(resp) {
setStatus(resp && resp.message ? resp.message : (resp && resp.ok ? 'ABHA valid.' : 'Validation failed.'), !(resp && resp.ok));
});
});
}
var btnConsent = document.getElementById('btn_diag_abdm_consent_request');
if (btnConsent) {
btnConsent.addEventListener('click', function() {
var abha = (document.getElementById('diag_abha_address') || {}).value || '';
var purpose = (document.getElementById('diag_abdm_purpose_code') || {}).value || 'CAREMGT';
var expires = (document.getElementById('diag_abdm_consent_expires_at') || {}).value || '';
if (!abha.trim()) { setStatus('Enter ABHA Address first.', true); return; }
abdmPost(baseUrl + 'AbdmGateway/consent_request', {
abha_address: abha, patient_id: patientId, purpose_code: purpose, expires_at: expires
}, function(resp) {
if (resp && resp.consent_handle) {
var ch = document.getElementById('diag_abdm_consent_handle');
if (ch) { ch.value = resp.consent_handle; }
}
setStatus(resp && resp.message ? resp.message : (resp && resp.ok ? 'Consent requested.' : 'Consent request failed.'), !(resp && resp.ok));
});
});
}
var btnShare = document.getElementById('btn_diag_abdm_share_report');
if (btnShare) {
btnShare.addEventListener('click', function() {
var abha = (document.getElementById('diag_abha_address') || {}).value || '';
var ch = (document.getElementById('diag_abdm_consent_handle') || {}).value || '';
if (!abha.trim()) { setStatus('Enter ABHA Address first.', true); return; }
abdmPost(baseUrl + 'AbdmGateway/share_diagnosis_report_bundle', {
lab_req_id: labReqId, patient_id: patientId, abha_id: abha, consent_handle: ch
}, function(resp) {
if (resp && resp.consent_handle) {
var chEl = document.getElementById('diag_abdm_consent_handle');
if (chEl && !chEl.value) { chEl.value = resp.consent_handle; }
}
var msg = resp && resp.status ? ('Queued: ' + resp.status) : (resp && resp.message ? resp.message : 'Share request sent.');
if (resp && resp.warning) { msg += ' Warning: ' + resp.warning; }
setStatus(msg, !(resp && (resp.ok || resp.queue_id)));
});
});
}
// Default consent expiry +30 days
(function() {
var el = document.getElementById('diag_abdm_consent_expires_at');
if (!el || el.value) { return; }
var d = new Date(); d.setDate(d.getDate() + 30);
el.value = d.toISOString().slice(0, 16);
})();
})();
// ─── end ABDM ─────────────────────────────────────────────────────
// ABDM report sharing panel removed per user requirement
</script>
<?php endif; ?>

View File

@ -94,11 +94,17 @@ $templates = $radiology_ultrasound_template ?? [];
$tplTitle = trim((string) ($tpl->title ?? ''));
$tplKeywords = trim((string) ($tpl->keywords ?? ''));
$tplCategory = trim((string) ($tpl->impression_cat ?? ''));
$searchBlob = strtolower(trim($tplName . ' ' . $tplTitle . ' ' . $tplKeywords . ' ' . $tplCategory));
$isRelated = !empty($tpl->is_related);
$searchBlob = strtolower(trim($tplName . ' ' . $tplTitle . ' ' . $tplKeywords . ' ' . $tplCategory . ($isRelated ? ' related' : '')));
?>
<div class="template-item mb-2" data-search="<?= esc($searchBlob) ?>">
<a href="javascript:set_template(<?= (int) ($tpl->id ?? 0) ?>)" class="d-block p-2 border rounded text-decoration-none">
<div class="fw-semibold"><?= esc($tplName) ?></div>
<a href="javascript:set_template(<?= (int) ($tpl->id ?? 0) ?>)" class="d-block p-2 border rounded text-decoration-none <?= $isRelated ? 'border-primary bg-primary-subtle bg-opacity-10' : '' ?>">
<div class="d-flex justify-content-between align-items-start gap-1">
<div class="fw-semibold <?= $isRelated ? 'text-primary' : '' ?>"><?= esc($tplName) ?></div>
<?php if ($isRelated): ?>
<span class="badge bg-primary text-white" style="font-size:0.68rem;">Related</span>
<?php endif; ?>
</div>
<?php if ($tplTitle !== ''): ?>
<div class="small text-muted"><?= esc($tplTitle) ?></div>
<?php endif; ?>