Checkpoint: pathology panel modal workflow, gateway component search, and ABDM-style component/print editors
This commit is contained in:
parent
e449af8137
commit
78c23af7e5
|
|
@ -65,11 +65,28 @@ class ExportLabTemplates extends BaseCommand
|
|||
orderBy: 'id ASC',
|
||||
);
|
||||
|
||||
// Include loinc_code only if the column exists (migration may not have run on all environments)
|
||||
$repoFields = $this->db->getFieldNames('lab_repo') ?? [];
|
||||
$testFields = $this->db->getFieldNames('lab_tests') ?? [];
|
||||
|
||||
$labRepoCols = ['mstRepoKey', 'Title', 'RTFData', 'HTMLData', 'GrpKey',
|
||||
'IncludeHeader', 'IncludeFooter', 'charge_id'];
|
||||
if (in_array('loinc_code', $repoFields, true)) {
|
||||
$labRepoCols[] = 'loinc_code';
|
||||
}
|
||||
|
||||
$labTestsCols = ['mstTestKey', 'Test', 'TestID', 'Result', 'Options', 'Formula',
|
||||
'VRule', 'VMsg', 'Unit', 'FixedNormals', 'isGenderSpecific', 'FixedNormalsWomen'];
|
||||
foreach (['loinc_code', 'loinc_property', 'loinc_system', 'loinc_scale'] as $col) {
|
||||
if (in_array($col, $testFields, true)) {
|
||||
$labTestsCols[] = $col;
|
||||
}
|
||||
}
|
||||
|
||||
$this->exportTable(
|
||||
filename: '03_lab_repo.sql',
|
||||
table: 'lab_repo',
|
||||
columns: ['mstRepoKey', 'Title', 'RTFData', 'HTMLData', 'GrpKey',
|
||||
'IncludeHeader', 'IncludeFooter', 'charge_id'],
|
||||
columns: $labRepoCols,
|
||||
schema: $this->schemaLabRepo(),
|
||||
orderBy: 'mstRepoKey ASC',
|
||||
);
|
||||
|
|
@ -77,8 +94,7 @@ class ExportLabTemplates extends BaseCommand
|
|||
$this->exportTable(
|
||||
filename: '04_lab_tests.sql',
|
||||
table: 'lab_tests',
|
||||
columns: ['mstTestKey', 'Test', 'TestID', 'Result', 'Options', 'Formula',
|
||||
'VRule', 'VMsg', 'Unit', 'FixedNormals', 'isGenderSpecific', 'FixedNormalsWomen'],
|
||||
columns: $labTestsCols,
|
||||
schema: $this->schemaLabTests(),
|
||||
orderBy: 'mstTestKey ASC',
|
||||
);
|
||||
|
|
@ -244,6 +260,8 @@ CREATE TABLE IF NOT EXISTS `lab_repo` (
|
|||
`IncludeHeader` tinyint(1) DEFAULT '1',
|
||||
`IncludeFooter` tinyint(1) DEFAULT '1',
|
||||
`charge_id` int DEFAULT '0',
|
||||
`loinc_code` varchar(20) DEFAULT NULL,
|
||||
`loinc_synced_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`mstRepoKey`),
|
||||
UNIQUE KEY `Title` (`Title`),
|
||||
KEY `GrpKey` (`GrpKey`),
|
||||
|
|
@ -268,9 +286,15 @@ CREATE TABLE IF NOT EXISTS `lab_tests` (
|
|||
`FixedNormals` varchar(30) NOT NULL DEFAULT '0',
|
||||
`isGenderSpecific` int NOT NULL DEFAULT '0',
|
||||
`FixedNormalsWomen` varchar(30) NOT NULL,
|
||||
`loinc_code` varchar(20) DEFAULT NULL,
|
||||
`loinc_property` varchar(50) DEFAULT NULL,
|
||||
`loinc_system` varchar(50) DEFAULT NULL,
|
||||
`loinc_scale` varchar(20) DEFAULT NULL,
|
||||
`loinc_synced_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`mstTestKey`),
|
||||
UNIQUE KEY `TestID` (`TestID`),
|
||||
KEY `Test` (`Test`)
|
||||
KEY `Test` (`Test`),
|
||||
KEY `idx_lab_tests_loinc` (`loinc_code`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
|
||||
SQL;
|
||||
}
|
||||
|
|
|
|||
403
app/Commands/SyncPathologyLoinc.php
Normal file
403
app/Commands/SyncPathologyLoinc.php
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Libraries\Abdm\EAtriaBridgeConnector;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Spark command: abdm:sync-pathology-loinc
|
||||
*
|
||||
* Fetches LOINC codes from the Bridge API pathology terminology endpoints
|
||||
* and maps them to the local lab_repo (panel) and lab_tests (component) tables.
|
||||
*
|
||||
* Matching strategy:
|
||||
* 1. For lab_repo: case-insensitive exact match on Title vs bridge test_name,
|
||||
* then normalised fuzzy match (strip spaces/punctuation, compare words).
|
||||
* 2. For lab_tests: same matching logic on Test vs component_test_name,
|
||||
* scoped to the matched panel.
|
||||
*
|
||||
* Usage:
|
||||
* php spark abdm:sync-pathology-loinc
|
||||
* php spark abdm:sync-pathology-loinc --dry-run
|
||||
* php spark abdm:sync-pathology-loinc --since=2025-01-01T00:00:00Z
|
||||
* php spark abdm:sync-pathology-loinc --category=PATHOLOGY
|
||||
* php spark abdm:sync-pathology-loinc --limit=500
|
||||
*/
|
||||
class SyncPathologyLoinc extends BaseCommand
|
||||
{
|
||||
protected $group = 'Bridge';
|
||||
protected $name = 'abdm:sync-pathology-loinc';
|
||||
protected $description = 'Sync LOINC codes from Bridge API into lab_tests and lab_repo tables.';
|
||||
protected $usage = 'abdm:sync-pathology-loinc [options]';
|
||||
protected $arguments = [];
|
||||
protected $options = [
|
||||
'--dry-run' => 'Print matches without updating the database.',
|
||||
'--since' => 'ISO-8601 datetime for incremental sync (e.g. 2025-01-01T00:00:00Z).',
|
||||
'--category' => 'Filter bridge masters by sub_category (PATHOLOGY|BIOPSY). Default: all.',
|
||||
'--limit' => 'Items per API page request. Default: 200.',
|
||||
];
|
||||
|
||||
/** @var \CodeIgniter\Database\BaseConnection */
|
||||
private $db;
|
||||
|
||||
/** @var bool */
|
||||
private bool $dryRun = false;
|
||||
|
||||
/** @var int */
|
||||
private int $masterMatched = 0;
|
||||
private int $masterSkipped = 0;
|
||||
private int $compMatched = 0;
|
||||
private int $compSkipped = 0;
|
||||
|
||||
public function run(array $params): void
|
||||
{
|
||||
$this->dryRun = (bool) CLI::getOption('dry-run');
|
||||
$since = (string) (CLI::getOption('since') ?? '');
|
||||
$category = (string) (CLI::getOption('category') ?? '');
|
||||
$limit = (int) (CLI::getOption('limit') ?? 200);
|
||||
|
||||
if ($limit <= 0) {
|
||||
$limit = 200;
|
||||
}
|
||||
|
||||
$this->db = \Config\Database::connect();
|
||||
|
||||
if ($this->dryRun) {
|
||||
CLI::write('[DRY-RUN] No database changes will be made.', 'yellow');
|
||||
}
|
||||
|
||||
CLI::write('=== Syncing Bridge Pathology LOINC Codes ===', 'cyan');
|
||||
|
||||
try {
|
||||
$connector = new EAtriaBridgeConnector();
|
||||
} catch (\Throwable $e) {
|
||||
CLI::error('Failed to initialise Bridge connector: ' . $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load local tables once
|
||||
$localRepos = $this->loadLocalRepos();
|
||||
$localTests = $this->loadLocalTests();
|
||||
|
||||
CLI::write('Local lab_repo records : ' . count($localRepos));
|
||||
CLI::write('Local lab_tests records: ' . count($localTests));
|
||||
CLI::write('');
|
||||
|
||||
// --- Step 1: Fetch all panel masters and match to lab_repo ---
|
||||
CLI::write('--- Phase 1: Panel Masters → lab_repo ---', 'yellow');
|
||||
$masters = $this->fetchAllPages(
|
||||
$connector,
|
||||
'masters',
|
||||
['category' => $category, 'since' => $since, 'limit' => $limit]
|
||||
);
|
||||
CLI::write('Bridge masters fetched: ' . count($masters));
|
||||
|
||||
// Build lookup: normalised name → master item
|
||||
$masterByName = [];
|
||||
foreach ($masters as $item) {
|
||||
$key = $this->normalise((string) ($item['test_name'] ?? ''));
|
||||
if ($key !== '') {
|
||||
$masterByName[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($localRepos as $repo) {
|
||||
$this->matchAndUpdateRepo($repo, $masterByName);
|
||||
}
|
||||
|
||||
CLI::write("Panel matches: {$this->masterMatched} | skipped (no match): {$this->masterSkipped}");
|
||||
CLI::write('');
|
||||
|
||||
// --- Step 2: For each matched panel, fetch components → lab_tests ---
|
||||
CLI::write('--- Phase 2: Components → lab_tests ---', 'yellow');
|
||||
|
||||
// Group local tests by repo id for fast lookup
|
||||
$testsByRepo = [];
|
||||
foreach ($localTests as $test) {
|
||||
$rid = (int) ($test['mstRepoKey'] ?? 0);
|
||||
$testsByRepo[$rid][] = $test;
|
||||
}
|
||||
|
||||
foreach ($localRepos as $repo) {
|
||||
$panelName = (string) ($repo['Title'] ?? '');
|
||||
if ($panelName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$components = $this->fetchAllPages(
|
||||
$connector,
|
||||
'components',
|
||||
['parent_test' => $panelName, 'since' => $since, 'limit' => $limit]
|
||||
);
|
||||
|
||||
if (empty($components)) {
|
||||
// Try with the bridge display_name if we synced one
|
||||
$bridgeName = (string) ($repo['_bridge_test_name'] ?? '');
|
||||
if ($bridgeName !== '' && $bridgeName !== $panelName) {
|
||||
$components = $this->fetchAllPages(
|
||||
$connector,
|
||||
'components',
|
||||
['parent_test' => $bridgeName, 'since' => $since, 'limit' => $limit]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($components)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$repoTests = $testsByRepo[(int) $repo['mstRepoKey']] ?? [];
|
||||
if (empty($repoTests)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build lookup by normalised component name
|
||||
$compByName = [];
|
||||
foreach ($components as $comp) {
|
||||
$key = $this->normalise((string) ($comp['component_test_name'] ?? ''));
|
||||
if ($key !== '') {
|
||||
$compByName[$key] = $comp;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($repoTests as $test) {
|
||||
$this->matchAndUpdateTest($test, $compByName);
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write("Component matches: {$this->compMatched} | skipped (no match): {$this->compSkipped}");
|
||||
CLI::write('');
|
||||
CLI::write('Done.', 'green');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Match + Update helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** @param array<string, mixed> $repo */
|
||||
private function matchAndUpdateRepo(array $repo, array $masterByName): void
|
||||
{
|
||||
$title = (string) ($repo['Title'] ?? '');
|
||||
if ($title === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $this->normalise($title);
|
||||
$item = $masterByName[$key] ?? null;
|
||||
|
||||
// Fuzzy fallback: try partial word match
|
||||
if ($item === null) {
|
||||
$item = $this->fuzzyMatch($title, $masterByName);
|
||||
}
|
||||
|
||||
if ($item === null) {
|
||||
$this->masterSkipped++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$loincCode = (string) ($item['code'] ?? '');
|
||||
$bridgeTestName = (string) ($item['test_name'] ?? '');
|
||||
|
||||
// Stash bridge name so phase 2 can reuse it
|
||||
$repo['_bridge_test_name'] = $bridgeTestName;
|
||||
|
||||
if ($this->dryRun) {
|
||||
CLI::write(" [DRY] lab_repo #{$repo['mstRepoKey']} '{$title}' → LOINC: {$loincCode} (bridge: '{$bridgeTestName}')");
|
||||
} else {
|
||||
$this->db->table('lab_repo')->where('mstRepoKey', $repo['mstRepoKey'])->update([
|
||||
'loinc_code' => $loincCode,
|
||||
'loinc_synced_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->masterMatched++;
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $test */
|
||||
private function matchAndUpdateTest(array $test, array $compByName): void
|
||||
{
|
||||
$testName = (string) ($test['Test'] ?? '');
|
||||
if ($testName === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $this->normalise($testName);
|
||||
$comp = $compByName[$key] ?? null;
|
||||
|
||||
if ($comp === null) {
|
||||
$comp = $this->fuzzyMatch($testName, $compByName);
|
||||
}
|
||||
|
||||
if ($comp === null) {
|
||||
$this->compSkipped++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$loincCode = (string) ($comp['component_code'] ?? '');
|
||||
$loincProperty = (string) ($comp['component_property'] ?? '');
|
||||
$loincSystem = (string) ($comp['component_system'] ?? '');
|
||||
$loincScale = (string) ($comp['component_scale'] ?? '');
|
||||
|
||||
if ($this->dryRun) {
|
||||
CLI::write(
|
||||
" [DRY] lab_tests #{$test['mstTestKey']} '{$testName}' → " .
|
||||
"LOINC: {$loincCode} | prop: {$loincProperty} | sys: {$loincSystem} | scale: {$loincScale}"
|
||||
);
|
||||
} else {
|
||||
$this->db->table('lab_tests')->where('mstTestKey', $test['mstTestKey'])->update([
|
||||
'loinc_code' => $loincCode,
|
||||
'loinc_property' => $loincProperty,
|
||||
'loinc_system' => $loincSystem,
|
||||
'loinc_scale' => $loincScale,
|
||||
'loinc_synced_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->compMatched++;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// API pagination
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch all pages from the Bridge API for either 'masters' or 'components'.
|
||||
*
|
||||
* @param array<string, mixed> $opts
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function fetchAllPages(EAtriaBridgeConnector $connector, string $type, array $opts): array
|
||||
{
|
||||
$limit = (int) ($opts['limit'] ?? 200);
|
||||
$since = (string) ($opts['since'] ?? '');
|
||||
$all = [];
|
||||
$offset = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
if ($type === 'masters') {
|
||||
$resp = $connector->pathologyMasters(
|
||||
(string) ($opts['category'] ?? ''),
|
||||
$limit,
|
||||
$offset,
|
||||
$since
|
||||
);
|
||||
} else {
|
||||
$resp = $connector->pathologyComponents(
|
||||
(string) ($opts['parent_test'] ?? ''),
|
||||
$limit,
|
||||
$offset,
|
||||
$since
|
||||
);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
CLI::error("API error ({$type}, offset={$offset}): " . $e->getMessage());
|
||||
break;
|
||||
}
|
||||
|
||||
if (! empty($resp['error']) || (isset($resp['success']) && $resp['success'] === false)) {
|
||||
CLI::write(' API error response: ' . json_encode($resp), 'red');
|
||||
break;
|
||||
}
|
||||
|
||||
$items = $resp['items'] ?? $resp['data'] ?? [];
|
||||
if (! is_array($items) || $items === []) {
|
||||
break;
|
||||
}
|
||||
|
||||
$all = array_merge($all, $items);
|
||||
$offset += count($items);
|
||||
|
||||
// If fewer items returned than limit, we've reached the end
|
||||
if (count($items) < $limit) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return $all;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Local DB loaders
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
private function loadLocalRepos(): array
|
||||
{
|
||||
return $this->db->table('lab_repo')
|
||||
->select('mstRepoKey, Title, loinc_code')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
private function loadLocalTests(): array
|
||||
{
|
||||
// Join via lab_repotests to know which repo each test belongs to.
|
||||
// A test may appear in multiple repos; we update it once (last-wins is fine).
|
||||
return $this->db->table('lab_tests lt')
|
||||
->select('lt.mstTestKey, lt.Test, lt.loinc_code, lrt.mstRepoKey')
|
||||
->join('lab_repotests lrt', 'lrt.mstTestKey = lt.mstTestKey', 'left')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Name matching helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private function normalise(string $name): string
|
||||
{
|
||||
$name = strtolower($name);
|
||||
// Remove parenthetical suffixes like "(CBC)" or "[Panel]"
|
||||
$name = preg_replace('/[\(\[\{][^\)\]\}]*[\)\]\}]/', '', $name);
|
||||
// Strip punctuation except hyphen
|
||||
$name = preg_replace('/[^a-z0-9\- ]/', '', $name);
|
||||
// Collapse multiple spaces
|
||||
$name = preg_replace('/\s+/', ' ', $name);
|
||||
|
||||
return trim($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fuzzy match: find the entry in $candidates whose normalised key shares
|
||||
* the highest proportion of words with the $name.
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $candidates [normalisedKey => item]
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function fuzzyMatch(string $name, array $candidates): ?array
|
||||
{
|
||||
$words = array_filter(explode(' ', $this->normalise($name)));
|
||||
if (empty($words)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$best = null;
|
||||
$bestScore = 0.0;
|
||||
|
||||
foreach ($candidates as $key => $item) {
|
||||
$keyWords = array_filter(explode(' ', $key));
|
||||
if (empty($keyWords)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$common = count(array_intersect($words, $keyWords));
|
||||
$total = count(array_unique(array_merge($words, $keyWords)));
|
||||
$score = $total > 0 ? $common / $total : 0.0;
|
||||
|
||||
if ($score > $bestScore) {
|
||||
$bestScore = $score;
|
||||
$best = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Require at least 50% Jaccard similarity
|
||||
return $bestScore >= 0.5 ? $best : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -825,12 +825,24 @@ $routes->get('Lab_Admin/print_pdf_create/(:num)/(:num)/(:num)', 'Diagnosis::prin
|
|||
$routes->get('Lab_Admin/print_pdf_create/(:num)/(:num)/(:num)/(:num)', 'Diagnosis::printPdfCreate/$1/$2/$3/$4');
|
||||
$routes->post('Lab_Admin/report_update', 'Setting\Template::report_update');
|
||||
$routes->post('Lab_Admin/report_insert', 'Setting\Template::report_insert');
|
||||
$routes->post('Lab_Admin/report_delete/(:num)', 'Setting\Template::report_delete/$1');
|
||||
$routes->post('Lab_Admin/report_ultrasound_insert/(:num)', 'Setting\Template::report_ultrasound_insert/$1');
|
||||
$routes->post('Lab_Admin/report_ultrasound_update/(:num)', 'Setting\Template::report_ultrasound_update/$1');
|
||||
$routes->post('Lab_Admin/add_test_repo/(:num)/(:num)', 'Setting\Template::add_test_repo/$1/$2');
|
||||
$routes->post('Lab_Admin/remove_test_item/(:num)/(:num)', 'Setting\Template::remove_test_item/$1/$2');
|
||||
$routes->post('Lab_Admin/test_item_search', 'Setting\Template::test_item_search');
|
||||
|
||||
// LOINC Mapping Admin
|
||||
$routes->get('Lab_Admin/loinc_panel', 'Setting\Template::loincPanel');
|
||||
$routes->post('Lab_Admin/loinc_sync', 'Setting\Template::loincSync');
|
||||
$routes->post('Lab_Admin/loinc_update', 'Setting\Template::loincUpdate');
|
||||
$routes->get('Lab_Admin/pathology_masters_search', 'Setting\Template::pathologyMastersSearch');
|
||||
$routes->get('Lab_Admin/pathology_component_masters_search', 'Setting\Template::pathologyComponentMastersSearch');
|
||||
$routes->get('Lab_Admin/pathology_master_template', 'Setting\Template::pathologyMasterTemplate');
|
||||
$routes->post('Lab_Admin/pathology_master_apply_panel', 'Setting\Template::pathologyMasterApplyPanel');
|
||||
$routes->post('Lab_Admin/pathology_master_add_component', 'Setting\Template::pathologyMasterAddComponent');
|
||||
$routes->get('Lab_Admin/pathology_bridge_debug', 'Setting\Template::pathologyBridgeDebug');
|
||||
|
||||
$routes->match(['get', 'post'], 'setting/template/discharge_templates', 'Setting\Template::discharge_templates');
|
||||
$routes->get('setting/template/discharge_templates/delete/(:num)', 'Setting\Template::discharge_template_delete/$1');
|
||||
$routes->match(['get', 'post'], 'setting/template/ipd_document_templates', 'Setting\Template::ipd_document_templates');
|
||||
|
|
|
|||
|
|
@ -144,15 +144,39 @@ class AbdmGateway extends BaseController
|
|||
]);
|
||||
|
||||
$queueId = null;
|
||||
$gatewayConsentId = '';
|
||||
try {
|
||||
$result = $this->connector->requestConsent($patientId, $abhaId, $purposeCode, $expiresAt, $consentHandle, $rawPayload);
|
||||
$queueId = $result['queue_id'] ?? null;
|
||||
$gatewayConsentId = trim((string) ($result['gateway_consent_id'] ?? $result['consent_id'] ?? ''));
|
||||
|
||||
if ($gatewayConsentId !== '') {
|
||||
$tableFields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
$updateData = [
|
||||
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
||||
];
|
||||
|
||||
// Persist gateway consent id when a compatible column exists.
|
||||
if (in_array('consent_id', $tableFields, true)) {
|
||||
$updateData['consent_id'] = $gatewayConsentId;
|
||||
} elseif (in_array('gateway_consent_id', $tableFields, true)) {
|
||||
$updateData['gateway_consent_id'] = $gatewayConsentId;
|
||||
}
|
||||
|
||||
$rawPayload['gateway_consent_id'] = $gatewayConsentId;
|
||||
$updateData['raw_payload_json'] = (string) json_encode($rawPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$this->db->table('abdm_consent_records')
|
||||
->where('consent_handle', $consentHandle)
|
||||
->update($updateData);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'consent_handle' => $consentHandle,
|
||||
'gateway_consent_id' => $gatewayConsentId !== '' ? $gatewayConsentId : null,
|
||||
'queue_id' => $queueId,
|
||||
'status' => 'requested',
|
||||
]);
|
||||
|
|
@ -177,6 +201,7 @@ class AbdmGateway extends BaseController
|
|||
$handle = trim((string) $payload['consent_handle']);
|
||||
$status = trim((string) ($payload['consent_status'] ?? 'approved'));
|
||||
$expiresAt = trim((string) ($payload['expires_at'] ?? ''));
|
||||
$incomingConsentId = trim((string) ($payload['consent_id'] ?? $payload['gateway_consent_id'] ?? ''));
|
||||
$now = Time::now('Asia/Kolkata')->toDateTimeString();
|
||||
|
||||
$existing = $this->db->table('abdm_consent_records')
|
||||
|
|
@ -229,11 +254,25 @@ class AbdmGateway extends BaseController
|
|||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
if ($incomingConsentId !== '') {
|
||||
$tableFields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
if (in_array('consent_id', $tableFields, true)) {
|
||||
$update['consent_id'] = $incomingConsentId;
|
||||
} elseif (in_array('gateway_consent_id', $tableFields, true)) {
|
||||
$update['gateway_consent_id'] = $incomingConsentId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->table('abdm_consent_records')
|
||||
->where('consent_handle', $handle)
|
||||
->update($update);
|
||||
|
||||
return $this->response->setJSON(['ok' => 1, 'consent_handle' => $handle, 'status' => $incomingStatus]);
|
||||
return $this->response->setJSON([
|
||||
'ok' => 1,
|
||||
'consent_handle' => $handle,
|
||||
'status' => $incomingStatus,
|
||||
'gateway_consent_id' => $incomingConsentId !== '' ? $incomingConsentId : null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function scanShareLookup()
|
||||
|
|
@ -589,6 +628,7 @@ class AbdmGateway extends BaseController
|
|||
'patient_id' => $patientId,
|
||||
'abha_id' => $abhaId,
|
||||
'consent_handle' => (string) ($consent['consent_handle'] ?? ''),
|
||||
'consent_id' => $this->resolveConsentExternalId($consent),
|
||||
'bundle_type' => (string) ($bundleRow['bundle_type'] ?? 'MedicationRequestBundle'),
|
||||
'bundle' => $bundle,
|
||||
];
|
||||
|
|
@ -634,6 +674,7 @@ class AbdmGateway extends BaseController
|
|||
'ok' => 1,
|
||||
'queue_id' => $queueId,
|
||||
'consent_handle' => (string) ($consent['consent_handle'] ?? ''),
|
||||
'gateway_consent_id' => $this->resolveConsentExternalId($consent) !== '' ? $this->resolveConsentExternalId($consent) : null,
|
||||
'status' => 'queued',
|
||||
]);
|
||||
}
|
||||
|
|
@ -896,6 +937,22 @@ class AbdmGateway extends BaseController
|
|||
'report_html' => trim((string) ($labReq->Report_Data ?? '')),
|
||||
];
|
||||
|
||||
// ── Load LOINC code for the panel from lab_repo ───────────────────────
|
||||
$labRepoRow = $this->db->table('lab_request lr')
|
||||
->select('lr.lab_repo_id, repo.loinc_code AS repo_loinc_code, repo.Title')
|
||||
->join('lab_repo repo', 'repo.mstRepoKey = lr.lab_repo_id', 'left')
|
||||
->where('lr.id', $labReqId)
|
||||
->get(1)
|
||||
->getRowArray() ?? [];
|
||||
|
||||
$repoLoincCode = trim((string) ($labRepoRow['repo_loinc_code'] ?? ''));
|
||||
if ($repoLoincCode !== '') {
|
||||
$diagnosticReport['loinc_code'] = $repoLoincCode;
|
||||
}
|
||||
|
||||
// ── Build structured observations from lab_request_item + lab_tests ───
|
||||
$observations = $this->buildLabObservations($labReqId, (string) ($labReq->status ?? '0'));
|
||||
|
||||
$organization = $hospitalProfile['name'] !== ''
|
||||
? ['name' => $hospitalProfile['name'], 'hfr_id' => $hospitalProfile['hfr_id']]
|
||||
: null;
|
||||
|
|
@ -907,7 +964,7 @@ class AbdmGateway extends BaseController
|
|||
'period_start' => $reportedAt,
|
||||
];
|
||||
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, [], null, $organization, $encounter);
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, $observations, null, $organization, $encounter);
|
||||
$bundleJson = (string) json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
// ── Store health_record ───────────────────────────────────────────────
|
||||
|
|
@ -1990,6 +2047,142 @@ class AbdmGateway extends BaseController
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load lab_request_item rows for a lab request and build the structured
|
||||
* observations array expected by FhirR4Builder::buildLabReportBundle().
|
||||
*
|
||||
* Each element contains:
|
||||
* test_name, loinc_code, value_type (quantity|string), value,
|
||||
* unit, ucum_code, ref_low, ref_high, interpretation, status
|
||||
*
|
||||
* @param int $labReqId lab_request.id
|
||||
* @param string $status raw status from lab_request.status
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function buildLabObservations(int $labReqId, string $status): array
|
||||
{
|
||||
if (! $this->db->tableExists('lab_request_item') || ! $this->db->tableExists('lab_tests')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = $this->db->table('lab_request_item lri')
|
||||
->select(
|
||||
'lri.lab_test_id, lri.lab_test_value, lri.lab_test_remark,' .
|
||||
' lt.Test, lt.Unit, lt.FixedNormals, lt.FixedNormalsWomen,' .
|
||||
' lt.loinc_code, lt.loinc_scale, lt.loinc_system, lt.loinc_property,' .
|
||||
' lt.isGenderSpecific'
|
||||
)
|
||||
->join('lab_tests lt', 'lt.mstTestKey = lri.lab_test_id', 'left')
|
||||
->where('lri.lab_request_id', $labReqId)
|
||||
->orderBy('lri.id', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$obsStatus = ($status == '1') ? 'final' : 'preliminary';
|
||||
$observations = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$rawValue = trim((string) ($row['lab_test_value'] ?? ''));
|
||||
if ($rawValue === '' || strtolower($rawValue) === 'n/a') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$testName = trim((string) ($row['Test'] ?? ''));
|
||||
$loincCode = trim((string) ($row['loinc_code'] ?? ''));
|
||||
$unit = trim((string) ($row['Unit'] ?? ''));
|
||||
$scale = strtolower(trim((string) ($row['loinc_scale'] ?? '')));
|
||||
|
||||
// Determine value type: Quantitative = numeric value
|
||||
$isNumeric = is_numeric($rawValue) && $scale !== 'nom' && $scale !== 'ord';
|
||||
$valueType = $isNumeric ? 'quantity' : 'string';
|
||||
|
||||
// Parse reference range from "low-high" format (e.g. "12.00-16.00")
|
||||
$refLow = $refHigh = '';
|
||||
$normals = trim((string) ($row['FixedNormals'] ?? ''));
|
||||
if ($normals !== '' && str_contains($normals, '-')) {
|
||||
$parts = explode('-', $normals, 2);
|
||||
if (count($parts) === 2 && is_numeric(trim($parts[0])) && is_numeric(trim($parts[1]))) {
|
||||
$refLow = trim($parts[0]);
|
||||
$refHigh = trim($parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Interpretation: H/L/N based on value vs reference range
|
||||
$interpretation = 'N';
|
||||
if ($isNumeric && $refLow !== '' && $refHigh !== '') {
|
||||
$fVal = (float) $rawValue;
|
||||
if ($fVal < (float) $refLow) {
|
||||
$interpretation = 'L';
|
||||
} elseif ($fVal > (float) $refHigh) {
|
||||
$interpretation = 'H';
|
||||
}
|
||||
}
|
||||
|
||||
$obs = [
|
||||
'test_name' => $testName !== '' ? $testName : ('Test-' . $row['lab_test_id']),
|
||||
'loinc_code' => $loincCode,
|
||||
'value_type' => $valueType,
|
||||
'value' => $rawValue,
|
||||
'unit' => $unit,
|
||||
'ucum_code' => $this->unitToUcum($unit),
|
||||
'ref_low' => $refLow,
|
||||
'ref_high' => $refHigh,
|
||||
'interpretation' => $interpretation,
|
||||
'status' => $obsStatus,
|
||||
];
|
||||
|
||||
if ($row['lab_test_remark'] !== '' && $row['lab_test_remark'] !== null) {
|
||||
$obs['remark'] = trim((string) $row['lab_test_remark']);
|
||||
}
|
||||
|
||||
$observations[] = $obs;
|
||||
}
|
||||
|
||||
return $observations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort map of common lab units to UCUM codes.
|
||||
* Returns the original unit string when no UCUM equivalent is known.
|
||||
*/
|
||||
private function unitToUcum(string $unit): string
|
||||
{
|
||||
$unit = trim($unit);
|
||||
if ($unit === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$map = [
|
||||
'g/dl' => 'g/dL',
|
||||
'g/dL' => 'g/dL',
|
||||
'mg/dl' => 'mg/dL',
|
||||
'mg/dL' => 'mg/dL',
|
||||
'mg/l' => 'mg/L',
|
||||
'mmol/l' => 'mmol/L',
|
||||
'umol/l' => 'umol/L',
|
||||
'u/l' => 'U/L',
|
||||
'iu/l' => 'IU/L',
|
||||
'iu/ml' => 'IU/mL',
|
||||
'cells/cumm'=> '10*3/uL',
|
||||
'cells/mm3' => '10*3/uL',
|
||||
'10^9/l' => '10*9/L',
|
||||
'10^3/ul' => '10*3/uL',
|
||||
'fl' => 'fL',
|
||||
'pg' => 'pg',
|
||||
'%' => '%',
|
||||
'sec' => 's',
|
||||
'min' => 'min',
|
||||
'mmhg' => 'mm[Hg]',
|
||||
'meq/l' => 'meq/L',
|
||||
];
|
||||
|
||||
return $map[strtolower($unit)] ?? $unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lazily-constructed AbdmAuditService instance.
|
||||
*/
|
||||
|
|
@ -2024,21 +2217,52 @@ class AbdmGateway extends BaseController
|
|||
}
|
||||
|
||||
$now = Time::now('Asia/Kolkata')->toDateTimeString();
|
||||
$consentFields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
|
||||
$builder = $this->db->table('abdm_consent_records')
|
||||
->where('patient_id', $patientId)
|
||||
->where('abha_id', $abhaId)
|
||||
->where('status', 'GRANTED')
|
||||
->where('expiry_date >=', $now);
|
||||
->whereIn('consent_status', ['approved', 'granted'])
|
||||
->groupStart()
|
||||
->where('expires_at IS NULL', null, false)
|
||||
->orWhere('expires_at >=', $now)
|
||||
->groupEnd();
|
||||
|
||||
if ($consentHandle !== '') {
|
||||
$builder->where('consent_handle', $consentHandle);
|
||||
$builder->groupStart()->where('consent_handle', $consentHandle);
|
||||
if (in_array('consent_id', $consentFields, true)) {
|
||||
$builder->orWhere('consent_id', $consentHandle);
|
||||
}
|
||||
if (in_array('gateway_consent_id', $consentFields, true)) {
|
||||
$builder->orWhere('gateway_consent_id', $consentHandle);
|
||||
}
|
||||
$builder->groupEnd();
|
||||
}
|
||||
|
||||
$row = $builder->orderBy('id', 'DESC')->get(1)->getRowArray();
|
||||
return ! empty($row) ? $row : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the consent id expected by gateway: consent_id/gateway_consent_id fallback to consent_handle.
|
||||
*
|
||||
* @param array<string, mixed> $consent
|
||||
*/
|
||||
private function resolveConsentExternalId(array $consent): string
|
||||
{
|
||||
$consentId = trim((string) ($consent['consent_id'] ?? ''));
|
||||
if ($consentId !== '') {
|
||||
return $consentId;
|
||||
}
|
||||
|
||||
$gatewayConsentId = trim((string) ($consent['gateway_consent_id'] ?? ''));
|
||||
if ($gatewayConsentId !== '') {
|
||||
return $gatewayConsentId;
|
||||
}
|
||||
|
||||
return trim((string) ($consent['consent_handle'] ?? ''));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// M1 ABHA OTP Flows
|
||||
// Calls EAtriaBridgeConnector synchronously — requires eatria_bridge connector.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
/**
|
||||
* Adds consent id tracking columns to abdm_consent_records.
|
||||
*
|
||||
* gateway_consent_id captures consent id returned by the bridge/gateway so
|
||||
* downstream share/audit flows can map consent_handle <-> gateway consent id.
|
||||
*/
|
||||
class AddConsentIdsToAbdmConsentRecords extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_consent_records')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
|
||||
if (! in_array('consent_id', $fields, true)) {
|
||||
$this->forge->addColumn('abdm_consent_records', [
|
||||
'consent_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
'after' => 'consent_handle',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
if (! in_array('gateway_consent_id', $fields, true)) {
|
||||
$this->forge->addColumn('abdm_consent_records', [
|
||||
'gateway_consent_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
'after' => 'consent_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$indexes = $this->db->getIndexData('abdm_consent_records') ?? [];
|
||||
$indexNames = array_map(static fn ($i) => (string) ($i->name ?? ''), $indexes);
|
||||
|
||||
if (! in_array('idx_abdm_consent_id', $indexNames, true)) {
|
||||
$this->db->query('CREATE INDEX idx_abdm_consent_id ON abdm_consent_records (consent_id)');
|
||||
}
|
||||
|
||||
if (! in_array('idx_abdm_gateway_consent_id', $indexNames, true)) {
|
||||
$this->db->query('CREATE INDEX idx_abdm_gateway_consent_id ON abdm_consent_records (gateway_consent_id)');
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! $this->db->tableExists('abdm_consent_records')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
|
||||
if (in_array('gateway_consent_id', $fields, true)) {
|
||||
$this->forge->dropColumn('abdm_consent_records', 'gateway_consent_id');
|
||||
}
|
||||
|
||||
$fields = $this->db->getFieldNames('abdm_consent_records') ?? [];
|
||||
if (in_array('consent_id', $fields, true)) {
|
||||
$this->forge->dropColumn('abdm_consent_records', 'consent_id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
/**
|
||||
* Adds LOINC coding fields to pathology master tables so that
|
||||
* lab test results can be published as structured FHIR Observations
|
||||
* with standard LOINC codes for ABDM DiagnosticReportRecord.
|
||||
*
|
||||
* lab_tests → loinc_code, loinc_property, loinc_system, loinc_scale
|
||||
* lab_repo → loinc_code (LOINC panel code for the DiagnosticReport.code)
|
||||
*/
|
||||
class AddLoincToPathologyTables extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->addLabTestsLoincColumns();
|
||||
$this->addLabRepoLoincColumn();
|
||||
}
|
||||
|
||||
private function addLabTestsLoincColumns(): void
|
||||
{
|
||||
if (! $this->db->tableExists('lab_tests')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = $this->db->getFieldNames('lab_tests') ?? [];
|
||||
$add = [];
|
||||
|
||||
if (! in_array('loinc_code', $existing, true)) {
|
||||
$add['loinc_code'] = [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'Unit',
|
||||
];
|
||||
}
|
||||
if (! in_array('loinc_property', $existing, true)) {
|
||||
$add['loinc_property'] = [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'loinc_code',
|
||||
];
|
||||
}
|
||||
if (! in_array('loinc_system', $existing, true)) {
|
||||
$add['loinc_system'] = [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'loinc_property',
|
||||
];
|
||||
}
|
||||
if (! in_array('loinc_scale', $existing, true)) {
|
||||
$add['loinc_scale'] = [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'loinc_system',
|
||||
];
|
||||
}
|
||||
if (! in_array('loinc_synced_at', $existing, true)) {
|
||||
$add['loinc_synced_at'] = [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'loinc_scale',
|
||||
];
|
||||
}
|
||||
|
||||
if ($add !== []) {
|
||||
$this->forge->addColumn('lab_tests', $add);
|
||||
}
|
||||
|
||||
// Index for fast LOINC lookup
|
||||
try {
|
||||
$indexes = $this->db->query('SHOW INDEX FROM lab_tests')->getResultArray();
|
||||
$hasIdx = false;
|
||||
foreach ($indexes as $idx) {
|
||||
if (strcasecmp((string) ($idx['Key_name'] ?? ''), 'idx_lab_tests_loinc') === 0) {
|
||||
$hasIdx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $hasIdx && in_array('loinc_code', $this->db->getFieldNames('lab_tests') ?? [], true)) {
|
||||
$this->db->query('ALTER TABLE lab_tests ADD INDEX idx_lab_tests_loinc (loinc_code)');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Non-blocking index creation
|
||||
}
|
||||
}
|
||||
|
||||
private function addLabRepoLoincColumn(): void
|
||||
{
|
||||
if (! $this->db->tableExists('lab_repo')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing = $this->db->getFieldNames('lab_repo') ?? [];
|
||||
|
||||
if (! in_array('loinc_code', $existing, true)) {
|
||||
$this->forge->addColumn('lab_repo', [
|
||||
'loinc_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'Title',
|
||||
],
|
||||
]);
|
||||
}
|
||||
if (! in_array('loinc_synced_at', $existing, true)) {
|
||||
$this->forge->addColumn('lab_repo', [
|
||||
'loinc_synced_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
'after' => 'loinc_code',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach (['loinc_code', 'loinc_property', 'loinc_system', 'loinc_scale', 'loinc_synced_at'] as $col) {
|
||||
try {
|
||||
if ($this->db->tableExists('lab_tests') &&
|
||||
in_array($col, $this->db->getFieldNames('lab_tests') ?? [], true)) {
|
||||
$this->forge->dropColumn('lab_tests', $col);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['loinc_code', 'loinc_synced_at'] as $col) {
|
||||
try {
|
||||
if ($this->db->tableExists('lab_repo') &&
|
||||
in_array($col, $this->db->getFieldNames('lab_repo') ?? [], true)) {
|
||||
$this->forge->dropColumn('lab_repo', $col);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->db->query('ALTER TABLE lab_tests DROP INDEX idx_lab_tests_loinc');
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,17 +11,18 @@ CREATE TABLE IF NOT EXISTS `lab_rgroups` (
|
|||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
INSERT IGNORE INTO `lab_rgroups` (`mstRGrpKey`, `RepoGrp`, `PreFix`, `Suffix`, `LastNo`, `sort_order`, `Notes`) VALUES
|
||||
('1', 'BIO CHEMISTRY', 'BIO', '', '0', '2', ''),
|
||||
('2', 'Biopsy', 'BP', '', '0', '3', ''),
|
||||
('3', 'CLINICAL PATHOLOGY', 'CP', '', '0', '4', ''),
|
||||
('4', 'MICRO BIOLOGY', 'CUL', '', '0', '5', ''),
|
||||
('5', 'General', 'GEN', '', '1', '6', ''),
|
||||
('6', 'HAEMATOLOGY', 'HAE', '', '0', '1', ''),
|
||||
('8', 'Urine', 'US', '', '0', '8', ''),
|
||||
('9', 'Fluid Examination', 'FE', '0', '0', '9', '0'),
|
||||
('10', 'CYTO-PATHOLOGY', 'CYP', '0', '0', '10', '0'),
|
||||
('11', 'HISTOPATHOLOGY', 'HP', '0', '0', '11', '0'),
|
||||
('12', 'IMMUNOLOGY-SEROLOGY', 'IS', '0', '0', '12', '0'),
|
||||
('13', 'ENDOCRINOLOGY', 'ELO', '0', '0', '13', '0'),
|
||||
('15', '', '0', '0', '0', '14', '0');
|
||||
|
||||
(1, 'Biochemistry', 'BCH', '', 0, 2, ''),
|
||||
(2, 'Biopsy', 'BP', '', 0, 3, ''),
|
||||
(3, 'Clinical Pathology', 'CP', '', 0, 4, ''),
|
||||
(4, 'Infectious Disease', 'INF', '', 0, 5, ''),
|
||||
(5, 'General', 'GEN', '', 1, 6, ''),
|
||||
(6, 'Hematology', 'HEM', '', 0, 1, ''),
|
||||
(8, 'Urine', 'US', '', 0, 8, ''),
|
||||
(9, 'Fluid Examination', 'FE', '0', 0, 9, '0'),
|
||||
(10, 'CYTO-PATHOLOGY', 'CYP', '0', 0, 10, '0'),
|
||||
(11, 'HISTOPATHOLOGY', 'HP', '0', 0, 11, '0'),
|
||||
(12, 'IMMUNOLOGY-SEROLOGY', 'IS', '0', 0, 12, '0'),
|
||||
(13, 'Endocrinology', 'END', '0', 0, 13, '0'),
|
||||
(16, 'Vitamins & Minerals', 'VTM', '', 0, 24, ''),
|
||||
(18, 'Inflammatory Markers', 'INM', '', 0, 26, ''),
|
||||
(19, 'Tumor Markers', 'TMR', '', 0, 27, '');
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,210 +11,15 @@ CREATE TABLE IF NOT EXISTS `lab_tests` (
|
|||
`FixedNormals` varchar(30) NOT NULL DEFAULT '0',
|
||||
`isGenderSpecific` int NOT NULL DEFAULT '0',
|
||||
`FixedNormalsWomen` varchar(30) NOT NULL,
|
||||
`loinc_code` varchar(20) DEFAULT NULL,
|
||||
`loinc_property` varchar(50) DEFAULT NULL,
|
||||
`loinc_system` varchar(50) DEFAULT NULL,
|
||||
`loinc_scale` varchar(20) DEFAULT NULL,
|
||||
`loinc_synced_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`mstTestKey`),
|
||||
UNIQUE KEY `TestID` (`TestID`),
|
||||
KEY `Test` (`Test`)
|
||||
KEY `Test` (`Test`),
|
||||
KEY `idx_lab_tests_loinc` (`loinc_code`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
INSERT IGNORE INTO `lab_tests` (`mstTestKey`, `Test`, `TestID`, `Result`, `Options`, `Formula`, `VRule`, `VMsg`, `Unit`, `FixedNormals`, `isGenderSpecific`, `FixedNormalsWomen`) VALUES
|
||||
('1', 'HAEMOGLOBIN(Hb)', 'HB', '', NULL, '', '', '', 'gm%', '12.00-16.00', '0', ''),
|
||||
('2', 'T.L.C.', 'TLC', '', NULL, '', '', '', 'per cu mm', '4000-11000', '0', ''),
|
||||
('3', 'D.L.C.', 'DLC', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('4', 'NEUTROPHILS', 'NEU', '', NULL, '', '', '', '%', '45.00-70.00', '0', ''),
|
||||
('5', 'LYMPHOCYTES', 'LYMP', '', NULL, '', '', '', '%', '20.00-45.00', '0', ''),
|
||||
('6', 'Absolute EOSINOPHILS', 'AEOSIN', '', NULL, '', '', '', 'per cu mm', '40-440', '0', ''),
|
||||
('7', 'MONOCYTES', 'MONO', '', NULL, '', '', '', '%', '1.00-6.00', '0', ''),
|
||||
('8', 'BAND CELLS', 'BAND', '', NULL, '', '', '', '%', '1.00-6.00', '0', ''),
|
||||
('9', 'IMMATURE CELLS', 'IMMATURE', '', NULL, '', '', '', '%', '', '0', ''),
|
||||
('10', 'RBC', 'RBC', '', NULL, '', '', '', 'million/cm', '4.2-5.5', '0', ''),
|
||||
('11', 'PCV', 'PCV', '', NULL, '', '', '', '%', '40.00-54.00', '0', ''),
|
||||
('12', 'MCV', 'MCV', '', NULL, '', '', '', 'fl', '80.00-100.00', '0', ''),
|
||||
('13', 'MCH', 'mch', '', NULL, '', '', '', 'pg', '25.00-35.00', '0', ''),
|
||||
('14', 'MCHC', 'MCHC', '', NULL, '', '', '', 'g/dl', '28.00-38.00', '0', ''),
|
||||
('15', 'PLATELET COUNT', 'PLATELET', '', NULL, '', '', '', 'Lakhs per ', '1.5-4.5', '0', ''),
|
||||
('16', 'SERUM BILIRUBIN', 'SERBIL', '', NULL, '', '', '', 'mg%', '0.20-1.20', '0', ''),
|
||||
('17', 'BLOOD UREA', 'BU', '', NULL, '', '', '', 'mg%', '11.00-37.00', '0', ''),
|
||||
('18', 'SERUM CREATININE', 'SC', '', NULL, '', '', '', 'mg%', '0.60-1.40', '0', ''),
|
||||
('19', 'SERUM URIC ACID', 'SUA', '', NULL, '', '', '', 'mg%', '2.6-7.2', '0', ''),
|
||||
('20', 'SERUM SODIUM', 'SS', '', NULL, '', '', '', 'mmol/L', '135.00-155.00', '0', ''),
|
||||
('21', 'SERUM POTASSIUM', 'SP', '', NULL, '', '', '', 'mmol/L', '3.50-5.50', '0', ''),
|
||||
('22', 'SERUM CALCIUM', 'SCA', '', NULL, '', '', '', 'mg%', '8.60-10.3', '0', ''),
|
||||
('23', 'TOTAL PROTEIN', 'TP', '', NULL, '', '', '', 'gm/dl', '6.4-8.3', '0', ''),
|
||||
('24', 'SERUM ALBUMIN', 'SA', '', NULL, '', '', '', 'mg/dl', '3.50-5.2', '0', ''),
|
||||
('25', 'SERUM GLOBULIN', 'SG', '', NULL, '', '', '', 'mg/dl', '2.4-4.0', '0', ''),
|
||||
('26', 'ESR ', 'ESR1', '', NULL, '', '', '', 'MM', '0.00-9.00', '0', ''),
|
||||
('27', 'SGOT/AST', 'SGOT', '', NULL, '', '', '', 'U/L', '5.00-35.00', '0', ''),
|
||||
('28', 'SGPT/ALT', 'SGPT', '', NULL, '', '', '', 'U/L', '10.00-40.00', '0', ''),
|
||||
('29', 'S. ALKALINE PHOSPHATASE', 'SALKALINE', '', NULL, '', '', '', 'U/L', '50.00-128.00', '0', ''),
|
||||
('30', 'TOTAL PROTEIN', 'TOTPRO', '', NULL, '', '', '', 'mg%', '6.2-8.0', '0', ''),
|
||||
('31', 'ALBUMIN', 'ALBU', '', NULL, '', '', '', 'mg%', '3.50-5.2', '0', ''),
|
||||
('32', 'GLOBULIN', 'GLOB', '', NULL, '', '', '', 'mg%', '1.8-3.6', '0', ''),
|
||||
('33', 'CRP Value', 'CRP', '', NULL, '', '', '', 'mg/l', '1-6', '0', ''),
|
||||
('34', 'Body Part', 'BPART', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('35', 'HIV I', 'HIV1', '', NULL, '', '', '', '', 'NON-REACTIVE', '0', ''),
|
||||
('36', 'HIV II', 'HIV2', '', NULL, '', '', '', '', 'NON-REACTIVE', '0', ''),
|
||||
('37', 'Serum Prolectin', 'SProlectin', '', NULL, '', '', '', 'ng/ml', '5-35', '0', ''),
|
||||
('38', 'Vitamin D', 'VITD', '', NULL, '', '', '', 'ng/ml', '20-100', '0', ''),
|
||||
('39', 'HbA1c', 'HBA1C', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('40', 'Volume', 'vol', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('41', 'Colour', 'sem_color', 'Milky White', NULL, '', '', '', '', '', '0', ''),
|
||||
('42', 'Viscosity', 'Viscosity', 'Viscous', NULL, '', '', '', '', '', '0', ''),
|
||||
('43', 'Liquification Time', 'LIQTIME', '30', NULL, '', '', '', '', '0-30', '0', ''),
|
||||
('44', 'Total Sperm Count', 'TOTSPCOUNT', '', NULL, '', '', '', 'millions p', '40-150', '0', ''),
|
||||
('45', 'Motility', 'MOTILITY', '', NULL, '', '', '', '%', '', '0', ''),
|
||||
('46', 'Non Motile', 'NONMOTILE', '', NULL, '', '', '', '', '0-20', '0', ''),
|
||||
('47', 'Abnormal Forms', 'Abnormal', '', NULL, '', '', '', '', '0-30', '0', ''),
|
||||
('48', 'Pus Cells', 'PUSCELL', '', NULL, '', '', '', 'per HPF', '1-2', '0', ''),
|
||||
('49', 'Epithelial cells', 'EPITH', 'NIL', NULL, '', '', '', '', '', '0', ''),
|
||||
('50', 'Test Head', 'THEAD', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('51', 'Test Head Value', 'THV', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('52', 'Glucose , Fasting (F)', 'BGR', '', NULL, '', '', '', 'mg%', '70.00-100.00', '0', ''),
|
||||
('53', 'Patients Prothombin', 'PPTV', '', NULL, '', '', '', 'sec', '', '0', ''),
|
||||
('54', 'Control Prothombin', 'CPTV', '18.0', NULL, '', '', '', '', '', '0', ''),
|
||||
('55', 'Patient INR Value', 'PINRV', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('56', 'ORAL ANTICOAUGLANT THERAPEUTIC', 'OATR', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('57', 'Control APT', 'CAPT', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('58', 'Patient APT', 'PAPT', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('59', 'Blood Gluose Fasting', 'BGF', '', NULL, '', '', '', 'mg%', '60.00-110.00', '0', ''),
|
||||
('61', 'Blood Glucose (PP)', 'BGPP', '', NULL, '', '', '', 'mg%', '80.00-140.00', '0', ''),
|
||||
('62', 'Appearance', 'APP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('63', 'Colour', 'COLOUR', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('64', 'Coagulum', 'COALGU', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('65', 'Proteins', 'PROTEINS', '', NULL, '', '', '', 'mg/dl', '', '0', ''),
|
||||
('66', 'Sugar', 'SUGAR', '', NULL, '', '', '', 'mg/dl', '', '0', ''),
|
||||
('67', 'Total Cell Count', 'TCC', '', NULL, '', '', '', 'per cmm', '', '0', ''),
|
||||
('68', 'Polymorphs', 'Polymorphs', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('69', 'Lymphocytes', 'Lymphocyte', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('70', 'Monocytes', 'Monocytes', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('71', 'R.B.Cs', 'RBCS', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('72', 'Others', 'OTHER', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('73', 'Gram Stain', 'GRAMSTAIN', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('74', 'Stain For A.F.B.', 'AFB', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('75', 'Mesothelial', 'MESO', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('76', 'Triiodothyronine', 'T3', '', NULL, '', '', '', 'ng/ml', '0.60-1.51', '0', ''),
|
||||
('77', 'Thyroxine', 'T4', '', NULL, '', '', '', 'ug/dl', '4.66-9.32', '0', ''),
|
||||
('78', 'Thyroid Stimulating Hormone', 'TSH', '', NULL, '', '', '', 'ulU/mL', '0.25-5.00', '0', ''),
|
||||
('79', 'Bilirubin Total', 'BT', '', NULL, '', '', '', '', '0.20-2.00', '0', ''),
|
||||
('80', 'Bilirubin Direct', 'BD', '', NULL, '', '', '', '', '0-0.3', '0', ''),
|
||||
('81', 'Bilirubin Indirect', 'BI', '', NULL, '', '', '', '', '0.00-0.70', '0', ''),
|
||||
('82', 'Troponin', 'TROP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('83', 'Troponine I (Trop I)', 'TROPI', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('84', 'SERUM PROTEIN', 'SPro', '', NULL, '', '', '', 'mg', '6.4-8.3', '0', ''),
|
||||
('85', 'Colour', 'urine_colo', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('86', 'Transparency', 'urine_tran', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('87', 'PH', 'urine_ph', '', NULL, '', '', '', '', '4.7-7.5', '0', ''),
|
||||
('88', 'Albumin', 'urine_albu', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('89', 'Sugar', 'urine_suga', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('90', 'Bile Salt', 'ub_salt', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('91', 'Bile Pigment', 'upigment', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('92', 'Pus Cells', 'u_puscell', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('93', 'Pus Cells', 'upuscells', '', NULL, '', '', '', '', '0-5', '0', ''),
|
||||
('94', 'Epithial Cells', 'uepithial', '', NULL, '', '', '', '', '0-5', '0', ''),
|
||||
('95', 'RBCs', 'urbc', '', NULL, '', '', '', '', '0-3', '0', ''),
|
||||
('96', 'crystals', 'urinecryst', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('97', 'Specific Gravity', 'usg', 'QNS', NULL, '', '', '', '', '', '0', ''),
|
||||
('98', 'Crystals', 'ucrystals', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('99', 'Cats', 'urinecasts', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('100', 'Cholesterol', 'l_Choleste', '', NULL, '', '', '', '', '130-200', '0', ''),
|
||||
('101', 'Triglyceride', 'l_Triglyce', '', NULL, '', '', '', '', '45-160', '0', '');
|
||||
|
||||
INSERT IGNORE INTO `lab_tests` (`mstTestKey`, `Test`, `TestID`, `Result`, `Options`, `Formula`, `VRule`, `VMsg`, `Unit`, `FixedNormals`, `isGenderSpecific`, `FixedNormalsWomen`) VALUES
|
||||
('102', 'HDL', 'l_hdl', '', NULL, '', '', '', '', '35-79', '0', ''),
|
||||
('103', 'LDL', 'l_ldl', '', NULL, '', '', '', '', '60-140', '0', ''),
|
||||
('104', 'VLDL', 'l_vldl', '', NULL, '', '', '', '', '10-35', '0', ''),
|
||||
('105', 'Bleeding Time', 'blood_bt', '', NULL, '', '', '', 'min', '0-7', '0', ''),
|
||||
('106', 'Clotting Time', 'blood_ct', '', NULL, '', '', '', 'min', '4.00-12.00', '0', ''),
|
||||
('107', 'HCV', 'hcv', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('108', 'HBsAG', 'HBsAG', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('109', 'Serum Amylase', 'AMYLASE', '', NULL, '', '', '', '', '20-80', '0', ''),
|
||||
('110', 'Serum Lipase', 'LIPASE', '', NULL, '', '', '', '', '0-60', '0', ''),
|
||||
('111', 'R A Factor', 'RAFACTOR', '', NULL, '', '', '', '', '0-25', '0', ''),
|
||||
('112', 'TLC Value', 'TLC1', '', NULL, '', '', '', '', '4000-11000', '0', ''),
|
||||
('113', 'Serum Cholesterol', 'Cholestero', '', NULL, '', '', '', '', '130-200', '0', ''),
|
||||
('114', 'Serum Trigycerides', 'Trigycerid', '', NULL, '', '', '', '', '45-160', '0', ''),
|
||||
('115', 'ABO', 'ABO', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('116', 'Rh Factor', 'RHF', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('117', 'PREGNANCY Test', 'PREGNANCYT', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('118', 'TYPHOID IgM', 'lgm', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('119', 'MALARIA ANTIGEN', 'MALARIA', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('120', 'TYPHOID IgG', 'IgG', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('121', 'TYPHOID IgM', 'IgM', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('122', 'GAMMA GT', 'GAMAGT', '', NULL, '', '', '', '', '09-45', '0', ''),
|
||||
('123', 'CPK MB', 'CPKMB', '', NULL, '', '', '', '', '0-24', '0', ''),
|
||||
('124', 'CPK-NAC', 'CPNACK', '', NULL, '', '', '', '', '35-174', '0', ''),
|
||||
('125', 'PSA(PROSTATIC SPE. ANTIGEN)', 'PSA', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('126', 'ASO', 'ASO', '', NULL, '', '', '', '', '0-200', '0', ''),
|
||||
('127', 'Dengue Ns1', 'DNS1', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('128', 'Dengue IgM', 'DIGM', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('129', 'Dengue IgG ', 'DIGG', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('130', 'DCT', 'DCT', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('131', 'Stool for Occult Blood', 'occult', 'Stool for Occult Blood', NULL, '', '', '', '', '', '0', ''),
|
||||
('132', 'RETICULOCYTE COUNT', 'Reticount', 'RETICULOCYTE COUNT', NULL, '', '', '', '%', '0.20-2.00', '0', ''),
|
||||
('133', 'BILE SALT', 'BS', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('134', 'BILE PIGMENT', 'BP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('135', 'V.D.R.L', 'V.D.R.L', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('136', 'G6PD', '{g6pd}', 'G6PD', NULL, '', '', '', '', '', '0', ''),
|
||||
('137', 'G6PD', 'G6PD', '', NULL, '', '', '', '', '6.40-18.7', '0', ''),
|
||||
('138', 'Indian Link', 'India Link', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('139', 'LDH', 'LDH', '', NULL, '', '', '', '', '114-240', '0', ''),
|
||||
('140', 'SAFB', 'SAFB', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('141', 'MALIGNANT CELLS', 'MC', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('142', 'Color', 'stool_colo', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('143', 'Consistency', 'Consistenc', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('144', 'Mucus', 'Mucus', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('145', 'PH', 'SPH', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('146', 'Reducing Sugar', 'RSugar', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('147', 'Pus Cells', 'PCells', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('148', 'RBCs', 'sRBC', 'NIL', NULL, '', '', '', '', '', '0', ''),
|
||||
('149', 'Ova', 'Ova', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('150', 'Cyst', 'Cyst', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('151', 'Fat Globules', 'FatGlobuli', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('152', 'S Typhi O 1:20', 'STyphiO1', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('153', 'S Typhi O 1 :40', 'STyphiO2', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('154', 'S Typhi O 1:80', 'STyphiO3', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('155', 'S Typhi O 1:160', 'STyphiO4', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('156', 'S Typhi O 1:320', 'STyphiO5', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('157', 'S Typhi H 1:20', 'STyphiH1', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('158', 'S Typhi H 1:40', 'STyphiH2', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('159', 'S Typhi H 1:80', 'STyphiH3', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('160', 'S Typhi H 1:320', 'STyphiH5', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('161', 'S Para Typhi AH 1:20', 'SParaTyphiAH1', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('162', 'S Para Typhi AH 1:40', 'SParaTyphiAH2', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('163', 'S Para Typhi AH 1:80', 'SParaTyphiAH3', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('164', 'S Para Typhi AH 1:160', 'SParaTyphiAH4', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('165', 'S Para Typhi AH 1:320', 'SParaTyphiAH5', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('166', 'S Para Typhi BH 1:20', 'SParaTyphiBH1', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('167', 'S Para Typhi BH 1:40', 'SParaTyphiBH2', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('168', 'S Para Typhi BH 1:80', 'SParaTyphiBH3', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('169', 'S Para Typhi BH 1:160', 'SParaTyphiBH4', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('170', 'S Para Typhi BH 1:320', 'SParaTyphiBH5', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('171', 'Indirect Coombs Test', 'ICT', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('172', 'Urine for Ketone', 'UFK', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('173', 'MP', 'MALARIAP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('174', 'Blood Urea Nitrogen', 'BUN', '', NULL, '', '', '', '', '6-24', '0', ''),
|
||||
('175', 'Banejone Protein', 'BanP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('176', 'Urine For Protien', 'UFP', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('177', 'Chikungunya IgM', 'CIGM', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('178', 'P.P.D Given', 'PPDG', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('179', 'Montoux Reading On', 'READON', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('180', 'Montoux Induration', 'INDURATION', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('181', 'Montoux Result', 'MONTEST', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('182', 'EOSINOPHILS', 'EOSIN', '', NULL, '', '', '', '', '1-8', '0', ''),
|
||||
('183', 'SERUM PHOSPHORUS', 'SPHOSPHORUS', '', NULL, '', '', '', 'mg/dl', '2.5-5.0', '0', ''),
|
||||
('184', 'Ionised Calcium', 'ICALCIUM', '', NULL, '', '', '', 'mg/dl', '4.4-5.5', '0', ''),
|
||||
('185', 'Prothrombin Time ', 'PT', '', NULL, '', '', '', 'secs', '11-16', '0', ''),
|
||||
('186', 'Glucose Random', 'GR', '', NULL, '', '', '', 'mg/dl', '70-140', '0', ''),
|
||||
('187', 'A:G Ratio', 'AGR', '', NULL, '', '', '', '', '1.1-2.0', '0', ''),
|
||||
('188', 'BASOPHILES', 'BASO', '', NULL, '', '', '', '%', '0-1', '0', ''),
|
||||
('190', 'Bacteria flora few ', 'BFF', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('191', 'Semen Reaction', 'semen_reaction', '', NULL, '', '', '', '', 'ALKALINE', '0', ''),
|
||||
('192', 'SEMENREMARKS', 'SEMENREMARKS', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('193', 'Semen Active', 'semen_active', '', NULL, '', '', '', '', '0-60', '0', ''),
|
||||
('194', 'Sluggish', 'SLUGGISH', '', NULL, '', '', '', '%', '0-30', '0', ''),
|
||||
('195', 'CRP Status', 'CRPSTATUS', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('196', 'VITAMIN12', 'VITAMIN12', '', NULL, '', '', '', 'ng/ml', '211-911', '0', ''),
|
||||
('197', 'DIMER', 'DIMER', '0', NULL, '', '', '', 'ug/ml', '0.0-0.5', '0', ''),
|
||||
('198', 'Beta HCG', 'BHCG', '', NULL, '', '', '', 'mIU/mL', '0-5.3', '0', ''),
|
||||
('199', 'Covid-19 Antgen Test', 'covid_antigen_t', '', NULL, '', '', '', '', '', '0', ''),
|
||||
('200', 'Glucose, Post Prandial (PP)', 'AGR1', '', NULL, '', '', '', '', '70.00-140.00', '0', '');
|
||||
|
||||
-- No rows found in `lab_tests`
|
||||
|
|
|
|||
|
|
@ -11,196 +11,4 @@ CREATE TABLE IF NOT EXISTS `lab_tests_option` (
|
|||
KEY `option_value` (`option_value`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
INSERT IGNORE INTO `lab_tests_option` (`id`, `mstTestKey`, `sort_id`, `option_value`, `option_text`, `option_bold`) VALUES
|
||||
('74', '35', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('75', '35', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('76', '36', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('77', '36', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('22', '41', '1', 'Milky_White', 'Milky_White', '0'),
|
||||
('206', '41', '4', 'brownish or red', 'brownish or red', '0'),
|
||||
('208', '41', '5', 'Reddish in color ', 'Reddish in color ', '0'),
|
||||
('209', '41', '6', 'grey in color ', 'grey in color ', '0'),
|
||||
('23', '42', '1', 'Viscous', 'Viscous', '0'),
|
||||
('210', '42', '2', 'Thin ', 'Thin ', '0'),
|
||||
('211', '42', '3', 'Thick', 'Thick', '0'),
|
||||
('24', '50', '1', 'Serum', 'Serum', '0'),
|
||||
('25', '50', '2', 'Plasma', 'Plasma', '0'),
|
||||
('26', '50', '3', 'Pleural', 'Pleural', '0'),
|
||||
('27', '50', '4', 'Pericadial & Ascitic Fluids', 'Pericadial', '0'),
|
||||
('28', '62', '1', 'CLEAR', 'CLEAR', '0'),
|
||||
('219', '62', '3', 'Semi_Turbid', 'Semi_Turbid', '0'),
|
||||
('201', '62', '4', 'Turbid', 'Turbid', '0'),
|
||||
('29', '64', '1', 'ABSENT', 'ABSENT', '0'),
|
||||
('30', '64', '2', 'PRESENT', 'PRESENT', '0'),
|
||||
('31', '73', '1', 'No_Organism_Seen', 'No_Organism_Seen', '0'),
|
||||
('32', '73', '2', 'Positive', 'Positive', '0'),
|
||||
('33', '74', '1', 'Not-Detected', 'Not-Detected', '0'),
|
||||
('34', '74', '2', 'Postive_1', '+1', '0'),
|
||||
('35', '74', '3', 'Postive_2', '+2', '0'),
|
||||
('36', '74', '4', 'Postive_3', '+3', '0'),
|
||||
('37', '74', '5', 'Postive_4', '+4', '0'),
|
||||
('38', '82', '1', 'Positive', 'Positive', '0'),
|
||||
('39', '82', '2', 'Negative', 'Negative', '0'),
|
||||
('40', '83', '1', 'Positive', 'Positive', '0'),
|
||||
('41', '83', '2', 'Negative', 'Negative', '0'),
|
||||
('42', '85', '1', 'Yellow', 'Yellow', '0'),
|
||||
('43', '85', '2', 'Pale-Yellow', 'Pale-Yellow', '0'),
|
||||
('44', '85', '3', 'Reddish_Deep_Yellow', 'Reddish_Deep_Yellow', '0'),
|
||||
('93', '85', '4', 'Reddish', 'Reddish', '0'),
|
||||
('94', '85', '5', 'Deep-Yellow', 'Deep-Yellow', '0'),
|
||||
('45', '86', '1', 'Clear', 'Clear', '0'),
|
||||
('46', '86', '2', 'Turbid', 'Turbid', '0'),
|
||||
('49', '88', '1', 'NIL', 'NIL', '0'),
|
||||
('88', '88', '2', 'Trace', 'Trace', '0'),
|
||||
('50', '88', '3', 'Present +', 'Present+', '0'),
|
||||
('51', '88', '4', 'Present++', 'Present++', '0'),
|
||||
('52', '88', '5', 'Present+++', 'Present+++', '0'),
|
||||
('53', '88', '6', 'Present++++', 'Present++++', '0'),
|
||||
('54', '89', '1', 'Nil', 'Nil', '0'),
|
||||
('99', '89', '2', 'Trace', 'Trace', '0'),
|
||||
('55', '89', '3', 'Present+', 'Present+', '0'),
|
||||
('56', '89', '4', 'Present++', 'Present++', '0'),
|
||||
('57', '89', '5', 'Present+++', 'Present+++', '0'),
|
||||
('58', '89', '6', 'Present++++', 'Present++++', '0'),
|
||||
('59', '90', '1', 'Nil', 'Nil', '0'),
|
||||
('60', '90', '2', 'Present', 'Present', '0'),
|
||||
('61', '91', '1', 'Nil', 'Nil', '0'),
|
||||
('62', '91', '2', 'Present', 'Present', '0'),
|
||||
('63', '92', '1', '0-1', '0-1', '0'),
|
||||
('64', '92', '2', '1-2', '1-2', '0'),
|
||||
('65', '96', '1', 'Nil', 'NIL', '0'),
|
||||
('66', '96', '2', 'Calcium-Oxalate', 'Calcium-Oxalate', '0'),
|
||||
('67', '96', '3', 'Phosphate', 'Phosphate', '0'),
|
||||
('68', '96', '4', 'UricAcid', 'UricAcid', '0'),
|
||||
('116', '97', '1', 'Qns', 'Qns', '0'),
|
||||
('69', '99', '1', 'Nil', 'Nil', '0'),
|
||||
('70', '99', '2', 'Present Hyaline', 'Present Hyaline', '0'),
|
||||
('71', '99', '3', 'Granular', 'Granular', '0'),
|
||||
('72', '107', '1', 'Non-Reactive', 'Non-Reactive', '0'),
|
||||
('73', '107', '2', 'Reactive', 'Reactive', '0'),
|
||||
('78', '108', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('79', '108', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('80', '115', '1', 'A', 'A', '0'),
|
||||
('81', '115', '2', 'B', 'B', '0'),
|
||||
('82', '115', '3', 'AB', 'AB', '0'),
|
||||
('83', '115', '4', 'O', 'O', '0'),
|
||||
('84', '116', '1', 'POSITIVE', 'POSITIVE', '0'),
|
||||
('85', '116', '2', 'NEGATIVE', 'NEGATIVE', '0'),
|
||||
('86', '117', '1', 'Positive', 'Positive', '0'),
|
||||
('87', '117', '2', 'Negative', 'Negative', '0'),
|
||||
('89', '118', '1', 'Non-Reactive', 'Non-Reactive', '0'),
|
||||
('90', '118', '2', 'Reactive', 'Reactive', '0'),
|
||||
('91', '119', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('92', '119', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('95', '120', '1', 'Negative', 'Negative', '0'),
|
||||
('96', '120', '2', 'Positive', 'Positive', '0'),
|
||||
('97', '121', '1', 'Negative', 'Negative', '0'),
|
||||
('98', '121', '2', 'Positive', 'Positive', '0'),
|
||||
('101', '127', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('100', '127', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('103', '128', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('102', '128', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('105', '129', '1', 'NON-REACTIVE', 'NON-REACTIVE', '0'),
|
||||
('104', '129', '2', 'REACTIVE', 'REACTIVE', '0'),
|
||||
('106', '130', '1', 'NEGATIVE', 'NEGATIVE', '0'),
|
||||
('107', '130', '2', 'POSITIVE', 'POSITIVE', '0'),
|
||||
('108', '131', '1', 'Negative', 'Negative', '0'),
|
||||
('109', '131', '2', 'Positive', 'Positive', '0'),
|
||||
('110', '133', '1', 'NEGATIVE', 'NEGATIVE', '0'),
|
||||
('111', '133', '2', 'POSITIVE', 'POSITIVE', '0'),
|
||||
('112', '134', '1', 'NEGATIVE', 'NEGATIVE', '0'),
|
||||
('113', '134', '2', 'POSITIVE', 'POSITIVE', '0'),
|
||||
('178', '135', '3', 'Non-Reactive', 'Non-Reactive', '0'),
|
||||
('179', '135', '4', 'Reactive', 'Reactive', '0');
|
||||
|
||||
INSERT IGNORE INTO `lab_tests_option` (`id`, `mstTestKey`, `sort_id`, `option_value`, `option_text`, `option_bold`) VALUES
|
||||
('117', '138', '1', 'Negative', 'Negative', '0'),
|
||||
('118', '138', '2', 'Positive', 'Positive', '0'),
|
||||
('119', '140', '1', 'Negative', 'Negative', '0'),
|
||||
('120', '140', '2', 'Postive+', 'Postive', '0'),
|
||||
('121', '140', '3', 'Postive++', 'Postive++', '0'),
|
||||
('122', '140', '4', 'Postive+++', 'Postive+++', '0'),
|
||||
('123', '140', '5', 'Postive++++', 'Postive++++', '0'),
|
||||
('124', '141', '1', 'Seen', 'Seen', '0'),
|
||||
('125', '141', '2', 'Not Seen', 'Not Seen', '0'),
|
||||
('126', '142', '1', 'Brownish', 'Brownish', '0'),
|
||||
('127', '142', '2', 'Yellow', 'Yellow', '0'),
|
||||
('128', '142', '3', 'Greenish', 'Greenish', '0'),
|
||||
('129', '143', '1', 'Loose', 'Loose', '0'),
|
||||
('130', '143', '2', 'Semi Formed', 'Semi Formed', '0'),
|
||||
('131', '143', '3', 'Fully Formed', 'Fully Formed', '0'),
|
||||
('132', '144', '1', 'Absent', 'Absent', '0'),
|
||||
('133', '144', '2', 'Present', 'Present', '0'),
|
||||
('134', '146', '1', 'Postive', 'Postive', '0'),
|
||||
('135', '146', '2', 'Negative', 'Negative', '0'),
|
||||
('136', '151', '1', 'Present', 'Present', '0'),
|
||||
('137', '151', '2', 'NIL', 'NIL', '0'),
|
||||
('139', '152', '1', '-', '-', '0'),
|
||||
('138', '152', '2', '+', '+', '0'),
|
||||
('140', '153', '1', '-', '-', '0'),
|
||||
('141', '153', '2', '+', '+', '0'),
|
||||
('142', '154', '1', '-', '-', '0'),
|
||||
('143', '154', '2', '+', '+', '0'),
|
||||
('144', '155', '1', '-', '-', '0'),
|
||||
('145', '155', '2', '+', '+', '0'),
|
||||
('146', '156', '1', '-', '-', '0'),
|
||||
('147', '156', '2', '+', '+', '0'),
|
||||
('148', '157', '1', '-', '-', '0'),
|
||||
('149', '157', '2', '+', '+', '0'),
|
||||
('150', '158', '1', '-', '-', '0'),
|
||||
('151', '158', '2', '+', '+', '0'),
|
||||
('152', '159', '1', '-', '-', '0'),
|
||||
('153', '159', '2', '+', '+', '0'),
|
||||
('154', '160', '1', '-', '-', '0'),
|
||||
('155', '160', '2', '+', '+', '0'),
|
||||
('156', '161', '1', '-', '-', '0'),
|
||||
('157', '161', '2', '+', '+', '0'),
|
||||
('158', '162', '1', '-', '-', '0'),
|
||||
('159', '162', '2', '+', '+', '0'),
|
||||
('160', '163', '1', '-', '-', '0'),
|
||||
('161', '163', '2', '+', '+', '0'),
|
||||
('162', '164', '1', '-', '-', '0'),
|
||||
('163', '164', '2', '+', '+', '0'),
|
||||
('164', '165', '1', '-', '-', '0'),
|
||||
('165', '165', '2', '+', '+', '0'),
|
||||
('166', '166', '1', '-', '-', '0'),
|
||||
('167', '166', '2', '+', '+', '0'),
|
||||
('168', '167', '1', '-', '-', '0'),
|
||||
('169', '167', '2', '+', '+', '0'),
|
||||
('170', '168', '1', '-', '-', '0'),
|
||||
('171', '168', '2', '+', '+', '0'),
|
||||
('172', '169', '1', '-', '-', '0'),
|
||||
('173', '169', '2', '+', '+', '0'),
|
||||
('174', '170', '1', '-', '-', '0'),
|
||||
('175', '170', '2', '+', '+', '0'),
|
||||
('176', '171', '1', 'Negative', 'Negative', '0'),
|
||||
('177', '171', '2', 'Positive', 'Positive', '0'),
|
||||
('180', '172', '1', 'Absent', 'Absent', '0'),
|
||||
('181', '172', '2', 'Present +', 'Present +', '0'),
|
||||
('182', '172', '3', 'Present ++', 'Present ++', '0'),
|
||||
('183', '172', '4', 'Present +++', 'Present +++', '0'),
|
||||
('184', '172', '5', 'Present ++++', 'Present ++++', '0'),
|
||||
('185', '173', '1', 'Not-Seen', 'Not-Seen', '0'),
|
||||
('186', '173', '2', 'Seen', 'Seen', '1'),
|
||||
('187', '175', '1', 'Negative', 'Negative', '0'),
|
||||
('188', '175', '2', 'Positive', 'Positive', '1'),
|
||||
('189', '176', '1', 'Absent', 'Absent', '0'),
|
||||
('190', '176', '2', 'Present', 'Present', '1'),
|
||||
('191', '177', '1', 'Negative', 'Negative', '0'),
|
||||
('192', '177', '2', 'Positive', 'Positive', '1'),
|
||||
('193', '181', '1', 'Negative', 'Negative', '0'),
|
||||
('194', '181', '2', 'Positive', 'Positive', '0'),
|
||||
('199', '190', '1', 'Nil', 'Nil', '0'),
|
||||
('195', '190', '2', '+', '+', '0'),
|
||||
('196', '190', '3', '+ +', '+ +', '0'),
|
||||
('197', '190', '4', '+ + +', '+ + +', '0'),
|
||||
('198', '190', '5', '+ + + +', '+ + + +', '0'),
|
||||
('202', '191', '1', 'ALKALINE', 'ALKALINE', '0'),
|
||||
('212', '191', '3', 'Acidic', 'Acidic', '0'),
|
||||
('213', '192', '1', 'Normal', 'Normal', '0'),
|
||||
('214', '192', '2', ' Abnormal', ' Abnormal', '0'),
|
||||
('217', '195', '1', 'Negative', 'Negative', '0'),
|
||||
('218', '195', '2', 'Positive', 'Positive', '1'),
|
||||
('220', '199', '1', 'Negative', 'Negative', '0'),
|
||||
('221', '199', '2', 'Positive', 'Positive', '1');
|
||||
|
||||
-- No rows found in `lab_tests_option`
|
||||
|
|
|
|||
|
|
@ -8,336 +8,4 @@ CREATE TABLE IF NOT EXISTS `lab_repotests` (
|
|||
UNIQUE KEY `mstRepoKey_EOrder` (`mstRepoKey`,`EOrder`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
INSERT IGNORE INTO `lab_repotests` (`id`, `mstRepoKey`, `mstTestKey`, `EOrder`) VALUES
|
||||
('1', '1', '1', '1'),
|
||||
('2', '1', '2', '2'),
|
||||
('4', '1', '4', '4'),
|
||||
('5', '1', '5', '5'),
|
||||
('270', '1', '182', '7'),
|
||||
('7', '1', '7', '8'),
|
||||
('276', '1', '188', '11'),
|
||||
('10', '1', '10', '12'),
|
||||
('15', '1', '15', '13'),
|
||||
('12', '1', '12', '14'),
|
||||
('13', '1', '13', '15'),
|
||||
('14', '1', '14', '16'),
|
||||
('11', '1', '11', '17'),
|
||||
('278', '1', '26', '18'),
|
||||
('16', '2', '16', '1'),
|
||||
('180', '2', '80', '2'),
|
||||
('181', '2', '81', '3'),
|
||||
('27', '2', '27', '4'),
|
||||
('28', '2', '28', '5'),
|
||||
('29', '2', '29', '6'),
|
||||
('30', '2', '30', '7'),
|
||||
('31', '2', '31', '8'),
|
||||
('32', '2', '32', '9'),
|
||||
('275', '2', '187', '10'),
|
||||
('17', '3', '17', '1'),
|
||||
('18', '3', '18', '2'),
|
||||
('19', '3', '19', '3'),
|
||||
('22', '3', '22', '4'),
|
||||
('20', '3', '20', '5'),
|
||||
('21', '3', '21', '6'),
|
||||
('33', '4', '33', '1'),
|
||||
('286', '4', '195', '2'),
|
||||
('34', '5', '34', '1'),
|
||||
('35', '6', '35', '1'),
|
||||
('36', '6', '36', '2'),
|
||||
('160', '7', '108', '1'),
|
||||
('37', '8', '37', '1'),
|
||||
('38', '9', '38', '1'),
|
||||
('39', '10', '39', '1'),
|
||||
('40', '11', '40', '1'),
|
||||
('41', '11', '41', '2'),
|
||||
('280', '11', '191', '3'),
|
||||
('42', '11', '42', '4'),
|
||||
('43', '11', '43', '5'),
|
||||
('44', '11', '44', '6'),
|
||||
('282', '11', '193', '7'),
|
||||
('283', '11', '194', '9'),
|
||||
('46', '11', '46', '10'),
|
||||
('47', '11', '47', '11'),
|
||||
('48', '11', '48', '12'),
|
||||
('49', '11', '49', '13'),
|
||||
('285', '11', '148', '14'),
|
||||
('281', '11', '192', '15'),
|
||||
('50', '12', '50', '1'),
|
||||
('51', '12', '51', '2'),
|
||||
('52', '13', '52', '1'),
|
||||
('451', '13', '200', '2'),
|
||||
('53', '14', '53', '1'),
|
||||
('54', '14', '54', '2'),
|
||||
('55', '14', '55', '3'),
|
||||
('56', '14', '56', '4'),
|
||||
('273', '15', '185', '1'),
|
||||
('58', '15', '58', '2'),
|
||||
('57', '15', '57', '3'),
|
||||
('59', '16', '59', '1'),
|
||||
('60', '16', '60', '2'),
|
||||
('61', '17', '61', '1'),
|
||||
('62', '18', '62', '1'),
|
||||
('63', '18', '63', '2'),
|
||||
('64', '18', '64', '3'),
|
||||
('65', '18', '65', '4'),
|
||||
('66', '18', '66', '5'),
|
||||
('67', '18', '67', '6'),
|
||||
('68', '18', '68', '7'),
|
||||
('69', '18', '69', '8'),
|
||||
('70', '18', '70', '9'),
|
||||
('71', '18', '71', '10'),
|
||||
('72', '18', '72', '11'),
|
||||
('73', '18', '73', '12'),
|
||||
('74', '18', '74', '13'),
|
||||
('89', '19', '76', '1'),
|
||||
('90', '19', '77', '2'),
|
||||
('91', '19', '78', '3'),
|
||||
('75', '20', '62', '1'),
|
||||
('76', '20', '63', '2'),
|
||||
('77', '20', '64', '3'),
|
||||
('78', '20', '65', '4'),
|
||||
('79', '20', '66', '5'),
|
||||
('80', '20', '67', '6'),
|
||||
('81', '20', '68', '7'),
|
||||
('82', '20', '69', '8'),
|
||||
('88', '20', '75', '10'),
|
||||
('84', '20', '71', '12'),
|
||||
('86', '20', '73', '13'),
|
||||
('87', '20', '74', '14'),
|
||||
('92', '21', '79', '1'),
|
||||
('93', '21', '80', '2'),
|
||||
('94', '21', '81', '3'),
|
||||
('95', '22', '82', '1'),
|
||||
('96', '23', '83', '1');
|
||||
|
||||
INSERT IGNORE INTO `lab_repotests` (`id`, `mstRepoKey`, `mstTestKey`, `EOrder`) VALUES
|
||||
('97', '24', '84', '1'),
|
||||
('98', '25', '16', '1'),
|
||||
('99', '26', '27', '1'),
|
||||
('100', '27', '28', '1'),
|
||||
('165', '28', '112', '1'),
|
||||
('102', '29', '4', '2'),
|
||||
('103', '29', '5', '3'),
|
||||
('269', '29', '182', '5'),
|
||||
('105', '29', '7', '6'),
|
||||
('106', '29', '8', '7'),
|
||||
('107', '29', '9', '8'),
|
||||
('113', '30', '4', '1'),
|
||||
('114', '31', '5', '1'),
|
||||
('116', '33', '10', '1'),
|
||||
('117', '34', '11', '1'),
|
||||
('118', '35', '12', '1'),
|
||||
('119', '36', '13', '1'),
|
||||
('120', '37', '14', '1'),
|
||||
('121', '38', '15', '1'),
|
||||
('122', '39', '17', '1'),
|
||||
('123', '40', '18', '1'),
|
||||
('124', '41', '19', '1'),
|
||||
('125', '42', '20', '1'),
|
||||
('126', '43', '21', '1'),
|
||||
('127', '44', '22', '1'),
|
||||
('128', '45', '24', '1'),
|
||||
('129', '46', '25', '1'),
|
||||
('130', '47', '26', '1'),
|
||||
('131', '48', '29', '1'),
|
||||
('133', '50', '73', '1'),
|
||||
('134', '51', '76', '1'),
|
||||
('135', '52', '77', '1'),
|
||||
('136', '54', '1', '1'),
|
||||
('137', '55', '85', '1'),
|
||||
('138', '55', '86', '2'),
|
||||
('149', '55', '97', '3'),
|
||||
('139', '55', '87', '4'),
|
||||
('140', '55', '88', '5'),
|
||||
('141', '55', '89', '6'),
|
||||
('145', '55', '93', '9'),
|
||||
('146', '55', '94', '10'),
|
||||
('147', '55', '95', '11'),
|
||||
('151', '55', '99', '12'),
|
||||
('148', '55', '96', '13'),
|
||||
('279', '55', '190', '14'),
|
||||
('152', '56', '100', '1'),
|
||||
('154', '56', '102', '2'),
|
||||
('155', '56', '103', '3'),
|
||||
('156', '56', '104', '4'),
|
||||
('153', '56', '101', '5'),
|
||||
('157', '57', '105', '1'),
|
||||
('158', '57', '106', '2'),
|
||||
('159', '58', '107', '1'),
|
||||
('161', '59', '78', '1'),
|
||||
('164', '62', '111', '1'),
|
||||
('234', '63', '152', '1'),
|
||||
('235', '63', '153', '2'),
|
||||
('236', '63', '154', '3'),
|
||||
('237', '63', '155', '4'),
|
||||
('238', '63', '156', '5'),
|
||||
('239', '63', '157', '6'),
|
||||
('240', '63', '158', '7'),
|
||||
('241', '63', '159', '8'),
|
||||
('242', '63', '160', '9'),
|
||||
('243', '63', '161', '10'),
|
||||
('244', '63', '162', '11'),
|
||||
('245', '63', '163', '12'),
|
||||
('246', '63', '164', '13'),
|
||||
('247', '63', '165', '14'),
|
||||
('248', '63', '166', '15'),
|
||||
('249', '63', '167', '16'),
|
||||
('250', '63', '168', '17'),
|
||||
('251', '63', '169', '18'),
|
||||
('252', '63', '170', '19'),
|
||||
('162', '64', '109', '1'),
|
||||
('163', '65', '110', '1'),
|
||||
('166', '66', '113', '1'),
|
||||
('167', '67', '114', '1'),
|
||||
('168', '68', '115', '1'),
|
||||
('169', '68', '116', '2'),
|
||||
('170', '69', '117', '1'),
|
||||
('224', '70', '142', '1'),
|
||||
('225', '70', '143', '2'),
|
||||
('226', '70', '144', '3'),
|
||||
('227', '70', '145', '4'),
|
||||
('228', '70', '146', '5'),
|
||||
('229', '70', '147', '6'),
|
||||
('230', '70', '148', '7'),
|
||||
('231', '70', '149', '8'),
|
||||
('232', '70', '150', '9'),
|
||||
('233', '70', '151', '10'),
|
||||
('174', '71', '121', '2'),
|
||||
('173', '71', '120', '3'),
|
||||
('172', '72', '119', '1'),
|
||||
('175', '73', '122', '1'),
|
||||
('177', '74', '124', '1'),
|
||||
('176', '75', '123', '1'),
|
||||
('178', '76', '125', '1'),
|
||||
('187', '77', '132', '1'),
|
||||
('179', '78', '126', '1');
|
||||
|
||||
INSERT IGNORE INTO `lab_repotests` (`id`, `mstRepoKey`, `mstTestKey`, `EOrder`) VALUES
|
||||
('182', '79', '127', '1'),
|
||||
('183', '79', '128', '2'),
|
||||
('184', '79', '129', '3'),
|
||||
('185', '80', '130', '1'),
|
||||
('186', '81', '131', '1'),
|
||||
('188', '82', '133', '1'),
|
||||
('189', '83', '134', '1'),
|
||||
('190', '86', '135', '1'),
|
||||
('192', '87', '137', '1'),
|
||||
('223', '88', '141', '1'),
|
||||
('257', '91', '175', '1'),
|
||||
('193', '92', '1', '1'),
|
||||
('194', '92', '2', '2'),
|
||||
('195', '92', '4', '3'),
|
||||
('196', '92', '5', '4'),
|
||||
('197', '92', '6', '5'),
|
||||
('198', '92', '7', '6'),
|
||||
('199', '92', '8', '7'),
|
||||
('200', '92', '9', '8'),
|
||||
('201', '92', '10', '9'),
|
||||
('202', '92', '11', '10'),
|
||||
('203', '92', '12', '11'),
|
||||
('204', '92', '13', '12'),
|
||||
('205', '92', '14', '13'),
|
||||
('206', '92', '15', '14'),
|
||||
('207', '92', '26', '15'),
|
||||
('208', '93', '138', '1'),
|
||||
('209', '95', '139', '1'),
|
||||
('211', '97', '62', '1'),
|
||||
('212', '97', '63', '2'),
|
||||
('213', '97', '64', '3'),
|
||||
('214', '97', '65', '4'),
|
||||
('215', '97', '66', '5'),
|
||||
('216', '97', '67', '6'),
|
||||
('217', '97', '68', '7'),
|
||||
('218', '97', '69', '8'),
|
||||
('219', '97', '75', '9'),
|
||||
('220', '97', '71', '10'),
|
||||
('221', '97', '73', '11'),
|
||||
('222', '97', '74', '12'),
|
||||
('253', '98', '171', '1'),
|
||||
('254', '99', '172', '1'),
|
||||
('255', '100', '173', '1'),
|
||||
('256', '101', '174', '1'),
|
||||
('258', '102', '176', '1'),
|
||||
('259', '103', '177', '1'),
|
||||
('264', '128', '178', '1'),
|
||||
('265', '128', '179', '2'),
|
||||
('266', '128', '180', '3'),
|
||||
('267', '128', '181', '4'),
|
||||
('271', '129', '183', '1'),
|
||||
('272', '130', '184', '1'),
|
||||
('274', '131', '186', '1'),
|
||||
('421', '132', '1', '1'),
|
||||
('422', '132', '2', '2'),
|
||||
('423', '132', '4', '3'),
|
||||
('424', '132', '5', '4'),
|
||||
('425', '132', '182', '5'),
|
||||
('426', '132', '7', '6'),
|
||||
('427', '132', '188', '7'),
|
||||
('428', '132', '10', '8'),
|
||||
('429', '132', '15', '9'),
|
||||
('430', '132', '12', '10'),
|
||||
('431', '132', '13', '11'),
|
||||
('432', '132', '14', '12'),
|
||||
('433', '132', '11', '13'),
|
||||
('434', '132', '26', '14'),
|
||||
('435', '132', '59', '15'),
|
||||
('436', '132', '60', '16'),
|
||||
('437', '132', '39', '17'),
|
||||
('438', '132', '100', '18'),
|
||||
('439', '132', '102', '19'),
|
||||
('440', '132', '103', '20'),
|
||||
('441', '132', '104', '21'),
|
||||
('442', '132', '101', '22'),
|
||||
('443', '132', '76', '23'),
|
||||
('444', '132', '77', '24'),
|
||||
('445', '132', '78', '25'),
|
||||
('379', '133', '1', '1'),
|
||||
('380', '133', '2', '2'),
|
||||
('381', '133', '4', '3'),
|
||||
('382', '133', '5', '4'),
|
||||
('383', '133', '182', '5'),
|
||||
('384', '133', '7', '6'),
|
||||
('385', '133', '188', '7'),
|
||||
('386', '133', '10', '8'),
|
||||
('387', '133', '15', '9'),
|
||||
('388', '133', '12', '10'),
|
||||
('389', '133', '13', '11'),
|
||||
('390', '133', '14', '12'),
|
||||
('391', '133', '11', '13'),
|
||||
('392', '133', '26', '14'),
|
||||
('393', '133', '59', '15'),
|
||||
('394', '133', '61', '16'),
|
||||
('395', '133', '39', '17'),
|
||||
('396', '133', '100', '18'),
|
||||
('397', '133', '102', '19'),
|
||||
('398', '133', '103', '20'),
|
||||
('399', '133', '104', '21'),
|
||||
('400', '133', '101', '22');
|
||||
|
||||
INSERT IGNORE INTO `lab_repotests` (`id`, `mstRepoKey`, `mstTestKey`, `EOrder`) VALUES
|
||||
('401', '133', '76', '23'),
|
||||
('402', '133', '77', '24'),
|
||||
('403', '133', '78', '25'),
|
||||
('404', '133', '16', '26'),
|
||||
('405', '133', '80', '27'),
|
||||
('406', '133', '81', '28'),
|
||||
('407', '133', '27', '29'),
|
||||
('408', '133', '28', '30'),
|
||||
('409', '133', '29', '31'),
|
||||
('410', '133', '30', '32'),
|
||||
('411', '133', '31', '33'),
|
||||
('412', '133', '32', '34'),
|
||||
('413', '133', '187', '35'),
|
||||
('414', '133', '17', '36'),
|
||||
('415', '133', '18', '37'),
|
||||
('416', '133', '19', '38'),
|
||||
('417', '133', '22', '39'),
|
||||
('418', '133', '20', '40'),
|
||||
('419', '133', '21', '41'),
|
||||
('420', '133', '38', '42'),
|
||||
('447', '133', '196', '43'),
|
||||
('446', '134', '196', '1'),
|
||||
('448', '135', '197', '1'),
|
||||
('449', '136', '198', '1'),
|
||||
('450', '137', '199', '1');
|
||||
|
||||
-- No rows found in `lab_repotests`
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
public function sharePrescriptionBundle(array $payload, string $entityId = ''): array
|
||||
{
|
||||
return $this->post('/v3/bundle/push', [
|
||||
'consent_id' => (string) ($payload['consent_handle'] ?? $payload['consent_id'] ?? ''),
|
||||
'consent_id' => (string) ($payload['consent_id'] ?? $payload['consent_handle'] ?? ''),
|
||||
'hi_type' => (string) ($payload['hi_type'] ?? 'OPConsultation'),
|
||||
'fhir_bundle' => $payload['bundle'] ?? $payload['fhir_bundle'] ?? [],
|
||||
]);
|
||||
|
|
@ -317,7 +317,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
public function shareIpdDischargeBundle(array $payload, string $entityId = ''): array
|
||||
{
|
||||
return $this->post('/v3/bundle/push', [
|
||||
'consent_id' => (string) ($payload['consent_handle'] ?? $payload['consent_id'] ?? ''),
|
||||
'consent_id' => (string) ($payload['consent_id'] ?? $payload['consent_handle'] ?? ''),
|
||||
'hi_type' => 'DischargeSummary',
|
||||
'fhir_bundle' => $payload['bundle'] ?? $payload['fhir_bundle'] ?? [],
|
||||
]);
|
||||
|
|
@ -326,7 +326,7 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
public function shareDiagnosisReportBundle(array $payload, string $entityId = ''): array
|
||||
{
|
||||
return $this->post('/v3/bundle/push', [
|
||||
'consent_id' => (string) ($payload['consent_handle'] ?? $payload['consent_id'] ?? ''),
|
||||
'consent_id' => (string) ($payload['consent_id'] ?? $payload['consent_handle'] ?? ''),
|
||||
'hi_type' => 'DiagnosticReport',
|
||||
'fhir_bundle' => $payload['bundle'] ?? $payload['fhir_bundle'] ?? [],
|
||||
]);
|
||||
|
|
@ -582,4 +582,66 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
|
|||
|
||||
return $this->get('/v3/drugs/version', $query);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Pathology Terminology (LOINC)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch pathology panel masters from the Bridge API.
|
||||
*
|
||||
* @param string $subCategory 'PATHOLOGY' | 'BIOPSY' | '' for all
|
||||
* @param int $limit items per page (max 500)
|
||||
* @param int $offset pagination offset
|
||||
* @param string $updatedSince ISO-8601 datetime for incremental sync
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function pathologyMasters(
|
||||
string $subCategory = '',
|
||||
int $limit = 200,
|
||||
int $offset = 0,
|
||||
string $updatedSince = ''
|
||||
): array {
|
||||
$query = ['limit' => max(1, min(500, $limit)), 'offset' => $offset];
|
||||
if ($subCategory !== '') {
|
||||
$query['sub_category'] = strtoupper($subCategory);
|
||||
}
|
||||
if ($updatedSince !== '') {
|
||||
$query['updated_since'] = $updatedSince;
|
||||
}
|
||||
if ($this->hfrId !== '') {
|
||||
$query['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
|
||||
return $this->get('/v3/pathology/masters', $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch LOINC component details for a specific pathology panel.
|
||||
*
|
||||
* @param string $parentTest Exact panel name (e.g. 'Complete Blood Count Panel')
|
||||
* @param int $limit items per page
|
||||
* @param int $offset pagination offset
|
||||
* @param string $updatedSince ISO-8601 datetime for incremental sync
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function pathologyComponents(
|
||||
string $parentTest = '',
|
||||
int $limit = 200,
|
||||
int $offset = 0,
|
||||
string $updatedSince = ''
|
||||
): array {
|
||||
$query = ['limit' => max(1, min(500, $limit)), 'offset' => $offset];
|
||||
if ($parentTest !== '') {
|
||||
$query['parent_test'] = $parentTest;
|
||||
}
|
||||
if ($updatedSince !== '') {
|
||||
$query['updated_since'] = $updatedSince;
|
||||
}
|
||||
if ($this->hfrId !== '') {
|
||||
$query['hfr_id'] = $this->hfrId;
|
||||
}
|
||||
|
||||
return $this->get('/v3/pathology/components', $query);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,22 +6,61 @@
|
|||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label>Report Name</label>
|
||||
<?php
|
||||
$repo_name = '';
|
||||
$repo_id = '0';
|
||||
$HTMLData = '';
|
||||
if (count($labReport_master ?? []) > 0) {
|
||||
$repo_name = $labReport_master[0]->Title ?? '';
|
||||
$repo_id = $labReport_master[0]->mstRepoKey ?? '0';
|
||||
$HTMLData = $labReport_master[0]->HTMLData ?? '';
|
||||
}
|
||||
?>
|
||||
<input class="form-control" id="input_Reportname" name="input_Reportname" placeholder="Report Name" type="text" value="<?= esc($repo_name) ?>" />
|
||||
<input type="hidden" id="repo_id" value="<?= esc($repo_id) ?>">
|
||||
<?php
|
||||
$repo_name = '';
|
||||
$repo_id = '0';
|
||||
$HTMLData = '';
|
||||
$repo_loinc_code = $repo_loinc_code ?? '';
|
||||
if (count($labReport_master ?? []) > 0) {
|
||||
$repo_name = $labReport_master[0]->Title ?? '';
|
||||
$repo_id = $labReport_master[0]->mstRepoKey ?? '0';
|
||||
$HTMLData = $labReport_master[0]->HTMLData ?? '';
|
||||
$repo_loinc_code = $labReport_master[0]->loinc_code ?? $repo_loinc_code;
|
||||
}
|
||||
?>
|
||||
<input type="hidden" id="repo_id" value="<?= esc($repo_id) ?>">
|
||||
</div>
|
||||
|
||||
<!-- Report Name with Bridge API Autocomplete -->
|
||||
<div class="col-md-8">
|
||||
<div class="form-group position-relative">
|
||||
<label for="input_Reportname">
|
||||
Report Name
|
||||
<span id="loinc-fetch-spinner" class="spinner-border spinner-border-sm text-primary ms-2 d-none" role="status"></span>
|
||||
</label>
|
||||
<input class="form-control" id="input_Reportname" name="input_Reportname"
|
||||
placeholder="Type to search from Bridge API…"
|
||||
type="text" value="<?= esc($repo_name) ?>"
|
||||
autocomplete="off" />
|
||||
<!-- Autocomplete dropdown -->
|
||||
<div id="report-name-suggestions"
|
||||
class="list-group shadow"
|
||||
style="position:absolute;z-index:1050;width:100%;display:none;max-height:260px;overflow-y:auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOINC Code (auto-filled on selection, editable) -->
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="input_loinc_code">
|
||||
LOINC Code
|
||||
<small class="text-muted">(auto-filled from Bridge API)</small>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fas fa-flask"></i></span>
|
||||
<input class="form-control" id="input_loinc_code" name="loinc_code"
|
||||
placeholder="e.g. 58410-2"
|
||||
type="text" value="<?= esc($repo_loinc_code) ?>" />
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 mt-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="btn-load-master-template">Load Master Template</button>
|
||||
<button type="button" class="btn btn-outline-success btn-sm" id="btn-apply-master-panel">Apply Panel Mapping</button>
|
||||
<span id="master-template-spinner" class="spinner-border spinner-border-sm text-primary d-none" role="status"></span>
|
||||
</div>
|
||||
<small id="selected-master-meta" class="text-muted d-block mt-2">Select a Bridge API master row, then click "Load Master Template".</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Attach Charge Name</label>
|
||||
|
|
@ -96,40 +135,401 @@
|
|||
</div>
|
||||
<?= form_close() ?>
|
||||
<script>
|
||||
$('#updatereport').click(function() {
|
||||
var repo_id = $('#repo_id').val();
|
||||
var input_Reportname = $('#input_Reportname').val();
|
||||
var charge_id = $('#charge_id').val();
|
||||
var group_id = $('#group_id').val();
|
||||
var HTMLData = (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.HTMLData) ? CKEDITOR.instances.HTMLData.getData() : $('#HTMLData').val();
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (repo_id > 0) {
|
||||
$.post('<?= base_url('Lab_Admin/report_update') ?>', {
|
||||
"repo_id": repo_id,
|
||||
"input_Reportname": input_Reportname,
|
||||
"charge_id": charge_id,
|
||||
"group_id": group_id,
|
||||
"HTMLData": HTMLData,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
const SEARCH_URL = '<?= base_url('Lab_Admin/pathology_masters_search') ?>';
|
||||
const TEMPLATE_URL = '<?= base_url('Lab_Admin/pathology_master_template') ?>';
|
||||
const APPLY_PANEL_URL = '<?= base_url('Lab_Admin/pathology_master_apply_panel') ?>';
|
||||
const nameInput = document.getElementById('input_Reportname');
|
||||
const loincInput = document.getElementById('input_loinc_code');
|
||||
const groupInput = document.getElementById('group_id');
|
||||
const dropdown = document.getElementById('report-name-suggestions');
|
||||
const spinner = document.getElementById('loinc-fetch-spinner');
|
||||
const templateBtn = document.getElementById('btn-load-master-template');
|
||||
const applyPanelBtn = document.getElementById('btn-apply-master-panel');
|
||||
const templateSpinner = document.getElementById('master-template-spinner');
|
||||
const masterMeta = document.getElementById('selected-master-meta');
|
||||
|
||||
let debounceTimer = null;
|
||||
let selectedMaster = null;
|
||||
|
||||
// ── Autocomplete on keyup ────────────────────────────────────────────────
|
||||
nameInput.addEventListener('input', function () {
|
||||
clearTimeout(debounceTimer);
|
||||
const q = this.value.trim();
|
||||
|
||||
if (q.length < 2) {
|
||||
hideDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.classList.remove('d-none');
|
||||
debounceTimer = setTimeout(function () {
|
||||
fetch(SEARCH_URL + '?q=' + encodeURIComponent(q), {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(items => {
|
||||
spinner.classList.add('d-none');
|
||||
renderDropdown(items);
|
||||
})
|
||||
.catch(() => {
|
||||
spinner.classList.add('d-none');
|
||||
hideDropdown();
|
||||
});
|
||||
}, 320);
|
||||
});
|
||||
|
||||
function renderDropdown(items) {
|
||||
dropdown.innerHTML = '';
|
||||
if (! items || items.length === 0) {
|
||||
hideDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(function (item) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
|
||||
|
||||
const sourceBadge = item.source === 'bridge'
|
||||
? '<span class="badge bg-primary ms-1" title="From Bridge API">API</span>'
|
||||
: '<span class="badge bg-secondary ms-1" title="From local database">Local</span>';
|
||||
|
||||
const rateBadge = item.standard_rate
|
||||
? ' <span class="badge bg-warning text-dark ms-1" title="Standard Rate">Rs ' + escHtml(item.standard_rate) + '</span>'
|
||||
: '';
|
||||
|
||||
btn.innerHTML =
|
||||
'<span>' + escHtml(item.name) + ' ' + sourceBadge +
|
||||
(item.sub_category ? ' <span class="badge bg-info text-dark ms-1" title="Sub Category">' + escHtml(item.sub_category) + '</span>' : '') +
|
||||
rateBadge +
|
||||
'</span>' +
|
||||
(item.loinc_code
|
||||
? '<span class="badge bg-success ms-2" title="LOINC">' + escHtml(item.loinc_code) + '</span>'
|
||||
: '<span class="badge bg-light text-muted border ms-2">No LOINC</span>');
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
nameInput.value = item.name;
|
||||
loincInput.value = item.loinc_code || '';
|
||||
applyGroupFromSubCategory(item.sub_category || '');
|
||||
selectedMaster = item;
|
||||
updateMasterMeta(item);
|
||||
hideDropdown();
|
||||
nameInput.focus();
|
||||
});
|
||||
|
||||
dropdown.appendChild(btn);
|
||||
});
|
||||
|
||||
dropdown.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideDropdown() {
|
||||
dropdown.style.display = 'none';
|
||||
dropdown.innerHTML = '';
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function (e) {
|
||||
if (! nameInput.contains(e.target) && ! dropdown.contains(e.target)) {
|
||||
hideDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function normalizeLabel(str) {
|
||||
return String(str || '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function canonicalGroupKey(str) {
|
||||
const key = normalizeLabel(str);
|
||||
if (!key) return '';
|
||||
|
||||
const aliasMap = {
|
||||
hematology: 'haematology',
|
||||
haematology: 'haematology',
|
||||
biochemistry: 'biochemistry',
|
||||
biochem: 'biochemistry',
|
||||
endocrinology: 'endocrinology',
|
||||
vitaminsminerals: 'vitaminsminerals',
|
||||
vitaminsandminerals: 'vitaminsminerals',
|
||||
infectiousdisease: 'infectiousdisease',
|
||||
microbiology: 'infectiousdisease',
|
||||
inflammatorymarkers: 'inflammatorymarkers',
|
||||
tumormarkers: 'tumormarkers',
|
||||
clinicalpathology: 'clinicalpathology'
|
||||
};
|
||||
|
||||
return aliasMap[key] || key;
|
||||
}
|
||||
|
||||
function updateMasterMeta(item) {
|
||||
if (!masterMeta) return;
|
||||
|
||||
if (!item) {
|
||||
masterMeta.textContent = 'Select a Bridge API master row, then click "Load Master Template".';
|
||||
return;
|
||||
}
|
||||
|
||||
const source = item.source === 'bridge' ? 'Bridge API' : 'Local';
|
||||
const name = item.display_name || item.test_name || item.name || '';
|
||||
const loinc = item.loinc_code || 'NA';
|
||||
const sub = item.sub_category || 'NA';
|
||||
const rate = item.standard_rate ? ('Rs ' + item.standard_rate) : 'NA';
|
||||
|
||||
masterMeta.textContent = 'Selected: ' + name + ' | LOINC: ' + loinc + ' | Sub Category: ' + sub + ' | Rate: ' + rate + ' | Source: ' + source;
|
||||
}
|
||||
|
||||
function applyGroupFromSubCategory(subCategory) {
|
||||
if (!groupInput) return;
|
||||
|
||||
const wanted = canonicalGroupKey(subCategory);
|
||||
const options = Array.from(groupInput.options || []);
|
||||
let match = null;
|
||||
|
||||
if (wanted) {
|
||||
match = options.find(function (opt) {
|
||||
return canonicalGroupKey(opt.textContent) === wanted;
|
||||
}) || null;
|
||||
|
||||
if (!match) {
|
||||
match = options.find(function (opt) {
|
||||
const txt = canonicalGroupKey(opt.textContent);
|
||||
return txt.includes(wanted) || wanted.includes(txt);
|
||||
}) || null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
match = options.find(function (opt) {
|
||||
return normalizeLabel(opt.textContent).includes('pathology');
|
||||
}) || null;
|
||||
}
|
||||
|
||||
if (match && groupInput.value !== match.value) {
|
||||
groupInput.value = match.value;
|
||||
groupInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
templateBtn.addEventListener('click', function () {
|
||||
const panelName = (selectedMaster && (selectedMaster.test_name || selectedMaster.name))
|
||||
? String(selectedMaster.test_name || selectedMaster.name).trim()
|
||||
: nameInput.value.trim();
|
||||
|
||||
if (!panelName) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Missing report name', 'Select a pathology master or enter report name first.');
|
||||
}
|
||||
nameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
templateBtn.disabled = true;
|
||||
templateSpinner.classList.remove('d-none');
|
||||
|
||||
fetch(TEMPLATE_URL + '?parent_test=' + encodeURIComponent(panelName), {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(resp => {
|
||||
templateBtn.disabled = false;
|
||||
templateSpinner.classList.add('d-none');
|
||||
|
||||
if (!resp || Number(resp.ok) !== 1) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Master template fetch failed', (resp && resp.error) ? resp.error : 'Unable to fetch template.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const existingHtml = (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.HTMLData)
|
||||
? CKEDITOR.instances.HTMLData.getData().trim()
|
||||
: String(document.getElementById('HTMLData').value || '').trim();
|
||||
|
||||
if (existingHtml !== '' && !confirm('Replace existing template content with gateway master template?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.HTMLData) {
|
||||
CKEDITOR.instances.HTMLData.setData(resp.template_html || '');
|
||||
} else {
|
||||
document.getElementById('HTMLData').value = resp.template_html || '';
|
||||
}
|
||||
|
||||
if (resp.loinc_code) {
|
||||
loincInput.value = resp.loinc_code;
|
||||
}
|
||||
if (resp.sub_category) {
|
||||
applyGroupFromSubCategory(resp.sub_category);
|
||||
}
|
||||
|
||||
selectedMaster = Object.assign({}, selectedMaster || {}, {
|
||||
name: resp.panel_name || panelName,
|
||||
test_name: resp.panel_name || panelName,
|
||||
loinc_code: resp.loinc_code || loincInput.value,
|
||||
sub_category: resp.sub_category || '',
|
||||
standard_rate: resp.standard_rate || '',
|
||||
source: 'bridge',
|
||||
});
|
||||
updateMasterMeta(selectedMaster);
|
||||
|
||||
if (typeof notify === 'function') {
|
||||
notify('success', 'Template loaded', 'Loaded ' + String(resp.components_count || 0) + ' component rows from gateway master.');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
templateBtn.disabled = false;
|
||||
templateSpinner.classList.add('d-none');
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Master template fetch failed', 'Network or server error while loading master template.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
applyPanelBtn.addEventListener('click', function () {
|
||||
const repoId = String(document.getElementById('repo_id').value || '').trim();
|
||||
const panelName = (selectedMaster && (selectedMaster.test_name || selectedMaster.name))
|
||||
? String(selectedMaster.test_name || selectedMaster.name).trim()
|
||||
: nameInput.value.trim();
|
||||
|
||||
if (!repoId || Number(repoId) <= 0) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Save report first', 'Create/save report once, then apply full panel mapping.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!panelName) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Missing report name', 'Select a pathology master or enter panel name first.');
|
||||
}
|
||||
nameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('This will replace current panel test mapping and print template with gateway panel mapping. Continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyPanelBtn.disabled = true;
|
||||
templateSpinner.classList.remove('d-none');
|
||||
|
||||
const csrfTokenName = '<?= csrf_token() ?>';
|
||||
const csrfTokenValue = document.querySelector('input[name="<?= csrf_token() ?>"]')?.value || '<?= csrf_hash() ?>';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('repo_id', repoId);
|
||||
formData.append('panel_name', panelName);
|
||||
formData.append('replace_existing', '1');
|
||||
formData.append(csrfTokenName, csrfTokenValue);
|
||||
|
||||
fetch(APPLY_PANEL_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
body: formData,
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(resp => {
|
||||
applyPanelBtn.disabled = false;
|
||||
templateSpinner.classList.add('d-none');
|
||||
|
||||
if (!resp || Number(resp.ok) !== 1) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Panel apply failed', (resp && resp.error) ? resp.error : 'Unable to apply panel mapping.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.HTMLData) {
|
||||
CKEDITOR.instances.HTMLData.setData(resp.template_html || '');
|
||||
} else {
|
||||
document.getElementById('HTMLData').value = resp.template_html || '';
|
||||
}
|
||||
|
||||
if (resp.panel_loinc_code) {
|
||||
loincInput.value = resp.panel_loinc_code;
|
||||
}
|
||||
|
||||
if (typeof notify === 'function') {
|
||||
notify(
|
||||
'success',
|
||||
'Panel mapping applied',
|
||||
'Imported ' + String(resp.components_count || 0) + ' tests (created: ' + String(resp.created_tests || 0) + ', updated: ' + String(resp.updated_tests || 0) + ').'
|
||||
);
|
||||
}
|
||||
|
||||
// Reload same edit screen so report parameter list reflects imported panel components.
|
||||
if (typeof load_form_div === 'function') {
|
||||
load_form_div('<?= base_url('Lab_Admin/reportedit_load') ?>/' + encodeURIComponent(repoId), 'test_div');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
applyPanelBtn.disabled = false;
|
||||
templateSpinner.classList.add('d-none');
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Panel apply failed', 'Network or server error while applying panel mapping.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
nameInput.addEventListener('change', function () {
|
||||
if (selectedMaster && String(selectedMaster.name || '').trim() !== this.value.trim()) {
|
||||
selectedMaster = null;
|
||||
updateMasterMeta(null);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Save / Insert ────────────────────────────────────────────────────────
|
||||
document.getElementById('updatereport').addEventListener('click', function () {
|
||||
const repo_id = document.getElementById('repo_id').value;
|
||||
const name = nameInput.value.trim();
|
||||
const charge_id = document.getElementById('charge_id').value;
|
||||
const group_id = document.getElementById('group_id').value;
|
||||
const loinc_code = loincInput.value.trim();
|
||||
const HTMLData = (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances.HTMLData)
|
||||
? CKEDITOR.instances.HTMLData.getData()
|
||||
: document.getElementById('HTMLData').value;
|
||||
const csrf_token = '<?= csrf_token() ?>';
|
||||
const csrf_value = document.querySelector('input[name="<?= csrf_token() ?>"]')?.value || '<?= csrf_hash() ?>';
|
||||
|
||||
const payload = {
|
||||
repo_id,
|
||||
input_Reportname: name,
|
||||
charge_id,
|
||||
group_id,
|
||||
HTMLData,
|
||||
loinc_code,
|
||||
[csrf_token]: csrf_value
|
||||
};
|
||||
|
||||
const url = repo_id > 0
|
||||
? '<?= base_url('Lab_Admin/report_update') ?>'
|
||||
: '<?= base_url('Lab_Admin/report_insert') ?>';
|
||||
|
||||
$.post(url, payload, function (data) {
|
||||
if (repo_id > 0) {
|
||||
if (data.showcontent && typeof notify === 'function') {
|
||||
notify('success', 'Saved', data.showcontent);
|
||||
}
|
||||
}, 'json');
|
||||
} else {
|
||||
$.post('<?= base_url('Lab_Admin/report_insert') ?>', {
|
||||
"repo_id": repo_id,
|
||||
"input_Reportname": input_Reportname,
|
||||
"charge_id": charge_id,
|
||||
"group_id": group_id,
|
||||
"HTMLData": HTMLData,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
} else {
|
||||
if (data.insertid > 0) {
|
||||
load_form('<?= base_url('Lab_Admin/report_list') ?>');
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
}
|
||||
}, 'json');
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ $Unit = '';
|
|||
$FixedNormals = '';
|
||||
$isGenderSpecific = 0;
|
||||
$FixedNormalsWomen = '';
|
||||
$checkbox_checked = '';
|
||||
$loincCode = '';
|
||||
$loincProperty = '';
|
||||
$loincSystem = '';
|
||||
$loincScale = '';
|
||||
|
||||
if (count($lab_test_parameter ?? []) > 0) {
|
||||
$mstTestKey = $lab_test_parameter[0]->mstTestKey ?? 0;
|
||||
|
|
@ -24,241 +27,407 @@ if (count($lab_test_parameter ?? []) > 0) {
|
|||
$FixedNormals = $lab_test_parameter[0]->FixedNormals ?? '';
|
||||
$isGenderSpecific = (int) ($lab_test_parameter[0]->isGenderSpecific ?? 0);
|
||||
$FixedNormalsWomen = $lab_test_parameter[0]->FixedNormalsWomen ?? '';
|
||||
$loincCode = $lab_test_parameter[0]->loinc_code ?? '';
|
||||
$loincProperty = $lab_test_parameter[0]->loinc_property ?? '';
|
||||
$loincSystem = $lab_test_parameter[0]->loinc_system ?? '';
|
||||
$loincScale = $lab_test_parameter[0]->loinc_scale ?? '';
|
||||
}
|
||||
|
||||
if ($isGenderSpecific == 1) {
|
||||
$checkbox_checked = 'checked';
|
||||
}
|
||||
$normalMin = '';
|
||||
$normalMax = '';
|
||||
if (strpos((string) $FixedNormals, '-') !== false) {
|
||||
[$normalMin, $normalMax] = array_pad(explode('-', (string) $FixedNormals, 2), 2, '');
|
||||
}
|
||||
|
||||
$femaleNormalMin = '';
|
||||
$femaleNormalMax = '';
|
||||
if (strpos((string) $FixedNormalsWomen, '-') !== false) {
|
||||
[$femaleNormalMin, $femaleNormalMax] = array_pad(explode('-', (string) $FixedNormalsWomen, 2), 2, '');
|
||||
}
|
||||
|
||||
$valueType = 'TEXT';
|
||||
if ($Formula !== '') {
|
||||
$valueType = 'FORMULA';
|
||||
} elseif ($VRule !== '' || ($normalMin !== '' || $normalMax !== '')) {
|
||||
$valueType = 'NUMERIC';
|
||||
}
|
||||
?>
|
||||
<?= form_open() ?>
|
||||
<div class="card admin-card">
|
||||
<div class="card admin-card component-rule-card">
|
||||
<style>
|
||||
.component-rule-card {
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.component-rule-card .card-header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.component-rule-card .card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #567299;
|
||||
}
|
||||
|
||||
.component-rule-card .card-body {
|
||||
padding: 16px 18px 12px;
|
||||
}
|
||||
|
||||
.component-rule-card .form-label {
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #567299;
|
||||
}
|
||||
|
||||
.component-rule-card .form-control,
|
||||
.component-rule-card .form-select {
|
||||
height: 38px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.component-rule-card textarea.form-control {
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.component-rule-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 170px 1fr 170px 1fr;
|
||||
gap: 12px 14px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.component-rule-grid .wide {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.component-rule-preset {
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.component-rule-preset .btn {
|
||||
font-size: 13px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.component-rule-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding-top: 14px;
|
||||
margin-top: 18px;
|
||||
border-top: 1px solid #e6edf5;
|
||||
}
|
||||
|
||||
.component-rule-hint {
|
||||
font-size: 12px;
|
||||
color: #7a8797;
|
||||
}
|
||||
|
||||
.component-gateway-meta {
|
||||
font-size: 12px;
|
||||
color: #5d738f;
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.component-rule-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.component-rule-grid .wide {
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card-header bg-white">
|
||||
<h3 class="mb-0">Test Parameter</h3>
|
||||
<h3>Edit Component Rule</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Name of Test</label>
|
||||
<input class="form-control" id="input_Test_name" placeholder="Test Name" value="<?= esc($Test) ?>" type="text" autocomplete="off">
|
||||
<input type="hidden" id="mstTestKey" value="<?= esc($mstTestKey) ?>">
|
||||
<input type="hidden" id="mstRepoKey" value="<?= esc($mstRepoKey ?? 0) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Test code</label>
|
||||
<input class="form-control" id="input_test_code" placeholder="Test code" value="<?= esc($TestID) ?>" type="text" autocomplete="off">
|
||||
<input type="hidden" id="mstTestKey" value="<?= esc($mstTestKey) ?>">
|
||||
<input type="hidden" id="mstRepoKey" value="<?= esc($mstRepoKey ?? 0) ?>">
|
||||
<input type="hidden" id="input_test_code" value="<?= esc($TestID) ?>">
|
||||
<input type="hidden" id="input_Formula" value="<?= esc($Formula) ?>">
|
||||
<input type="hidden" id="input_loinc_property" value="<?= esc($loincProperty) ?>">
|
||||
<input type="hidden" id="input_loinc_system" value="<?= esc($loincSystem) ?>">
|
||||
<input type="hidden" id="input_loinc_scale" value="<?= esc($loincScale) ?>">
|
||||
|
||||
<div class="component-rule-grid">
|
||||
<label for="input_Test_name" class="form-label text-end">Component</label>
|
||||
<input class="form-control wide" id="input_Test_name" value="<?= esc($Test) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="input_loinc_code" class="form-label text-end">LOINC Code</label>
|
||||
<div>
|
||||
<div class="d-flex gap-2">
|
||||
<input class="form-control" id="input_loinc_code" value="<?= esc($loincCode) ?>" type="text" autocomplete="off">
|
||||
<button type="button" class="btn btn-outline-primary" id="btn_load_gateway_component">Load From API</button>
|
||||
</div>
|
||||
<div id="gateway_meta_text" class="component-gateway-meta">Property: <?= esc($loincProperty ?: '-') ?> | System: <?= esc($loincSystem ?: '-') ?> | Scale: <?= esc($loincScale ?: '-') ?></div>
|
||||
</div>
|
||||
|
||||
<label for="input_Unit" class="form-label text-end">Units</label>
|
||||
<input class="form-control" id="input_Unit" placeholder="e.g. g/dL" value="<?= esc($Unit) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="rule_value_type" class="form-label text-end">Value Type</label>
|
||||
<select class="form-select" id="rule_value_type">
|
||||
<option value="NUMERIC" <?= $valueType === 'NUMERIC' ? 'selected' : '' ?>>NUMERIC</option>
|
||||
<option value="TEXT" <?= $valueType === 'TEXT' ? 'selected' : '' ?>>TEXT</option>
|
||||
<option value="FORMULA" <?= $valueType === 'FORMULA' ? 'selected' : '' ?>>FORMULA</option>
|
||||
</select>
|
||||
|
||||
<label class="form-label text-end">Gender Specific</label>
|
||||
<div>
|
||||
<label class="form-check-label"><input id="chk_isGenderSpecific" class="form-check-input me-2" type="checkbox" <?= $isGenderSpecific === 1 ? 'checked' : '' ?>>Use separate female normal range</label>
|
||||
</div>
|
||||
|
||||
<label for="input_Default" class="form-label text-end">Default Value</label>
|
||||
<input class="form-control wide" id="input_Default" placeholder="Default result value" value="<?= esc($Result) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="input_Validation" class="form-label text-end">Validation Rule</label>
|
||||
<input class="form-control wide" id="input_Validation" placeholder="e.g. numeric|min:0|max:100" value="<?= esc($VRule) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label class="form-label text-end">Quick Preset</label>
|
||||
<div class="wide">
|
||||
<div class="component-rule-preset">
|
||||
<button type="button" class="btn btn-outline-secondary js-rule-preset" data-rule="numeric|min:0|max:100">CBC Numeric</button>
|
||||
<button type="button" class="btn btn-outline-secondary js-rule-preset" data-rule="numeric|min:0|max:1000">LFT Numeric</button>
|
||||
<button type="button" class="btn btn-outline-secondary js-rule-preset" data-rule="numeric|min:0|max:10000">Hormone Numeric</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="normal_min" class="form-label text-end">Normal Min</label>
|
||||
<input class="form-control" id="normal_min" placeholder="e.g. 12.0" value="<?= esc($normalMin) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="normal_max" class="form-label text-end">Normal Max</label>
|
||||
<input class="form-control" id="normal_max" placeholder="e.g. 15.5" value="<?= esc($normalMax) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="female_normal_min" class="form-label text-end">Female Normal Min</label>
|
||||
<input class="form-control" id="female_normal_min" placeholder="optional" value="<?= esc($femaleNormalMin) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="female_normal_max" class="form-label text-end">Female Normal Max</label>
|
||||
<input class="form-control" id="female_normal_max" placeholder="optional" value="<?= esc($femaleNormalMax) ?>" type="text" autocomplete="off">
|
||||
|
||||
<label for="input_Message" class="form-label text-end">Notes</label>
|
||||
<textarea class="form-control wide" id="input_Message" placeholder="Adult male, fasting, etc."><?= esc($VMsg) ?></textarea>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Default Value</label>
|
||||
<input class="form-control" id="input_Default" placeholder="Default Value" value="<?= esc($Result) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Formula</label>
|
||||
<input class="form-control" id="input_Formula" placeholder="Formula" value="<?= esc($Formula) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Validation Rule</label>
|
||||
<input class="form-control" id="input_Validation" placeholder="Validation Rule" value="<?= esc($VRule) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Unit</label>
|
||||
<input class="form-control" id="input_Unit" placeholder="Unit" value="<?= esc($Unit) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Message</label>
|
||||
<input class="form-control" id="input_Message" placeholder="Message" value="<?= esc($VMsg) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Fixed Normals</label>
|
||||
<input class="form-control" id="input_Fixed" placeholder="Fixed Normals" value="<?= esc($FixedNormals) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label><input id="chk_isGenderSpecific" name="chk_isGenderSpecific" type="checkbox" <?= $checkbox_checked ?>> Is Gender Specific</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Fixed Normals For Women</label>
|
||||
<input class="form-control" id="input_FixedNormalsWomen" placeholder="Fixed Normals For Women" value="<?= esc($FixedNormalsWomen) ?>" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-12" id="div_option_list">
|
||||
<?= view('PathLab_Report/_test_option_table', ['lab_test_option' => $lab_test_option ?? [], 'mstTestKey' => $mstTestKey]) ?>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label>Code</label>
|
||||
<input class="form-control" id="input_op_value" name="input_op_value" placeholder="Test Name" value="" type="text" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<label><input id="chk_bold" name="chk_bold" type="checkbox"> Bold</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<button type="button" class="btn btn-primary" id="btn_add_option">Add Option</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<button type="button" class="btn btn-primary" id="btn_item_update">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<button onclick="load_form_div('<?= base_url('Lab_Admin/report_test_list') ?>/<?= esc($mstRepoKey ?? 0) ?>','test_div');" type="button" class="btn btn-outline-primary">Test List</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<button onclick="load_form_div('<?= base_url('Lab_Admin/test_parameter_load') ?>/0/<?= esc($mstRepoKey ?? 0) ?>','test_div');" type="button" class="btn btn-outline-secondary">Add New Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-rule-hint mt-2">Normal range is stored as `min-max`. Female normal range is used only when gender-specific range is enabled.</div>
|
||||
|
||||
<div class="component-rule-actions">
|
||||
<button type="button" class="btn btn-primary" id="btn_item_update">Save Rule</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="btn_component_close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= form_close() ?>
|
||||
<script>
|
||||
$('#btn_item_update').click(function() {
|
||||
var input_Test_name = $('#input_Test_name').val();
|
||||
var input_test_code = $('#input_test_code').val();
|
||||
var input_Default = $('#input_Default').val();
|
||||
var input_Formula = $('#input_Formula').val();
|
||||
var input_Validation = $('#input_Validation').val();
|
||||
var input_Unit = $('#input_Unit').val();
|
||||
var input_Message = $('#input_Message').val();
|
||||
var input_Fixed = $('#input_Fixed').val();
|
||||
var input_FixedNormalsWomen = $('#input_FixedNormalsWomen').val();
|
||||
var mstTestKey = $('#mstTestKey').val();
|
||||
var mstRepoKey = $('#mstRepoKey').val();
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
var isChecked = $('#chk_isGenderSpecific').is(':checked') ? 1 : 0;
|
||||
(function () {
|
||||
var GATEWAY_COMPONENT_SEARCH_URL = '<?= base_url('Lab_Admin/pathology_component_masters_search') ?>';
|
||||
|
||||
if (mstTestKey > 0) {
|
||||
$.post('<?= base_url('Lab_Admin/test_parameter_edit') ?>', {
|
||||
"input_Test_name": input_Test_name,
|
||||
"input_test_code": input_test_code,
|
||||
"input_Default": input_Default,
|
||||
"input_Formula": input_Formula,
|
||||
"input_Validation": input_Validation,
|
||||
"input_Unit": input_Unit,
|
||||
"input_Message": input_Message,
|
||||
"input_Fixed": input_Fixed,
|
||||
"input_isChecked": isChecked,
|
||||
"input_FixedNormalsWomen": input_FixedNormalsWomen,
|
||||
"mstTestKey": mstTestKey,
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
function buildRangeValue(minValue, maxValue) {
|
||||
var min = String(minValue || '').trim();
|
||||
var max = String(maxValue || '').trim();
|
||||
if (min === '' && max === '') {
|
||||
return '';
|
||||
}
|
||||
return min + '-' + max;
|
||||
}
|
||||
|
||||
function setGatewayMeta(item) {
|
||||
var p = document.getElementById('input_loinc_property');
|
||||
var s = document.getElementById('input_loinc_system');
|
||||
var sc = document.getElementById('input_loinc_scale');
|
||||
var text = document.getElementById('gateway_meta_text');
|
||||
|
||||
var property = String((item && item.property) || '').trim();
|
||||
var system = String((item && item.specimen_system) || '').trim();
|
||||
var scale = String((item && item.scale_type) || '').trim();
|
||||
|
||||
if (p) p.value = property;
|
||||
if (s) s.value = system;
|
||||
if (sc) sc.value = scale;
|
||||
if (text) {
|
||||
text.textContent = 'Property: ' + (property || '-') + ' | System: ' + (system || '-') + ' | Scale: ' + (scale || '-');
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeName(v) {
|
||||
return String(v || '').toLowerCase().replace(/[^a-z0-9]+/g, '').trim();
|
||||
}
|
||||
|
||||
function pickBestMatch(items, targetName, targetCode) {
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var wantedName = normalizeName(targetName);
|
||||
var wantedCode = String(targetCode || '').trim().toUpperCase();
|
||||
var first = items[0];
|
||||
|
||||
for (var i = 0; i < items.length; i += 1) {
|
||||
var item = items[i] || {};
|
||||
var itemCode = String(item.code || '').trim().toUpperCase();
|
||||
if (wantedCode !== '' && itemCode === wantedCode) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < items.length; j += 1) {
|
||||
var it = items[j] || {};
|
||||
if (normalizeName(it.name || '') === wantedName) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
function loadGatewayComponentMeta(silent) {
|
||||
var nameInput = document.getElementById('input_Test_name');
|
||||
var codeInput = document.getElementById('input_loinc_code');
|
||||
var unitInput = document.getElementById('input_Unit');
|
||||
var btn = document.getElementById('btn_load_gateway_component');
|
||||
var q = String((nameInput && nameInput.value) || '').trim();
|
||||
|
||||
if (q.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (btn) btn.disabled = true;
|
||||
|
||||
fetch(GATEWAY_COMPONENT_SEARCH_URL + '?q=' + encodeURIComponent(q), {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (resp) {
|
||||
if (btn) btn.disabled = false;
|
||||
if (!resp || Number(resp.ok || 0) !== 1) {
|
||||
if (!silent && typeof notify === 'function') {
|
||||
notify('warning', 'Gateway lookup failed', (resp && resp.error) ? resp.error : 'Unable to fetch component metadata.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var match = pickBestMatch(resp.items || [], q, codeInput ? codeInput.value : '');
|
||||
if (!match) {
|
||||
if (!silent && typeof notify === 'function') {
|
||||
notify('warning', 'No match found', 'No matching component metadata found in gateway.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (codeInput && !String(codeInput.value || '').trim() && match.code) {
|
||||
codeInput.value = String(match.code || '').trim();
|
||||
}
|
||||
if (unitInput && !String(unitInput.value || '').trim() && match.unit) {
|
||||
unitInput.value = String(match.unit || '').trim();
|
||||
}
|
||||
|
||||
setGatewayMeta(match);
|
||||
|
||||
if (!silent && typeof notify === 'function') {
|
||||
notify('success', 'Gateway metadata loaded', 'Component rule helper fields filled from API.');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (btn) btn.disabled = false;
|
||||
if (!silent && typeof notify === 'function') {
|
||||
notify('warning', 'Gateway lookup failed', 'Network/server error while fetching metadata.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var gatewayBtn = document.getElementById('btn_load_gateway_component');
|
||||
if (gatewayBtn) {
|
||||
gatewayBtn.addEventListener('click', function () {
|
||||
loadGatewayComponentMeta(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-load helper metadata when opening edit rule.
|
||||
loadGatewayComponentMeta(true);
|
||||
|
||||
document.querySelectorAll('.js-rule-preset').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var rule = String(btn.getAttribute('data-rule') || '').trim();
|
||||
var ruleInput = document.getElementById('input_Validation');
|
||||
var valueType = document.getElementById('rule_value_type');
|
||||
if (ruleInput) {
|
||||
ruleInput.value = rule;
|
||||
}
|
||||
if (valueType) {
|
||||
valueType.value = 'NUMERIC';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('btn_component_close').addEventListener('click', function () {
|
||||
load_form_div('<?= base_url('Lab_Admin/report_test_list') ?>/<?= esc($mstRepoKey ?? 0) ?>', 'test_div');
|
||||
});
|
||||
|
||||
document.getElementById('btn_item_update').addEventListener('click', function () {
|
||||
var input_Test_name = document.getElementById('input_Test_name').value;
|
||||
var input_test_code = document.getElementById('input_test_code').value;
|
||||
var input_loinc_code = document.getElementById('input_loinc_code').value;
|
||||
var input_Default = document.getElementById('input_Default').value;
|
||||
var input_Formula = document.getElementById('input_Formula').value;
|
||||
var input_Validation = document.getElementById('input_Validation').value;
|
||||
var input_Unit = document.getElementById('input_Unit').value;
|
||||
var input_loinc_property = document.getElementById('input_loinc_property').value;
|
||||
var input_loinc_system = document.getElementById('input_loinc_system').value;
|
||||
var input_loinc_scale = document.getElementById('input_loinc_scale').value;
|
||||
var input_Message = document.getElementById('input_Message').value;
|
||||
var input_Fixed = buildRangeValue(document.getElementById('normal_min').value, document.getElementById('normal_max').value);
|
||||
var input_FixedNormalsWomen = buildRangeValue(document.getElementById('female_normal_min').value, document.getElementById('female_normal_max').value);
|
||||
var mstTestKey = document.getElementById('mstTestKey').value;
|
||||
var mstRepoKey = document.getElementById('mstRepoKey').value;
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
var isChecked = document.getElementById('chk_isGenderSpecific').checked ? 1 : 0;
|
||||
|
||||
var payload = {
|
||||
"input_Test_name": input_Test_name,
|
||||
"input_test_code": input_test_code,
|
||||
"input_loinc_code": input_loinc_code,
|
||||
"input_Default": input_Default,
|
||||
"input_Formula": input_Formula,
|
||||
"input_Validation": input_Validation,
|
||||
"input_Unit": input_Unit,
|
||||
"input_loinc_property": input_loinc_property,
|
||||
"input_loinc_system": input_loinc_system,
|
||||
"input_loinc_scale": input_loinc_scale,
|
||||
"input_Message": input_Message,
|
||||
"input_Fixed": input_Fixed,
|
||||
"input_isChecked": isChecked,
|
||||
"input_FixedNormalsWomen": input_FixedNormalsWomen,
|
||||
"mstTestKey": mstTestKey,
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
};
|
||||
|
||||
if (Number(mstTestKey) > 0) {
|
||||
$.post('<?= base_url('Lab_Admin/test_parameter_edit') ?>', payload, function (data) {
|
||||
if (data.showcontent && typeof notify === 'function') {
|
||||
notify('success', 'Saved', data.showcontent);
|
||||
}
|
||||
load_form_div('<?= base_url('Lab_Admin/report_test_list') ?>/<?= esc($mstRepoKey ?? 0) ?>', 'test_div');
|
||||
}, 'json');
|
||||
} else {
|
||||
$.post('<?= base_url('Lab_Admin/test_parameter_add') ?>', {
|
||||
"input_Test_name": input_Test_name,
|
||||
"input_test_code": input_test_code,
|
||||
"input_Default": input_Default,
|
||||
"input_Formula": input_Formula,
|
||||
"input_Validation": input_Validation,
|
||||
"input_Unit": input_Unit,
|
||||
"input_Message": input_Message,
|
||||
"input_Fixed": input_Fixed,
|
||||
"mstTestKey": mstTestKey,
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
if (data.insert_id > 0) {
|
||||
$('#mstTestKey').val(data.insert_id);
|
||||
$.post('<?= base_url('Lab_Admin/test_parameter_add') ?>', payload, function (data) {
|
||||
if (Number(data.insert_id || 0) > 0) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('success', 'Saved', data.showcontent || 'Data Saved successfully');
|
||||
}
|
||||
load_form_div('<?= base_url('Lab_Admin/report_test_list') ?>/<?= esc($mstRepoKey ?? 0) ?>', 'test_div');
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn_add_option').click(function() {
|
||||
var input_op_value = $('#input_op_value').val();
|
||||
var chk_bold = $('#chk_bold').is(':checked') ? 1 : 0;
|
||||
var mstTestKey = $('#mstTestKey').val();
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
|
||||
if (mstTestKey > 0) {
|
||||
$.post('<?= base_url('Lab_Admin/test_parameter_option_add') ?>', {
|
||||
"input_op_value": input_op_value,
|
||||
"chk_bold": chk_bold,
|
||||
"mstTestKey": mstTestKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
if (data.insert_id > 0) {
|
||||
$('#div_option_list').html(data.option_content);
|
||||
}
|
||||
}, 'json');
|
||||
} else {
|
||||
alert('First create test');
|
||||
}
|
||||
});
|
||||
|
||||
function sortchange(mstTestKey, option_current, sort_current, option_prev, sort_prev) {
|
||||
var postStr = '<?= base_url('Lab_Admin/change_sort') ?>/' + mstTestKey + '/' + option_current + '/' + sort_current + '/' + option_prev + '/' + sort_prev;
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
|
||||
$.post(postStr, {
|
||||
"mstTestKey": mstTestKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
if (data.insert_id > 0) {
|
||||
$('#div_option_list').html(data.option_content);
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
function remove_option(option_id, mstTestKey) {
|
||||
var postStr = '<?= base_url('Lab_Admin/remove_test_option') ?>/' + option_id + '/' + mstTestKey;
|
||||
var csrf_value = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
|
||||
$.post(postStr, {
|
||||
"mstTestKey": mstTestKey,
|
||||
"<?= csrf_token() ?>": csrf_value
|
||||
}, function(data) {
|
||||
if (data.insert_id > 0) {
|
||||
$('#div_option_list').html(data.option_content);
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,80 +1,474 @@
|
|||
<?= form_open() ?>
|
||||
<div class="card admin-card">
|
||||
<div class="card-header bg-white d-flex align-items-center justify-content-between">
|
||||
<h3 class="mb-0">Test List</h3>
|
||||
<button onclick="load_form_div('<?= base_url('Lab_Admin/test_search_page') ?>/<?= esc($mstRepoKey ?? 0) ?>','test_div');" type="button" class="btn btn-primary btn-sm">Add New Test</button>
|
||||
<div id="test_div">
|
||||
<div class="card admin-card component-attach-card">
|
||||
<style>
|
||||
.component-attach-card .card-header h3 {
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
font-weight: 500;
|
||||
color: #567299;
|
||||
}
|
||||
|
||||
.component-panel-pill {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border-radius: 3px;
|
||||
background: #2f72b1;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.component-panel-pill .label {
|
||||
opacity: 0.95;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.component-attach-toolbar {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 160px;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
margin-top: 14px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.component-attach-toolbar .form-control,
|
||||
.component-attach-toolbar .btn {
|
||||
height: 46px;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#gatewayComponentSuggestions {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 174px;
|
||||
top: 48px;
|
||||
z-index: 10;
|
||||
display: none;
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #d5dde8;
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#gatewayComponentSuggestions .list-group-item {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#gatewayComponentSuggestions .list-group-item.active,
|
||||
#gatewayComponentSuggestions .list-group-item:focus {
|
||||
background: #e8f1fb;
|
||||
border-color: #b8d0ea;
|
||||
color: #1f3f66;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.component-attach-table th,
|
||||
.component-attach-table td {
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.component-attach-table th {
|
||||
color: #6c7f96;
|
||||
}
|
||||
|
||||
.component-attach-table .col-number {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.component-attach-table .col-action {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.component-action-stack {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.component-action-stack .btn {
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.component-action-stack .btn-arrow {
|
||||
min-width: 40px;
|
||||
padding: 4px 8px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.component-attach-footer {
|
||||
margin-top: 18px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.component-attach-footer .btn {
|
||||
min-width: 90px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.component-attach-card .btn {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.component-attach-card .form-control {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.component-attach-card .card-header h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.component-panel-pill {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.component-attach-toolbar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.component-attach-toolbar .form-control,
|
||||
.component-attach-toolbar .btn {
|
||||
height: auto;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#gatewayComponentSuggestions {
|
||||
right: 0;
|
||||
top: 44px;
|
||||
}
|
||||
|
||||
.component-attach-table th,
|
||||
.component-attach-table td,
|
||||
.component-action-stack .btn {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card-header bg-white">
|
||||
<h3 class="mb-0">Add Components to Panel</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table id="example2" class="table table-striped table-hover align-middle TableData">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Test Name</th>
|
||||
<th>Test Code</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($i = 0; $i < count($lab_Rep_Item_List ?? []); ++$i) { ?>
|
||||
<span class="component-panel-pill"><span class="label">Panel:</span> <?= esc($panel_name ?? '') ?></span>
|
||||
|
||||
<div class="component-attach-toolbar">
|
||||
<input type="text" class="form-control" id="componentSearchInput" placeholder="Search component (name / short / code)">
|
||||
<button id="btnGatewayAdd" type="button" class="btn btn-primary">+ Add</button>
|
||||
<div id="gatewayComponentSuggestions" class="list-group"></div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="componentAttachTable" class="table table-bordered table-hover align-middle component-attach-table mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<td><?= $i + 1 ?></td>
|
||||
<td><?= esc($lab_Rep_Item_List[$i]->Test ?? '') ?></td>
|
||||
<td><?= esc($lab_Rep_Item_List[$i]->TestID ?? '') ?></td>
|
||||
<td>
|
||||
<div class="btn-group-horizontal">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="load_form_div('<?= base_url('Lab_Admin/test_parameter_load') ?>/<?= esc($lab_Rep_Item_List[$i]->mstTestKey ?? 0) ?>/<?= esc($mstRepoKey ?? 0) ?>','test_div');">
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" onclick="remove_item('<?= esc($mstRepoKey ?? 0) ?>','<?= esc($lab_Rep_Item_List[$i]->mstTestKey ?? 0) ?>');">
|
||||
Remove
|
||||
</button>
|
||||
<?php
|
||||
$option_current = $lab_Rep_Item_List[$i]->id ?? 0;
|
||||
$sort_current = $lab_Rep_Item_List[$i]->EOrder ?? 0;
|
||||
if ($i + 1 < count($lab_Rep_Item_List ?? [])) {
|
||||
$option_next = $lab_Rep_Item_List[$i + 1]->id ?? 0;
|
||||
$sort_next = $lab_Rep_Item_List[$i + 1]->EOrder ?? 0;
|
||||
echo '<button type="button" class="btn btn-outline-primary btn-sm" onclick="sortchange(' . (int) ($mstRepoKey ?? 0) . ',' . (int) $option_current . ',' . (int) $sort_current . ',' . (int) $option_next . ',' . (int) $sort_next . ')">Down</button>';
|
||||
}
|
||||
if ($i > 0) {
|
||||
$option_prev = $lab_Rep_Item_List[$i - 1]->id ?? 0;
|
||||
$sort_prev = $lab_Rep_Item_List[$i - 1]->EOrder ?? 0;
|
||||
echo '<button type="button" class="btn btn-outline-primary btn-sm" onclick="sortchange(' . (int) ($mstRepoKey ?? 0) . ',' . (int) $option_current . ',' . (int) $sort_current . ',' . (int) $option_prev . ',' . (int) $sort_prev . ')">Up</button>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<th class="col-number">#</th>
|
||||
<th>Component Name</th>
|
||||
<th>Short</th>
|
||||
<th>Code</th>
|
||||
<th class="col-action">Action</th>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($i = 0; $i < count($lab_Rep_Item_List ?? []); ++$i) { ?>
|
||||
<?php
|
||||
$option_current = $lab_Rep_Item_List[$i]->id ?? 0;
|
||||
$sort_current = $lab_Rep_Item_List[$i]->EOrder ?? 0;
|
||||
$shortCode = (string) ($lab_Rep_Item_List[$i]->short_name ?? $lab_Rep_Item_List[$i]->TestID ?? '');
|
||||
$componentCode = (string) ($lab_Rep_Item_List[$i]->component_code ?? '');
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $i + 1 ?></td>
|
||||
<td><?= esc($lab_Rep_Item_List[$i]->Test ?? '') ?></td>
|
||||
<td><?= esc($shortCode) ?></td>
|
||||
<td><?= esc($componentCode) ?></td>
|
||||
<td>
|
||||
<div class="component-action-stack">
|
||||
<?php if ($i > 0) {
|
||||
$option_prev = $lab_Rep_Item_List[$i - 1]->id ?? 0;
|
||||
$sort_prev = $lab_Rep_Item_List[$i - 1]->EOrder ?? 0;
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-arrow" title="Move Up" onclick="sortchange(' . (int) ($mstRepoKey ?? 0) . ',' . (int) $option_current . ',' . (int) $sort_current . ',' . (int) $option_prev . ',' . (int) $sort_prev . ')">▲</button>';
|
||||
} else {
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-arrow disabled" title="Move Up">▲</button>';
|
||||
} ?>
|
||||
|
||||
<?php if ($i + 1 < count($lab_Rep_Item_List ?? [])) {
|
||||
$option_next = $lab_Rep_Item_List[$i + 1]->id ?? 0;
|
||||
$sort_next = $lab_Rep_Item_List[$i + 1]->EOrder ?? 0;
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-arrow" title="Move Down" onclick="sortchange(' . (int) ($mstRepoKey ?? 0) . ',' . (int) $option_current . ',' . (int) $sort_current . ',' . (int) $option_next . ',' . (int) $sort_next . ')">▼</button>';
|
||||
} else {
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-arrow disabled" title="Move Down">▼</button>';
|
||||
} ?>
|
||||
|
||||
<button type="button" class="btn btn-warning text-white" onclick="load_form_div('<?= base_url('Lab_Admin/test_parameter_load') ?>/<?= esc($lab_Rep_Item_List[$i]->mstTestKey ?? 0) ?>/<?= esc($mstRepoKey ?? 0) ?>','test_div');">Edit</button>
|
||||
<button type="button" class="btn btn-danger" onclick="remove_item('<?= esc($mstRepoKey ?? 0) ?>','<?= esc($lab_Rep_Item_List[$i]->mstTestKey ?? 0) ?>');">Remove</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="component-attach-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= form_close() ?>
|
||||
<script>
|
||||
function sortchange(mstRepoKey, option_current, sort_current, option_prev, sort_prev) {
|
||||
var postStr = '<?= base_url('Lab_Admin/change_sort_item') ?>/' + mstRepoKey + '/' + option_current + '/' + sort_current + '/' + option_prev + '/' + sort_prev;
|
||||
var csrfValue = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
$.post(postStr, {
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrfValue
|
||||
}, function(data) {
|
||||
$('#test_div').html(data);
|
||||
});
|
||||
}
|
||||
(function () {
|
||||
var GATEWAY_COMPONENT_SEARCH_URL = '<?= base_url('Lab_Admin/pathology_component_masters_search') ?>';
|
||||
var GATEWAY_COMPONENT_ADD_URL = '<?= base_url('Lab_Admin/pathology_master_add_component') ?>';
|
||||
var RELOAD_COMPONENT_LIST_URL = '<?= base_url('Lab_Admin/report_test_list') ?>/';
|
||||
var globalScope = window;
|
||||
|
||||
function remove_item(mstRepoKey, mstTestKey) {
|
||||
var postStr = '<?= base_url('Lab_Admin/remove_test_item') ?>/' + mstRepoKey + '/' + mstTestKey;
|
||||
var csrfValue = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
$.post(postStr, {
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrfValue
|
||||
}, function() {
|
||||
load_form_div('<?= base_url('Lab_Admin/report_test_list') ?>/' + mstRepoKey, 'test_div');
|
||||
});
|
||||
}
|
||||
window.sortchange = function (mstRepoKey, option_current, sort_current, option_prev, sort_prev) {
|
||||
var postStr = '<?= base_url('Lab_Admin/change_sort_item') ?>/' + mstRepoKey + '/' + option_current + '/' + sort_current + '/' + option_prev + '/' + sort_prev;
|
||||
var csrfValue = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
$.post(postStr, {
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrfValue
|
||||
}, function(data) {
|
||||
$('#test_div').html(data);
|
||||
});
|
||||
};
|
||||
|
||||
if (window.jQuery && $.fn && $.fn.DataTable) {
|
||||
$('#example2').DataTable();
|
||||
}
|
||||
window.remove_item = function (mstRepoKey, mstTestKey) {
|
||||
var postStr = '<?= base_url('Lab_Admin/remove_test_item') ?>/' + mstRepoKey + '/' + mstTestKey;
|
||||
var csrfValue = $('input[name="<?= csrf_token() ?>"]').first().val() || '<?= csrf_hash() ?>';
|
||||
$.post(postStr, {
|
||||
"mstRepoKey": mstRepoKey,
|
||||
"<?= csrf_token() ?>": csrfValue
|
||||
}, function() {
|
||||
load_form_div(RELOAD_COMPONENT_LIST_URL + mstRepoKey, 'test_div');
|
||||
});
|
||||
};
|
||||
|
||||
var input = document.getElementById('componentSearchInput');
|
||||
var addBtn = document.getElementById('btnGatewayAdd');
|
||||
var box = document.getElementById('gatewayComponentSuggestions');
|
||||
if (!input || !addBtn || !box) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selected = null;
|
||||
var searchTimer = null;
|
||||
var suggestionItems = [];
|
||||
var activeIndex = -1;
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function hideSuggestionBox() {
|
||||
box.style.display = 'none';
|
||||
box.innerHTML = '';
|
||||
suggestionItems = [];
|
||||
activeIndex = -1;
|
||||
}
|
||||
|
||||
function setActiveSuggestion(nextIndex) {
|
||||
if (!suggestionItems.length) {
|
||||
activeIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextIndex < 0) {
|
||||
nextIndex = suggestionItems.length - 1;
|
||||
}
|
||||
if (nextIndex >= suggestionItems.length) {
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
suggestionItems.forEach(function (btn, index) {
|
||||
btn.classList.toggle('active', index === nextIndex);
|
||||
});
|
||||
|
||||
activeIndex = nextIndex;
|
||||
if (suggestionItems[activeIndex] && typeof suggestionItems[activeIndex].scrollIntoView === 'function') {
|
||||
suggestionItems[activeIndex].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function chooseSuggestion(item) {
|
||||
selected = item;
|
||||
input.value = String(item.name || '').trim();
|
||||
hideSuggestionBox();
|
||||
}
|
||||
|
||||
function renderSuggestionBox(items) {
|
||||
box.innerHTML = '';
|
||||
suggestionItems = [];
|
||||
activeIndex = -1;
|
||||
if (!items || items.length === 0) {
|
||||
hideSuggestionBox();
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(function (item) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'list-group-item list-group-item-action';
|
||||
btn.innerHTML =
|
||||
'<div><strong>' + escHtml(item.name || '') + '</strong></div>'
|
||||
+ '<div class="text-muted">Short: ' + escHtml(item.short_name || '-')
|
||||
+ ' | Code: ' + escHtml(item.code || '-') + '</div>';
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
chooseSuggestion(item);
|
||||
});
|
||||
|
||||
box.appendChild(btn);
|
||||
suggestionItems.push(btn);
|
||||
});
|
||||
|
||||
box.style.display = 'block';
|
||||
setActiveSuggestion(0);
|
||||
}
|
||||
|
||||
if (!globalScope.__pathologyComponentModalHandlers) {
|
||||
globalScope.__pathologyComponentModalHandlers = {};
|
||||
}
|
||||
|
||||
function bindDocumentHandler(key, handler) {
|
||||
var bucket = globalScope.__pathologyComponentModalHandlers;
|
||||
if (bucket[key]) {
|
||||
document.removeEventListener('click', bucket[key]);
|
||||
}
|
||||
bucket[key] = handler;
|
||||
document.addEventListener('click', handler);
|
||||
}
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
var q = String(this.value || '').trim();
|
||||
selected = null;
|
||||
|
||||
clearTimeout(searchTimer);
|
||||
if (q.length < 2) {
|
||||
hideSuggestionBox();
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimer = setTimeout(function () {
|
||||
fetch(GATEWAY_COMPONENT_SEARCH_URL + '?q=' + encodeURIComponent(q), {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (resp) {
|
||||
if (!resp || Number(resp.ok || 0) !== 1) {
|
||||
hideSuggestionBox();
|
||||
return;
|
||||
}
|
||||
renderSuggestionBox(Array.isArray(resp.items) ? resp.items : []);
|
||||
})
|
||||
.catch(function () {
|
||||
hideSuggestionBox();
|
||||
});
|
||||
}, 260);
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', function (event) {
|
||||
if (box.style.display !== 'block' || !suggestionItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
setActiveSuggestion(activeIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
setActiveSuggestion(activeIndex - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
if (activeIndex >= 0) {
|
||||
event.preventDefault();
|
||||
suggestionItems[activeIndex].click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
hideSuggestionBox();
|
||||
}
|
||||
});
|
||||
|
||||
addBtn.addEventListener('click', function () {
|
||||
var repoId = <?= (int) ($mstRepoKey ?? 0) ?>;
|
||||
if (!selected || !selected.name) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Select component', 'Search and select a gateway component first.');
|
||||
}
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var csrfValue = (window.jQuery && $('input[name="<?= csrf_token() ?>"]').first().val()) || '<?= csrf_hash() ?>';
|
||||
var fd = new FormData();
|
||||
fd.append('repo_id', String(repoId));
|
||||
fd.append('component_name', String(selected.name || ''));
|
||||
fd.append('short_name', String(selected.short_name || ''));
|
||||
fd.append('code', String(selected.code || ''));
|
||||
fd.append('unit', String(selected.unit || ''));
|
||||
fd.append('property', String(selected.property || ''));
|
||||
fd.append('specimen_system', String(selected.specimen_system || ''));
|
||||
fd.append('scale_type', String(selected.scale_type || ''));
|
||||
fd.append('<?= csrf_token() ?>', csrfValue);
|
||||
|
||||
addBtn.disabled = true;
|
||||
fetch(GATEWAY_COMPONENT_ADD_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
body: fd,
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (resp) {
|
||||
addBtn.disabled = false;
|
||||
if (!resp || Number(resp.ok || 0) !== 1) {
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Add failed', (resp && resp.error) ? resp.error : 'Unable to add gateway component.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof notify === 'function') {
|
||||
notify('success', 'Component attached', String(resp.message || 'Added successfully.'));
|
||||
}
|
||||
|
||||
load_form_div(RELOAD_COMPONENT_LIST_URL + repoId, 'test_div');
|
||||
})
|
||||
.catch(function () {
|
||||
addBtn.disabled = false;
|
||||
if (typeof notify === 'function') {
|
||||
notify('warning', 'Add failed', 'Network/server error while adding gateway component.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bindDocumentHandler('componentSuggestionOutsideClick', function (event) {
|
||||
if (!input.contains(event.target) && !box.contains(event.target)) {
|
||||
hideSuggestionBox();
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
|
|
|||
231
app/Views/PathLab_Report/loinc_panel.php
Normal file
231
app/Views/PathLab_Report/loinc_panel.php
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
/**
|
||||
* LOINC Mapping Admin Panel — PathLab_Report/loinc_panel.php
|
||||
*
|
||||
* Shows all lab panels (lab_repo) and their component tests (lab_tests)
|
||||
* with editable LOINC codes. Includes a "Sync from Bridge API" button.
|
||||
*
|
||||
* Variables injected by Setting\Template::loincPanel():
|
||||
* $panels — array of lab_repo rows (mstRepoKey, Title, loinc_code, loinc_synced_at)
|
||||
* $testsByRepo — [mstRepoKey => [lab_tests rows with loinc_* columns]]
|
||||
*/
|
||||
?>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
|
||||
<h4 class="mb-0"><i class="fas fa-flask me-2 text-primary"></i>LOINC Mapping — Pathology Tests</h4>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button id="btn-sync-loinc" class="btn btn-sm btn-success">
|
||||
<i class="fas fa-sync-alt me-1"></i>Sync from Bridge API
|
||||
</button>
|
||||
<a href="<?= site_url('Lab_Admin/report_list') ?>" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i>Back to Report List
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync output box -->
|
||||
<div id="sync-output-box" class="alert alert-info d-none mb-3" style="white-space:pre-wrap;font-family:monospace;font-size:0.82rem;max-height:220px;overflow-y:auto;"></div>
|
||||
|
||||
<p class="text-muted small mb-3">
|
||||
LOINC codes allow lab results to be published as structured FHIR <strong>Observations</strong> inside
|
||||
ABDM <em>DiagnosticReportRecord</em> bundles. Blank LOINC codes are sent as display-only text observations.
|
||||
Use <kbd>Sync from Bridge API</kbd> for bulk auto-matching or edit individual rows below.
|
||||
</p>
|
||||
|
||||
<?php if (empty($panels)): ?>
|
||||
<div class="alert alert-warning">No lab panels found. Add panels via <a href="<?= site_url('Lab_Admin/report_list') ?>">Report List</a>.</div>
|
||||
<?php else: ?>
|
||||
|
||||
<?php foreach ($panels as $panel):
|
||||
$panelId = (int) $panel['mstRepoKey'];
|
||||
$panelTitle = esc($panel['Title'] ?? '');
|
||||
$panelLoinc = esc($panel['loinc_code'] ?? '');
|
||||
$panelSync = esc($panel['loinc_synced_at'] ?? '');
|
||||
$tests = $testsByRepo[$panelId] ?? [];
|
||||
$hasPanelLoinc = trim($panel['loinc_code'] ?? '') !== '';
|
||||
?>
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-header py-2 d-flex align-items-center justify-content-between flex-wrap gap-2 bg-light">
|
||||
<div>
|
||||
<strong><?= $panelTitle ?></strong>
|
||||
<?php if ($hasPanelLoinc): ?>
|
||||
<span class="badge bg-success ms-2" title="LOINC Panel Code"><?= $panelLoinc ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary ms-2">No Panel LOINC</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($panelSync): ?>
|
||||
<small class="text-muted ms-2">synced: <?= $panelSync ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<input type="text" class="form-control form-control-sm loinc-panel-input"
|
||||
style="width:130px"
|
||||
placeholder="Panel LOINC"
|
||||
value="<?= $panelLoinc ?>"
|
||||
data-id="<?= $panelId ?>"
|
||||
title="LOINC code for panel (DiagnosticReport.code)">
|
||||
<button class="btn btn-xs btn-outline-primary btn-save-panel"
|
||||
data-id="<?= $panelId ?>">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (! empty($tests)): ?>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered mb-0 align-middle" style="font-size:0.82rem">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:22%">Test Name</th>
|
||||
<th style="width:8%">Unit</th>
|
||||
<th style="width:14%">LOINC Code</th>
|
||||
<th style="width:16%">Property</th>
|
||||
<th style="width:16%">System</th>
|
||||
<th style="width:12%">Scale</th>
|
||||
<th style="width:8%">Synced</th>
|
||||
<th style="width:4%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($tests as $t):
|
||||
$tid = (int) $t['mstTestKey'];
|
||||
$tname = esc($t['Test'] ?? '');
|
||||
$tunit = esc($t['Unit'] ?? '');
|
||||
$tloinc = esc($t['loinc_code'] ?? '');
|
||||
$tprop = esc($t['loinc_property'] ?? '');
|
||||
$tsys = esc($t['loinc_system'] ?? '');
|
||||
$tscale = esc($t['loinc_scale'] ?? '');
|
||||
$tsync = esc($t['loinc_synced_at'] ?? '');
|
||||
$hasLoinc = trim($t['loinc_code'] ?? '') !== '';
|
||||
?>
|
||||
<tr data-test-id="<?= $tid ?>">
|
||||
<td>
|
||||
<?= $tname ?>
|
||||
<?php if ($hasLoinc): ?><i class="fas fa-check-circle text-success ms-1" title="Has LOINC"></i><?php endif; ?>
|
||||
</td>
|
||||
<td class="text-muted"><?= $tunit ?></td>
|
||||
<td><input type="text" class="form-control form-control-sm loinc-test-code"
|
||||
value="<?= $tloinc ?>" placeholder="e.g. 718-7" style="width:100%"></td>
|
||||
<td><input type="text" class="form-control form-control-sm loinc-test-prop"
|
||||
value="<?= $tprop ?>" placeholder="MCnc" style="width:100%"></td>
|
||||
<td><input type="text" class="form-control form-control-sm loinc-test-sys"
|
||||
value="<?= $tsys ?>" placeholder="Bld" style="width:100%"></td>
|
||||
<td><input type="text" class="form-control form-control-sm loinc-test-scale"
|
||||
value="<?= $tscale ?>" placeholder="Qn" style="width:100%"></td>
|
||||
<td class="text-muted" style="font-size:0.75rem"><?= $tsync ? date('d-m-Y', strtotime($tsync)) : '-' ?></td>
|
||||
<td>
|
||||
<button class="btn btn-xs btn-outline-success btn-save-test" data-test-id="<?= $tid ?>" title="Save LOINC">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /container-fluid -->
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const SYNC_URL = '<?= site_url('Lab_Admin/loinc_sync') ?>';
|
||||
const UPDATE_URL = '<?= site_url('Lab_Admin/loinc_update') ?>';
|
||||
|
||||
// ── Sync button ──────────────────────────────────────────────────────────
|
||||
document.getElementById('btn-sync-loinc').addEventListener('click', function () {
|
||||
const btn = this;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Syncing…';
|
||||
|
||||
const outputBox = document.getElementById('sync-output-box');
|
||||
outputBox.classList.add('d-none');
|
||||
outputBox.textContent = '';
|
||||
|
||||
const fd = new FormData();
|
||||
fetch(SYNC_URL, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: fd })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
outputBox.classList.remove('d-none');
|
||||
outputBox.classList.toggle('alert-success', data.ok == 1);
|
||||
outputBox.classList.toggle('alert-danger', data.ok != 1);
|
||||
outputBox.classList.remove('alert-info');
|
||||
outputBox.textContent = data.output || (data.ok == 1 ? 'Sync complete.' : 'Sync failed.');
|
||||
if (data.ok == 1) {
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
outputBox.classList.remove('d-none', 'alert-info');
|
||||
outputBox.classList.add('alert-danger');
|
||||
outputBox.textContent = 'Request error: ' + err.message;
|
||||
})
|
||||
.finally(() => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-sync-alt me-1"></i>Sync from Bridge API';
|
||||
});
|
||||
});
|
||||
|
||||
// ── Save panel LOINC ─────────────────────────────────────────────────────
|
||||
document.querySelectorAll('.btn-save-panel').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const id = this.dataset.id;
|
||||
const input = document.querySelector('.loinc-panel-input[data-id="' + id + '"]');
|
||||
const code = input ? input.value.trim() : '';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('type', 'panel');
|
||||
fd.append('id', id);
|
||||
fd.append('loinc_code', code);
|
||||
|
||||
fetch(UPDATE_URL, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: fd })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
input.classList.toggle('is-valid', data.ok == 1);
|
||||
input.classList.toggle('is-invalid', data.ok != 1);
|
||||
setTimeout(() => input.classList.remove('is-valid', 'is-invalid'), 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Save individual test LOINC ───────────────────────────────────────────
|
||||
document.querySelectorAll('.btn-save-test').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const tid = this.dataset.testId;
|
||||
const row = document.querySelector('tr[data-test-id="' + tid + '"]');
|
||||
if (! row) return;
|
||||
|
||||
const code = row.querySelector('.loinc-test-code')?.value.trim() ?? '';
|
||||
const prop = row.querySelector('.loinc-test-prop')?.value.trim() ?? '';
|
||||
const sys = row.querySelector('.loinc-test-sys')?.value.trim() ?? '';
|
||||
const scale = row.querySelector('.loinc-test-scale')?.value.trim() ?? '';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('type', 'test');
|
||||
fd.append('id', tid);
|
||||
fd.append('loinc_code', code);
|
||||
fd.append('loinc_property', prop);
|
||||
fd.append('loinc_system', sys);
|
||||
fd.append('loinc_scale', scale);
|
||||
|
||||
fetch(UPDATE_URL, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: fd })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const icon = btn.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = data.ok == 1 ? 'fas fa-check text-success' : 'fas fa-times text-danger';
|
||||
setTimeout(() => { icon.className = 'fas fa-save'; }, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
|
@ -153,7 +153,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
|
|||
<div class="row g-3 admin-tiles">
|
||||
<?php if ($canPathology) { ?>
|
||||
<div class="col-6 col-md-2 col-lg-2">
|
||||
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('Lab_Admin/report_list') ?>','maindiv','Pathology Template');">
|
||||
<a class="admin-tile" href="javascript:load_form('<?= base_url('Lab_Admin/report_list') ?>','Pathology Template');">
|
||||
<span class="tile-icon"><i class="bi bi-heart-pulse"></i></span>
|
||||
<span>Pathology Template</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -178,6 +178,11 @@
|
|||
<input type="text" class="form-control form-control-sm" id="tb_abdm_consent_handle" placeholder="Auto-filled after consent request">
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label mb-1 small">Gateway Consent ID</label>
|
||||
<input type="text" class="form-control form-control-sm" id="tb_abdm_gateway_consent_id" placeholder="Auto-filled if returned by gateway" readonly>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-7">
|
||||
<input type="number" min="1" class="form-control form-control-sm" id="tb_abdm_claim_document_id" placeholder="Claim Document ID">
|
||||
|
|
@ -385,6 +390,7 @@
|
|||
var sandboxPurposeCodeInput = document.getElementById('tb_abdm_purpose_code');
|
||||
var sandboxConsentExpiresInput = document.getElementById('tb_abdm_consent_expires_at');
|
||||
var sandboxConsentHandleInput = document.getElementById('tb_abdm_consent_handle');
|
||||
var sandboxGatewayConsentIdInput = document.getElementById('tb_abdm_gateway_consent_id');
|
||||
var sandboxClaimDocumentInput = document.getElementById('tb_abdm_claim_document_id');
|
||||
var quickOpdInput = null;
|
||||
var quickSessionInput = null;
|
||||
|
|
@ -725,6 +731,7 @@
|
|||
sandboxQrPayloadInput.value = '';
|
||||
sandboxPurposeCodeInput.value = sandboxPurposeCodeInput.value || 'CAREMGT';
|
||||
sandboxConsentHandleInput.value = '';
|
||||
sandboxGatewayConsentIdInput.value = '';
|
||||
sandboxClaimDocumentInput.value = '';
|
||||
clearScanResolvePane();
|
||||
|
||||
|
|
@ -1028,7 +1035,14 @@
|
|||
if (res.consent_handle) {
|
||||
sandboxConsentHandleInput.value = (res.consent_handle || '').toString();
|
||||
}
|
||||
setSandboxStatus('Consent requested. Handle: ' + (res.consent_handle || '-'), 'success');
|
||||
if (res.gateway_consent_id) {
|
||||
sandboxGatewayConsentIdInput.value = (res.gateway_consent_id || '').toString();
|
||||
}
|
||||
var statusText = 'Consent requested. Handle: ' + (res.consent_handle || '-');
|
||||
if (res.gateway_consent_id) {
|
||||
statusText += ' | Consent ID: ' + res.gateway_consent_id;
|
||||
}
|
||||
setSandboxStatus(statusText, 'success');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,59 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
function executeInjectedScripts(containerId) {
|
||||
var container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some pages (dashboard charts) can be reloaded into the same canvas IDs.
|
||||
// Destroy existing chart instances before executing injected scripts.
|
||||
if (window.Chart) {
|
||||
var canvases = container.querySelectorAll('canvas');
|
||||
canvases.forEach(function(canvasEl) {
|
||||
try {
|
||||
if (typeof window.Chart.getChart === 'function') {
|
||||
var existingChart = window.Chart.getChart(canvasEl);
|
||||
if (existingChart) {
|
||||
existingChart.destroy();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Chart cleanup failed', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var scripts = container.querySelectorAll('script');
|
||||
if (!scripts || scripts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
scripts.forEach(function(oldScript) {
|
||||
var newScript = document.createElement('script');
|
||||
|
||||
for (var i = 0; i < oldScript.attributes.length; i++) {
|
||||
var attr = oldScript.attributes[i];
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
if (oldScript.src) {
|
||||
var existing = document.querySelector('script[src="' + oldScript.src + '"]');
|
||||
if (existing) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
newScript.text = oldScript.textContent || oldScript.innerText || '';
|
||||
}
|
||||
|
||||
document.head.appendChild(newScript);
|
||||
if (!oldScript.src) {
|
||||
document.head.removeChild(newScript);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleAuthError(jqXHR) {
|
||||
if (jqXHR.status === 401 || jqXHR.status === 403) {
|
||||
alert('You have been logged out. Please login again.');
|
||||
|
|
@ -187,21 +240,27 @@
|
|||
}
|
||||
}
|
||||
$.ajax({
|
||||
url: ourl,
|
||||
dataType: "html",
|
||||
async: true,
|
||||
timeout: REQUEST_TIMEOUT_MS,
|
||||
beforeSend: function() {
|
||||
$('#main').html('loading...');
|
||||
$("#wait").css("display", "block");
|
||||
}
|
||||
})
|
||||
url: ourl,
|
||||
dataType: "html",
|
||||
async: true,
|
||||
timeout: REQUEST_TIMEOUT_MS,
|
||||
beforeSend: function() {
|
||||
$('#main').html('loading...');
|
||||
$("#wait").css("display", "block");
|
||||
}
|
||||
})
|
||||
.done(function(html) {
|
||||
if (typeof delete_varible === 'function') {
|
||||
delete_varible();
|
||||
}
|
||||
$("#wait").css("display", "none");
|
||||
$("#main").html(html);
|
||||
var mainEl = document.getElementById('main');
|
||||
if (mainEl) {
|
||||
mainEl.innerHTML = html;
|
||||
} else {
|
||||
$("#main").html(html);
|
||||
}
|
||||
executeInjectedScripts('main');
|
||||
if (typeof initfunc === 'function') {
|
||||
initfunc();
|
||||
}
|
||||
|
|
@ -239,15 +298,20 @@
|
|||
window.load_form_div = function(ourl, xdiv, top_title = '') {
|
||||
if (!requireJquery()) return;
|
||||
$.ajax({
|
||||
url: ourl,
|
||||
dataType: "html",
|
||||
cache: false,
|
||||
url: ourl,
|
||||
dataType: "html",
|
||||
cache: false,
|
||||
async: true,
|
||||
timeout: REQUEST_TIMEOUT_MS
|
||||
})
|
||||
timeout: REQUEST_TIMEOUT_MS
|
||||
})
|
||||
.done(function(html) {
|
||||
$("#" + xdiv).html(html);
|
||||
var targetEl = document.getElementById(xdiv);
|
||||
if (targetEl) {
|
||||
targetEl.innerHTML = html;
|
||||
} else {
|
||||
$("#" + xdiv).html(html);
|
||||
}
|
||||
executeInjectedScripts(xdiv);
|
||||
if (typeof initfunc === 'function') {
|
||||
initfunc();
|
||||
}
|
||||
|
|
@ -340,6 +404,158 @@
|
|||
};
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(function() {
|
||||
if (!window.jQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
var SEARCH_URL = '<?= base_url('Lab_Admin/pathology_masters_search') ?>';
|
||||
var debounceTimer = null;
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function getModalEls() {
|
||||
return {
|
||||
input: document.getElementById('add_panel_name'),
|
||||
list: document.getElementById('add-panel-suggestions'),
|
||||
spinner: document.getElementById('add-panel-search-spinner'),
|
||||
meta: document.getElementById('add-panel-master-meta')
|
||||
};
|
||||
}
|
||||
|
||||
function hideList() {
|
||||
var els = getModalEls();
|
||||
if (!els.list) {
|
||||
return;
|
||||
}
|
||||
els.list.style.display = 'none';
|
||||
els.list.innerHTML = '';
|
||||
}
|
||||
|
||||
function renderList(items) {
|
||||
var els = getModalEls();
|
||||
if (!els.list) {
|
||||
return;
|
||||
}
|
||||
|
||||
els.list.innerHTML = '';
|
||||
if (!items || items.length === 0) {
|
||||
hideList();
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(function(item) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
|
||||
btn.innerHTML = '<span>' + escHtml(item.name || item.test_name || '') + '</span>'
|
||||
+ (item.loinc_code ? '<span class="badge bg-success ms-2">' + escHtml(item.loinc_code) + '</span>' : '<span class="badge bg-light text-muted border ms-2">No LOINC</span>');
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
if (els.input) {
|
||||
els.input.value = String(item.test_name || item.name || '').trim();
|
||||
}
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = 'Selected from ABDM API: ' + escHtml(item.test_name || item.name || '');
|
||||
}
|
||||
hideList();
|
||||
});
|
||||
|
||||
els.list.appendChild(btn);
|
||||
});
|
||||
|
||||
els.list.style.display = 'block';
|
||||
}
|
||||
|
||||
$(document)
|
||||
.off('input.abdmPanelSearch', '#add_panel_name')
|
||||
.on('input.abdmPanelSearch', '#add_panel_name', function() {
|
||||
clearTimeout(debounceTimer);
|
||||
|
||||
var q = String(this.value || '').trim();
|
||||
var els = getModalEls();
|
||||
|
||||
if (q.length < 2) {
|
||||
hideList();
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = 'Type at least 2 letters to live-search panel templates from ABDM Gateway API.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (els.spinner) {
|
||||
els.spinner.classList.remove('d-none');
|
||||
}
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = 'Searching panel templates from ABDM Gateway API...';
|
||||
}
|
||||
|
||||
debounceTimer = setTimeout(function() {
|
||||
$.ajax({
|
||||
url: SEARCH_URL,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
data: { q: q, source: 'api', panel_only: 1 },
|
||||
cache: false,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.done(function(payload) {
|
||||
if (els.spinner) {
|
||||
els.spinner.classList.add('d-none');
|
||||
}
|
||||
|
||||
if (payload && !Array.isArray(payload) && Number(payload.ok || 0) === 0) {
|
||||
hideList();
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = 'Search failed: ' + escHtml(payload.error || 'Gateway API search failed');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var items = Array.isArray(payload)
|
||||
? payload
|
||||
: (payload && Array.isArray(payload.items) ? payload.items : []);
|
||||
|
||||
renderList(items);
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = items.length > 0
|
||||
? ('Found ' + items.length + ' API matches')
|
||||
: ('No API matches for "' + escHtml(q) + '"');
|
||||
}
|
||||
})
|
||||
.fail(function(jqXHR, textStatus) {
|
||||
if (els.spinner) {
|
||||
els.spinner.classList.add('d-none');
|
||||
}
|
||||
hideList();
|
||||
if (els.meta) {
|
||||
els.meta.innerHTML = 'API search request failed (' + escHtml(textStatus || 'error') + ').';
|
||||
}
|
||||
});
|
||||
}, 280);
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('click.abdmPanelSearchDoc')
|
||||
.on('click.abdmPanelSearchDoc', function(e) {
|
||||
var input = document.getElementById('add_panel_name');
|
||||
var list = document.getElementById('add-panel-suggestions');
|
||||
if (!input || !list) {
|
||||
return;
|
||||
}
|
||||
if (!input.contains(e.target) && !list.contains(e.target)) {
|
||||
hideList();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user