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 CodeIgniter\Controller;
use App\Libraries\ValueSet;
use App\Models\OrderTest\OrderTestModel;
use App\Models\Patient\PatientModel;
use App\Models\PatVisit\PatVisitModel;
use App\Models\OrderTest\OrderTestModel;
use App\Models\Patient\PatientModel;
use App\Models\PatVisit\PatVisitModel;
class OrderTestController extends Controller {
use ResponseTrait;
@ -29,14 +29,22 @@ class OrderTestController extends Controller {
public function index() {
$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';
try {
if ($internalPID) {
$rows = $this->model->getOrdersByPatient($internalPID);
$rows = $this->model->getOrdersByPatient((int) $internalPID, $pvadtid);
} else {
$rows = $this->db->table('ordertest')
->where('DelDate', null)
$builder = $this->db->table('ordertest')
->where('DelDate', null);
if ($pvadtid !== null) {
$builder->where('PVADTID', $pvadtid);
}
$rows = $builder
->orderBy('TrnDate', 'DESC')
->get()
->getResultArray();
@ -179,35 +187,35 @@ class OrderTestController extends Controller {
$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)
return $this->respondCreated([
'status' => 'success',
'message' => 'Order created successfully',
'data' => $order
], 201);
// Rule engine triggers are fired at the test/result level (test_created, result_updated)
return $this->respondCreated([
'status' => 'success',
'message' => 'Order created successfully',
'data' => $order
], 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');
}
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'];
@ -220,9 +228,9 @@ class OrderTestController extends Controller {
$this->model->update($order['InternalOID'], $updateData);
}
$updatedOrder = $this->model->getOrder($OrderID);
$updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']);
$updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']);
$updatedOrder = $this->model->getOrder($OrderID);
$updatedOrder['Specimens'] = $this->getOrderSpecimens($updatedOrder['InternalOID']);
$updatedOrder['Tests'] = $this->getOrderTests($updatedOrder['InternalOID']);
return $this->respond([
'status' => 'success',

View File

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

File diff suppressed because it is too large Load Diff

View File

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