feat(ordertest): add PVADTID filter support and sync order API docs

- Add PVADTID query param handling in OrderTestController::index() (supports both PVADTID and pvadtid, with numeric casting).

- Extend OrderTestModel::getOrdersByPatient() with optional PVADTID filtering.

- Apply PVADTID filtering to general order listing queries.

- Update public/paths/orders.yaml to document PVADTID on GET /api/ordertest.

- Regenerate public/api-docs.bundled.yaml to keep OpenAPI bundle in sync.
This commit is contained in:
mahdahar 2026-04-20 13:48:18 +07:00
parent 0ec13e404a
commit 4aad7d331a
4 changed files with 8986 additions and 8962 deletions

View File

@ -4,9 +4,9 @@ namespace App\Controllers;
use App\Traits\ResponseTrait; use App\Traits\ResponseTrait;
use CodeIgniter\Controller; use CodeIgniter\Controller;
use App\Libraries\ValueSet; use App\Libraries\ValueSet;
use App\Models\OrderTest\OrderTestModel; use App\Models\OrderTest\OrderTestModel;
use App\Models\Patient\PatientModel; use App\Models\Patient\PatientModel;
use App\Models\PatVisit\PatVisitModel; use App\Models\PatVisit\PatVisitModel;
class OrderTestController extends Controller { class OrderTestController extends Controller {
use ResponseTrait; use ResponseTrait;
@ -29,14 +29,22 @@ class OrderTestController extends Controller {
public function index() { public function index() {
$internalPID = $this->request->getVar('InternalPID'); $internalPID = $this->request->getVar('InternalPID');
$pvadtid = $this->request->getVar('PVADTID') ?? $this->request->getVar('pvadtid');
$pvadtid = is_numeric($pvadtid) ? (int) $pvadtid : null;
$includeDetails = $this->request->getVar('include') === 'details'; $includeDetails = $this->request->getVar('include') === 'details';
try { try {
if ($internalPID) { if ($internalPID) {
$rows = $this->model->getOrdersByPatient($internalPID); $rows = $this->model->getOrdersByPatient((int) $internalPID, $pvadtid);
} else { } else {
$rows = $this->db->table('ordertest') $builder = $this->db->table('ordertest')
->where('DelDate', null) ->where('DelDate', null);
if ($pvadtid !== null) {
$builder->where('PVADTID', $pvadtid);
}
$rows = $builder
->orderBy('TrnDate', 'DESC') ->orderBy('TrnDate', 'DESC')
->get() ->get()
->getResultArray(); ->getResultArray();
@ -179,35 +187,35 @@ class OrderTestController extends Controller {
$order['Specimens'] = $this->getOrderSpecimens($order['InternalOID']); $order['Specimens'] = $this->getOrderSpecimens($order['InternalOID']);
$order['Tests'] = $this->getOrderTests($order['InternalOID']); $order['Tests'] = $this->getOrderTests($order['InternalOID']);
// Rule engine triggers are fired at the test/result level (test_created, result_updated) // Rule engine triggers are fired at the test/result level (test_created, result_updated)
return $this->respondCreated([ return $this->respondCreated([
'status' => 'success', 'status' => 'success',
'message' => 'Order created successfully', 'message' => 'Order created successfully',
'data' => $order 'data' => $order
], 201); ], 201);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage()); return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
} }
public function update($OrderID = null) { public function update($OrderID = null) {
$input = $this->request->getJSON(true); $input = $this->request->getJSON(true);
if ($OrderID === null || $OrderID === '') { if ($OrderID === null || $OrderID === '') {
return $this->failValidationErrors(['OrderID' => 'OrderID is required']); return $this->failValidationErrors(['OrderID' => 'OrderID is required']);
} }
if (isset($input['OrderID']) && (string) $input['OrderID'] !== (string) $OrderID) { if (isset($input['OrderID']) && (string) $input['OrderID'] !== (string) $OrderID) {
return $this->failValidationErrors(['OrderID' => 'OrderID in URL does not match body']); return $this->failValidationErrors(['OrderID' => 'OrderID in URL does not match body']);
} }
try { try {
$input['OrderID'] = $OrderID; $input['OrderID'] = $OrderID;
$order = $this->model->getOrder($OrderID); $order = $this->model->getOrder($OrderID);
if (!$order) { if (!$order) {
return $this->failNotFound('Order not found'); return $this->failNotFound('Order not found');
} }
$updateData = []; $updateData = [];
if (isset($input['Priority'])) $updateData['Priority'] = $input['Priority']; if (isset($input['Priority'])) $updateData['Priority'] = $input['Priority'];
@ -220,9 +228,9 @@ class OrderTestController extends Controller {
$this->model->update($order['InternalOID'], $updateData); $this->model->update($order['InternalOID'], $updateData);
} }
$updatedOrder = $this->model->getOrder($OrderID); $updatedOrder = $this->model->getOrder($OrderID);
$updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']); $updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']);
$updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']); $updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']);
return $this->respond([ return $this->respond([
'status' => 'success', 'status' => 'success',

View File

@ -161,25 +161,25 @@ class OrderTestModel extends BaseModel {
} }
} }
// Insert unique tests into patres with specimen links // Insert unique tests into patres with specimen links
if (!empty($testToOrder)) { if (!empty($testToOrder)) {
$resModel = new \App\Models\PatResultModel(); $resModel = new \App\Models\PatResultModel();
$patientModel = new \App\Models\Patient\PatientModel(); $patientModel = new \App\Models\Patient\PatientModel();
$patient = $patientModel->find((int) $data['InternalPID']); $patient = $patientModel->find((int) $data['InternalPID']);
$age = null; $age = null;
if (is_array($patient) && !empty($patient['Birthdate'])) { if (is_array($patient) && !empty($patient['Birthdate'])) {
try { try {
$birthdate = new \DateTime((string) $patient['Birthdate']); $birthdate = new \DateTime((string) $patient['Birthdate']);
$age = (new \DateTime())->diff($birthdate)->y; $age = (new \DateTime())->diff($birthdate)->y;
} catch (\Throwable $e) { } catch (\Throwable $e) {
$age = null; $age = null;
} }
} }
$ruleEngine = new \App\Services\RuleEngineService(); $ruleEngine = new \App\Services\RuleEngineService();
foreach ($testToOrder as $tid => $tinfo) { foreach ($testToOrder as $tid => $tinfo) {
$specimenInfo = $specimenConDefMap[$tid] ?? null; $specimenInfo = $specimenConDefMap[$tid] ?? null;
$patResData = [ $patResData = [
'OrderID' => $internalOID, 'OrderID' => $internalOID,
@ -191,32 +191,32 @@ class OrderTestModel extends BaseModel {
'CreateDate' => date('Y-m-d H:i:s') 'CreateDate' => date('Y-m-d H:i:s')
]; ];
if ($specimenInfo) { if ($specimenInfo) {
$patResData['InternalSID'] = $specimenInfo['InternalSID']; $patResData['InternalSID'] = $specimenInfo['InternalSID'];
} }
$resModel->insert($patResData); $resModel->insert($patResData);
// Fire test_created rules after the test row is persisted // Fire test_created rules after the test row is persisted
try { try {
$ruleEngine->run('test_created', [ $ruleEngine->run('test_created', [
'order' => [ 'order' => [
'InternalOID' => $internalOID, 'InternalOID' => $internalOID,
'Priority' => $orderData['Priority'] ?? 'R', 'Priority' => $orderData['Priority'] ?? 'R',
'TestSiteID' => $tid, 'TestSiteID' => $tid,
], ],
'patient' => [ 'patient' => [
'Sex' => is_array($patient) ? ($patient['Sex'] ?? null) : null, 'Sex' => is_array($patient) ? ($patient['Sex'] ?? null) : null,
], ],
'age' => $age, 'age' => $age,
'testSiteID' => $tid, 'testSiteID' => $tid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
log_message('error', 'OrderTestModel::createOrder rule engine error: ' . $e->getMessage()); log_message('error', 'OrderTestModel::createOrder rule engine error: ' . $e->getMessage());
} }
} }
} }
} }
$this->db->transComplete(); $this->db->transComplete();
@ -299,10 +299,16 @@ class OrderTestModel extends BaseModel {
->getRowArray(); ->getRowArray();
} }
public function getOrdersByPatient(int $internalPID): array { public function getOrdersByPatient(int $internalPID, ?int $pvadtid = null): array {
return $this->select('*') $builder = $this->select('*')
->where('InternalPID', $internalPID) ->where('InternalPID', $internalPID)
->where('DelDate', null) ->where('DelDate', null);
if ($pvadtid !== null) {
$builder->where('PVADTID', $pvadtid);
}
return $builder
->orderBy('TrnDate', 'DESC') ->orderBy('TrnDate', 'DESC')
->get() ->get()
->getResultArray(); ->getResultArray();

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/api/ordertest: /api/ordertest:
get: get:
tags: [Order] tags: [Order]
summary: List orders summary: List orders
security: security:
- bearerAuth: [] - bearerAuth: []
@ -18,6 +18,11 @@
schema: schema:
type: integer type: integer
description: Filter by internal patient ID description: Filter by internal patient ID
- name: PVADTID
in: query
schema:
type: integer
description: Filter by patient visit ADT ID
- name: OrderStatus - name: OrderStatus
in: query in: query
schema: schema:
@ -47,8 +52,8 @@
items: items:
$ref: '../components/schemas/orders.yaml#/OrderTestList' $ref: '../components/schemas/orders.yaml#/OrderTestList'
post: post:
tags: [Order] tags: [Order]
summary: Create order with specimens and tests summary: Create order with specimens and tests
description: Creates an order with associated specimens and patres records. Tests are grouped by container type to minimize specimen creation. description: Creates an order with associated specimens and patres records. Tests are grouped by container type to minimize specimen creation.
security: security:
@ -124,8 +129,8 @@
description: Server error description: Server error
delete: delete:
tags: [Order] tags: [Order]
summary: Delete order summary: Delete order
security: security:
- bearerAuth: [] - bearerAuth: []
@ -144,9 +149,9 @@
'200': '200':
description: Order deleted description: Order deleted
/api/ordertest/status: /api/ordertest/status:
post: post:
tags: [Order] tags: [Order]
summary: Update order status summary: Update order status
security: security:
- bearerAuth: [] - bearerAuth: []
@ -187,9 +192,9 @@
data: data:
$ref: '../components/schemas/orders.yaml#/OrderTest' $ref: '../components/schemas/orders.yaml#/OrderTest'
/api/ordertest/{id}: /api/ordertest/{id}:
get: get:
tags: [Order] tags: [Order]
summary: Get order by ID summary: Get order by ID
description: Returns order details with associated specimens and tests description: Returns order details with associated specimens and tests
security: security:
@ -201,63 +206,63 @@
schema: schema:
type: string type: string
description: Order ID (e.g., 0025030300001) description: Order ID (e.g., 0025030300001)
responses: responses:
'200': '200':
description: Order details with specimens and tests description: Order details with specimens and tests
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
status: status:
type: string type: string
message: message:
type: string type: string
data: data:
$ref: '../components/schemas/orders.yaml#/OrderTest' $ref: '../components/schemas/orders.yaml#/OrderTest'
patch: patch:
tags: [Order] tags: [Order]
summary: Update order summary: Update order
security: security:
- bearerAuth: [] - bearerAuth: []
parameters: parameters:
- name: id - name: id
in: path in: path
required: true required: true
schema: schema:
type: string type: string
description: Order ID description: Order ID
requestBody: requestBody:
required: true required: true
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
Priority: Priority:
type: string type: string
enum: [R, S, U] enum: [R, S, U]
OrderStatus: OrderStatus:
type: string type: string
enum: [ORD, SCH, ANA, VER, REV, REP] enum: [ORD, SCH, ANA, VER, REV, REP]
OrderingProvider: OrderingProvider:
type: string type: string
DepartmentID: DepartmentID:
type: integer type: integer
WorkstationID: WorkstationID:
type: integer type: integer
responses: responses:
'200': '200':
description: Order updated description: Order updated
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
status: status:
type: string type: string
message: message:
type: string type: string
data: data:
$ref: '../components/schemas/orders.yaml#/OrderTest' $ref: '../components/schemas/orders.yaml#/OrderTest'