clqms-be/app/Controllers/OrderTestController.php

296 lines
11 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Controllers;
use App\Traits\ResponseTrait;
use CodeIgniter\Controller;
use App\Libraries\ValueSet;
use App\Models\OrderTest\OrderTestModel;
feat(valueset): refactor from ID-based to name-based lookups Complete overhaul of the valueset system to use human-readable names instead of numeric IDs for improved maintainability and API consistency. - PatientController: Renamed 'Gender' field to 'Sex' in validation rules - ValuesetController: Changed API endpoints from ID-based (/:num) to name-based (/:any) - TestsController: Refactored to use ValueSet library instead of direct valueset queries - Added ValueSet library (app/Libraries/ValueSet.php) with static lookup methods: - getOptions() - returns dropdown format [{value, label}] - getLabel(, ) - returns label for a value - transformLabels(, ) - batch transform records - get() and getRaw() for Lookups compatibility - Added ValueSetApiController for public valueset API endpoints - Added ValueSet refresh endpoint (POST /api/valueset/refresh) - Added DemoOrderController for testing order creation without auth - 2026-01-12-000001: Convert valueset references from VID to VValue - 2026-01-12-000002: Rename patient.Gender column to Sex - OrderTestController: Now uses OrderTestModel with proper model pattern - TestsController: Uses ValueSet library for all lookup operations - ValueSetController: Simplified to use name-based lookups - Updated all organization (account/site/workstation) dialogs and index views - Updated specimen container dialogs and index views - Updated tests_index.php with ValueSet integration - Updated patient dialog form and index views - Removed .factory/config.json and CLAUDE.md (replaced by AGENTS.md) - Consolidated lookups in Lookups.php (removed inline valueset constants) - Updated all test files to match new field names - 32 modified files, 17 new files, 2 deleted files - Net: +661 insertions, -1443 deletions (significant cleanup)
2026-01-12 16:53:41 +07:00
use App\Models\Patient\PatientModel;
use App\Models\PatVisit\PatVisitModel;
class OrderTestController extends Controller {
use ResponseTrait;
protected $db;
protected $model;
protected $patientModel;
protected $visitModel;
protected $rules;
public function __construct() {
$this->db = \Config\Database::connect();
$this->model = new OrderTestModel();
$this->patientModel = new PatientModel();
$this->visitModel = new PatVisitModel();
$this->rules = [
'InternalPID' => 'required|is_natural'
];
}
public function index() {
$internalPID = $this->request->getVar('InternalPID');
$includeDetails = $this->request->getVar('include') === 'details';
try {
if ($internalPID) {
$rows = $this->model->getOrdersByPatient($internalPID);
} else {
$rows = $this->db->table('ordertest')
->where('DelDate', null)
->orderBy('TrnDate', 'DESC')
->get()
->getResultArray();
}
$rows = ValueSet::transformLabels($rows, [
'Priority' => 'order_priority',
'OrderStatus' => 'order_status',
]);
if ($includeDetails && !empty($rows)) {
foreach ($rows as &$row) {
$row['Specimens'] = $this->getOrderSpecimens($row['InternalOID']);
$row['Tests'] = $this->getOrderTests($row['InternalOID']);
}
}
return $this->respond([
'status' => 'success',
'message' => 'Data fetched successfully',
'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function show($orderID = null) {
try {
$row = $this->model->getOrder($orderID);
if (empty($row)) {
return $this->respond([
'status' => 'success',
'message' => 'Data not found.',
'data' => null
], 200);
}
$row = ValueSet::transformLabels([$row], [
'Priority' => 'order_priority',
'OrderStatus' => 'order_status',
])[0];
// Include specimens and tests
$row['Specimens'] = $this->getOrderSpecimens($row['InternalOID']);
$row['Tests'] = $this->getOrderTests($row['InternalOID']);
return $this->respond([
'status' => 'success',
'message' => 'Data fetched successfully',
'data' => $row
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
private function getOrderSpecimens($internalOID) {
$specimens = $this->db->table('specimen s')
->select('s.*, cd.ConCode, cd.ConName')
->join('containerdef cd', 'cd.ConDefID = s.ConDefID', 'left')
->where('s.OrderID', $internalOID)
->where('s.EndDate IS NULL')
->get()
->getResultArray();
// Get status for each specimen
foreach ($specimens as &$specimen) {
$status = $this->db->table('specimenstatus')
->where('SID', $specimen['SID'])
->where('EndDate IS NULL')
->orderBy('CreateDate', 'DESC')
->get()
->getRowArray();
$specimen['Status'] = $status['SpcStatus'] ?? 'PENDING';
}
return $specimens;
}
private function getOrderTests($internalOID) {
$tests = $this->db->table('patres pr')
->select('pr.*, tds.TestSiteCode, tds.TestSiteName, tds.TestType, tds.SeqScr AS TestSeqScr, tds.SeqRpt AS TestSeqRpt, tds.DisciplineID, d.DisciplineCode, d.DisciplineName, d.SeqScr AS DisciplineSeqScr, d.SeqRpt AS DisciplineSeqRpt')
->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left')
->join('discipline d', 'd.DisciplineID = tds.DisciplineID', 'left')
->where('pr.OrderID', $internalOID)
->where('pr.DelDate IS NULL')
->orderBy('COALESCE(d.SeqScr, 999999) ASC')
->orderBy('COALESCE(d.SeqRpt, 999999) ASC')
->orderBy('COALESCE(tds.SeqScr, 999999) ASC')
->orderBy('COALESCE(tds.SeqRpt, 999999) ASC')
->orderBy('pr.ResultID ASC')
->get()
->getResultArray();
foreach ($tests as &$test) {
$discipline = [
'DisciplineID' => $test['DisciplineID'] ?? null,
'DisciplineCode' => $test['DisciplineCode'] ?? null,
'DisciplineName' => $test['DisciplineName'] ?? null,
'SeqScr' => $test['DisciplineSeqScr'] ?? null,
'SeqRpt' => $test['DisciplineSeqRpt'] ?? null,
];
$test['Discipline'] = $discipline;
$test['SeqScr'] = $test['TestSeqScr'] ?? null;
$test['SeqRpt'] = $test['TestSeqRpt'] ?? null;
$test['DisciplineID'] = $discipline['DisciplineID'];
unset($test['DisciplineCode'], $test['DisciplineName'], $test['DisciplineSeqScr'], $test['DisciplineSeqRpt'], $test['TestSeqScr'], $test['TestSeqRpt']);
}
unset($test);
return $tests;
}
public function create() {
$input = $this->request->getJSON(true);
if (!$this->validateData($input, $this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
if (!$this->patientModel->find($input['InternalPID'])) {
return $this->failValidationErrors(['InternalPID' => 'Patient not found']);
}
if (!empty($input['PatVisitID'])) {
$visit = $this->visitModel->find($input['PatVisitID']);
if (!$visit) {
return $this->failValidationErrors(['PatVisitID' => 'Visit not found']);
}
}
$orderID = $this->model->createOrder($input);
// Fetch complete order details
$order = $this->model->getOrder($orderID);
$order['Specimens'] = $this->getOrderSpecimens($order['InternalOID']);
$order['Tests'] = $this->getOrderTests($order['InternalOID']);
// Rule engine triggers are fired at the test/result level (test_created, result_updated)
2025-09-19 15:22:25 +07:00
return $this->respondCreated([
feat(valueset): refactor from ID-based to name-based lookups Complete overhaul of the valueset system to use human-readable names instead of numeric IDs for improved maintainability and API consistency. - PatientController: Renamed 'Gender' field to 'Sex' in validation rules - ValuesetController: Changed API endpoints from ID-based (/:num) to name-based (/:any) - TestsController: Refactored to use ValueSet library instead of direct valueset queries - Added ValueSet library (app/Libraries/ValueSet.php) with static lookup methods: - getOptions() - returns dropdown format [{value, label}] - getLabel(, ) - returns label for a value - transformLabels(, ) - batch transform records - get() and getRaw() for Lookups compatibility - Added ValueSetApiController for public valueset API endpoints - Added ValueSet refresh endpoint (POST /api/valueset/refresh) - Added DemoOrderController for testing order creation without auth - 2026-01-12-000001: Convert valueset references from VID to VValue - 2026-01-12-000002: Rename patient.Gender column to Sex - OrderTestController: Now uses OrderTestModel with proper model pattern - TestsController: Uses ValueSet library for all lookup operations - ValueSetController: Simplified to use name-based lookups - Updated all organization (account/site/workstation) dialogs and index views - Updated specimen container dialogs and index views - Updated tests_index.php with ValueSet integration - Updated patient dialog form and index views - Removed .factory/config.json and CLAUDE.md (replaced by AGENTS.md) - Consolidated lookups in Lookups.php (removed inline valueset constants) - Updated all test files to match new field names - 32 modified files, 17 new files, 2 deleted files - Net: +661 insertions, -1443 deletions (significant cleanup)
2026-01-12 16:53:41 +07:00
'status' => 'success',
'message' => 'Order created successfully',
'data' => $order
2025-09-19 15:22:25 +07:00
], 201);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($OrderID = null) {
$input = $this->request->getJSON(true);
if ($OrderID === null || $OrderID === '') {
return $this->failValidationErrors(['OrderID' => 'OrderID is required']);
}
if (isset($input['OrderID']) && (string) $input['OrderID'] !== (string) $OrderID) {
return $this->failValidationErrors(['OrderID' => 'OrderID in URL does not match body']);
}
try {
$input['OrderID'] = $OrderID;
$order = $this->model->getOrder($OrderID);
if (!$order) {
return $this->failNotFound('Order not found');
}
$updateData = [];
if (isset($input['Priority'])) $updateData['Priority'] = $input['Priority'];
if (isset($input['OrderStatus'])) $updateData['OrderStatus'] = $input['OrderStatus'];
if (isset($input['OrderingProvider'])) $updateData['OrderingProvider'] = $input['OrderingProvider'];
if (isset($input['DepartmentID'])) $updateData['DepartmentID'] = $input['DepartmentID'];
if (isset($input['WorkstationID'])) $updateData['WorkstationID'] = $input['WorkstationID'];
if (!empty($updateData)) {
$this->model->update($order['InternalOID'], $updateData);
}
$updatedOrder = $this->model->getOrder($OrderID);
$updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']);
$updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']);
return $this->respond([
'status' => 'success',
'message' => 'Order updated successfully',
'data' => $updatedOrder
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function delete() {
$input = $this->request->getJSON(true);
$orderID = $input['OrderID'] ?? null;
if (empty($orderID)) {
return $this->failValidationErrors(['OrderID' => 'OrderID is required']);
}
try {
$order = $this->model->getOrder($orderID);
if (!$order) {
return $this->failNotFound('Order not found');
}
$this->model->softDelete($orderID);
return $this->respondDeleted([
'status' => 'success',
'message' => 'Order deleted successfully'
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function updateStatus() {
$input = $this->request->getJSON(true);
if (empty($input['OrderID']) || empty($input['OrderStatus'])) {
return $this->failValidationErrors(['error' => 'OrderID and OrderStatus are required']);
}
$validStatuses = ['ORD', 'SCH', 'ANA', 'VER', 'REV', 'REP'];
if (!in_array($input['OrderStatus'], $validStatuses)) {
return $this->failValidationErrors(['OrderStatus' => 'Invalid status. Valid: ' . implode(', ', $validStatuses)]);
}
try {
$order = $this->model->getOrder($input['OrderID']);
if (!$order) {
return $this->failNotFound('Order not found');
}
$this->model->updateStatus($input['OrderID'], $input['OrderStatus']);
$updatedOrder = $this->model->getOrder($input['OrderID']);
$updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']);
$updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']);
return $this->respond([
'status' => 'success',
'message' => 'Order status updated successfully',
'data' => $updatedOrder
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}