feat: implement comprehensive order management with specimens and tests
Major updates to order system: - Add specimen and test data to order responses (index, show, create, update, status update) - Implement getOrderSpecimens() and getOrderTests() private methods in OrderTestController - Support 'include=details' query parameter for expanded order data - Update OrderTestModel with enhanced query capabilities and transaction handling API documentation: - Update OpenAPI specs for orders, patient-visits, and tests - Add new schemas for order specimens and tests - Regenerate bundled API documentation Database: - Add Requestable field to TestDefSite migration - Create OrderSeeder and ClearOrderDataSeeder for test data - Update DBSeeder to include order seeding Patient visits: - Add filtering by PatientID, PatientName, and date ranges - Include LastLocation in visit queries Testing: - Add OrderCreateTest with comprehensive test coverage Documentation: - Update AGENTS.md with improved controller structure examples
This commit is contained in:
parent
e9c7beeb2b
commit
42006e1af9
18
AGENTS.md
18
AGENTS.md
@ -70,6 +70,8 @@ use Firebase\JWT\JWT;
|
||||
```
|
||||
|
||||
### Controller Structure
|
||||
Controllers handle HTTP requests and delegate business logic to Models. They should NOT contain database queries.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
@ -82,16 +84,24 @@ class ExampleController extends Controller
|
||||
use ResponseTrait;
|
||||
|
||||
protected $model;
|
||||
protected $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = \Config\Database::connect();
|
||||
$this->model = new \App\Models\ExampleModel();
|
||||
}
|
||||
|
||||
public function index() { /* ... */ }
|
||||
public function create() { /* ... */ }
|
||||
public function index()
|
||||
{
|
||||
$data = $this->model->findAll();
|
||||
return $this->respond(['status' => 'success', 'data' => $data], 200);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$data = $this->request->getJSON(true);
|
||||
$result = $this->model->createWithRelations($data);
|
||||
return $this->respond(['status' => 'success', 'data' => $result], 201);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -4,9 +4,9 @@ namespace App\Controllers;
|
||||
use App\Traits\ResponseTrait;
|
||||
use CodeIgniter\Controller;
|
||||
use App\Libraries\ValueSet;
|
||||
use App\Models\OrderTestModel;
|
||||
use App\Models\OrderTest\OrderTestModel;
|
||||
use App\Models\Patient\PatientModel;
|
||||
use App\Models\Patient\PatVisitModel;
|
||||
use App\Models\PatVisit\PatVisitModel;
|
||||
|
||||
class OrderTestController extends Controller {
|
||||
use ResponseTrait;
|
||||
@ -29,6 +29,7 @@ class OrderTestController extends Controller {
|
||||
|
||||
public function index() {
|
||||
$internalPID = $this->request->getVar('InternalPID');
|
||||
$includeDetails = $this->request->getVar('include') === 'details';
|
||||
|
||||
try {
|
||||
if ($internalPID) {
|
||||
@ -46,6 +47,13 @@ class OrderTestController extends Controller {
|
||||
'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',
|
||||
@ -72,6 +80,10 @@ class OrderTestController extends Controller {
|
||||
'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',
|
||||
@ -82,6 +94,39 @@ class OrderTestController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return $this->db->table('patres pr')
|
||||
->select('pr.*, tds.TestSiteCode, tds.TestSiteName')
|
||||
->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left')
|
||||
->where('pr.OrderID', $internalOID)
|
||||
->where('pr.DelDate IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$input = $this->request->getJSON(true);
|
||||
|
||||
@ -103,10 +148,15 @@ class OrderTestController extends Controller {
|
||||
|
||||
$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']);
|
||||
|
||||
return $this->respondCreated([
|
||||
'status' => 'success',
|
||||
'message' => 'Order created successfully',
|
||||
'data' => ['OrderID' => $orderID]
|
||||
'data' => $order
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||
@ -137,10 +187,14 @@ class OrderTestController extends Controller {
|
||||
$this->model->update($order['InternalOID'], $updateData);
|
||||
}
|
||||
|
||||
$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 updated successfully',
|
||||
'data' => $this->model->getOrder($input['OrderID'])
|
||||
'data' => $updatedOrder
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||
@ -192,10 +246,14 @@ class OrderTestController extends Controller {
|
||||
|
||||
$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' => $this->model->getOrder($input['OrderID'])
|
||||
'data' => $updatedOrder
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->failServerError('Something went wrong: ' . $e->getMessage());
|
||||
|
||||
@ -20,15 +20,43 @@ class PatVisitController extends BaseController {
|
||||
try {
|
||||
$InternalPID = $this->request->getVar('InternalPID');
|
||||
$PVID = $this->request->getVar('PVID');
|
||||
$PatientID = $this->request->getVar('PatientID');
|
||||
$PatientName = $this->request->getVar('PatientName');
|
||||
$CreateDateFrom = $this->request->getVar('CreateDateFrom');
|
||||
$CreateDateTo = $this->request->getVar('CreateDateTo');
|
||||
|
||||
$builder = $this->model->select('patvisit.*, patient.NameFirst, patient.NameLast, patient.PatientID')
|
||||
->join('patient', 'patient.InternalPID=patvisit.InternalPID', 'left');
|
||||
$builder = $this->model->select('patvisit.*, patient.NameFirst, patient.NameLast, patient.PatientID, location.LocFull as LastLocation')
|
||||
->join('patient', 'patient.InternalPID=patvisit.InternalPID', 'left')
|
||||
->join('(SELECT a1.*
|
||||
FROM patvisitadt a1
|
||||
INNER JOIN (
|
||||
SELECT InternalPVID, MAX(PVADTID) AS MaxID
|
||||
FROM patvisitadt
|
||||
GROUP BY InternalPVID
|
||||
) a2 ON a1.InternalPVID = a2.InternalPVID AND a1.PVADTID = a2.MaxID
|
||||
) AS latest_patvisitadt', 'latest_patvisitadt.InternalPVID = patvisit.InternalPVID', 'left')
|
||||
->join('location', 'location.LocationID = latest_patvisitadt.LocationID', 'left');
|
||||
|
||||
if ($InternalPID) {
|
||||
$builder->where('patvisit.InternalPID', $InternalPID);
|
||||
}
|
||||
if ($PVID) {
|
||||
$builder->where('patvisit.PVID', $PVID);
|
||||
$builder->like('patvisit.PVID', $PVID, 'both');
|
||||
}
|
||||
if ($PatientID) {
|
||||
$builder->like('patient.PatientID', $PatientID, 'both');
|
||||
}
|
||||
if ($PatientName) {
|
||||
$builder->groupStart()
|
||||
->like('patient.NameFirst', $PatientName, 'both')
|
||||
->orLike('patient.NameLast', $PatientName, 'both')
|
||||
->groupEnd();
|
||||
}
|
||||
if ($CreateDateFrom) {
|
||||
$builder->where('patvisit.CreateDate >=', $CreateDateFrom);
|
||||
}
|
||||
if ($CreateDateTo) {
|
||||
$builder->where('patvisit.CreateDate <=', $CreateDateTo);
|
||||
}
|
||||
|
||||
$rows = $builder->orderBy('patvisit.CreateDate', 'DESC')->findAll();
|
||||
|
||||
@ -364,7 +364,18 @@ class TestsController extends BaseController
|
||||
$this->modelRefTxt->where('TestSiteID', $id)->set('EndDate', $now)->update();
|
||||
}
|
||||
|
||||
$this->modelMap->disableByTestSiteID($id);
|
||||
// Disable testmap by test code
|
||||
$testSiteCode = $existing['TestSiteCode'] ?? null;
|
||||
if ($testSiteCode) {
|
||||
$existingMaps = $this->modelMap->getMappingsByTestCode($testSiteCode);
|
||||
foreach ($existingMaps as $existingMap) {
|
||||
$this->modelMapDetail->where('TestMapID', $existingMap['TestMapID'])
|
||||
->where('EndDate', null)
|
||||
->set('EndDate', $now)
|
||||
->update();
|
||||
$this->modelMap->update($existingMap['TestMapID'], ['EndDate' => $now]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
@ -387,10 +398,12 @@ class TestsController extends BaseController
|
||||
private function handleDetails($testSiteID, $input, $action)
|
||||
{
|
||||
$testTypeID = $input['TestType'] ?? null;
|
||||
$testSiteCode = null;
|
||||
|
||||
if (!$testTypeID && $action === 'update') {
|
||||
$existing = $this->model->find($testSiteID);
|
||||
$testTypeID = $existing['TestType'] ?? null;
|
||||
$testSiteCode = $existing['TestSiteCode'] ?? null;
|
||||
}
|
||||
|
||||
if (!$testTypeID) {
|
||||
@ -416,7 +429,7 @@ class TestsController extends BaseController
|
||||
|
||||
case 'TITLE':
|
||||
if (isset($input['testmap']) && is_array($input['testmap'])) {
|
||||
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
||||
$this->saveTestMap($testSiteID, $testSiteCode, $input['testmap'], $action);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -443,7 +456,7 @@ class TestsController extends BaseController
|
||||
}
|
||||
|
||||
if ((TestValidationService::isTechnicalTest($typeCode) || TestValidationService::isCalc($typeCode)) && isset($input['testmap']) && is_array($input['testmap'])) {
|
||||
$this->saveTestMap($testSiteID, $input['testmap'], $action);
|
||||
$this->saveTestMap($testSiteID, $testSiteCode, $input['testmap'], $action);
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,12 +585,11 @@ class TestsController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
private function saveTestMap($testSiteID, $mappings, $action)
|
||||
private function saveTestMap($testSiteID, $testSiteCode, $mappings, $action)
|
||||
{
|
||||
if ($action === 'update') {
|
||||
$existingMaps = $this->modelMap->where('TestSiteID', $testSiteID)
|
||||
->where('EndDate', null)
|
||||
->findAll();
|
||||
if ($action === 'update' && $testSiteCode) {
|
||||
// Find existing mappings by test code through testmapdetail
|
||||
$existingMaps = $this->modelMap->getMappingsByTestCode($testSiteCode);
|
||||
|
||||
foreach ($existingMaps as $existingMap) {
|
||||
$this->modelMapDetail->where('TestMapID', $existingMap['TestMapID'])
|
||||
@ -586,13 +598,15 @@ class TestsController extends BaseController
|
||||
->update();
|
||||
}
|
||||
|
||||
$this->modelMap->disableByTestSiteID($testSiteID);
|
||||
// Soft delete the testmap headers
|
||||
foreach ($existingMaps as $existingMap) {
|
||||
$this->modelMap->update($existingMap['TestMapID'], ['EndDate' => date('Y-m-d H:i:s')]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($mappings)) {
|
||||
foreach ($mappings as $map) {
|
||||
$mapData = [
|
||||
'TestSiteID' => $testSiteID,
|
||||
'HostType' => $map['HostType'] ?? null,
|
||||
'HostID' => $map['HostID'] ?? null,
|
||||
'ClientType' => $map['ClientType'] ?? null,
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddRequestableToTestDefSite extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$fields = [
|
||||
'Requestable' => [
|
||||
'type' => 'TINYINT',
|
||||
'constraint' => 1,
|
||||
'null' => true,
|
||||
'default' => 1,
|
||||
'comment' => 'Flag indicating if test can be requested (1=yes, 0=no)'
|
||||
]
|
||||
];
|
||||
|
||||
$this->forge->addColumn('testdefsite', $fields);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropColumn('testdefsite', 'Requestable');
|
||||
}
|
||||
}
|
||||
40
app/Database/Seeds/ClearOrderDataSeeder.php
Normal file
40
app/Database/Seeds/ClearOrderDataSeeder.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class ClearOrderDataSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
echo "Clearing order-related tables...\n";
|
||||
|
||||
// Disable foreign key checks temporarily
|
||||
$this->db->query('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
// Clear tables in reverse dependency order
|
||||
$this->db->table('ordercom')->truncate();
|
||||
echo " - ordercom truncated\n";
|
||||
|
||||
$this->db->table('orderstatus')->truncate();
|
||||
echo " - orderstatus truncated\n";
|
||||
|
||||
$this->db->table('patres')->truncate();
|
||||
echo " - patres truncated\n";
|
||||
|
||||
$this->db->table('specimenstatus')->truncate();
|
||||
echo " - specimenstatus truncated\n";
|
||||
|
||||
$this->db->table('specimen')->truncate();
|
||||
echo " - specimen truncated\n";
|
||||
|
||||
$this->db->table('ordertest')->truncate();
|
||||
echo " - ordertest truncated\n";
|
||||
|
||||
// Re-enable foreign key checks
|
||||
$this->db->query('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
echo "\nAll order-related tables cleared successfully!\n";
|
||||
}
|
||||
}
|
||||
@ -17,5 +17,6 @@ class DBSeeder extends Seeder
|
||||
$this->call('TestSeeder');
|
||||
$this->call('PatientSeeder');
|
||||
$this->call('DummySeeder');
|
||||
$this->call('OrderSeeder');
|
||||
}
|
||||
}
|
||||
537
app/Database/Seeds/OrderSeeder.php
Normal file
537
app/Database/Seeds/OrderSeeder.php
Normal file
@ -0,0 +1,537 @@
|
||||
<?php
|
||||
|
||||
namespace App\Database\Seeds;
|
||||
|
||||
use CodeIgniter\Database\Seeder;
|
||||
|
||||
class OrderSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$yesterday = date('Y-m-d H:i:s', strtotime('-1 day'));
|
||||
$twoDaysAgo = date('Y-m-d H:i:s', strtotime('-2 days'));
|
||||
|
||||
echo "Creating dummy orders with specimens and results...\n";
|
||||
|
||||
// Get existing test IDs from testdefsite
|
||||
$testIDs = $this->getTestIDs();
|
||||
if (empty($testIDs)) {
|
||||
echo "Error: No test definitions found. Please run TestSeeder first.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container definitions
|
||||
$containers = $this->db->table('containerdef')->get()->getResultArray();
|
||||
if (empty($containers)) {
|
||||
echo "Error: No container definitions found. Please run SpecimenSeeder first.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// ORDER 1: Patient 1 - Complete Blood Count (CBC)
|
||||
// ========================================
|
||||
$orderID1 = '001' . date('ymd', strtotime('-2 days')) . '00001';
|
||||
$internalOID1 = $this->createOrder([
|
||||
'OrderID' => $orderID1,
|
||||
'PlacerID' => 'PLC001',
|
||||
'InternalPID' => 1,
|
||||
'SiteID' => '1',
|
||||
'PVADTID' => 2,
|
||||
'ReqApp' => 'HIS',
|
||||
'Priority' => 'R',
|
||||
'TrnDate' => $twoDaysAgo,
|
||||
'EffDate' => $twoDaysAgo,
|
||||
'CreateDate' => $twoDaysAgo
|
||||
]);
|
||||
|
||||
echo "Created Order 1: {$orderID1} (InternalOID: {$internalOID1})\n";
|
||||
|
||||
// Create specimen for CBC (EDTA tube - ConDefID = 9)
|
||||
$specimenID1 = $orderID1 . '-S01';
|
||||
$internalSID1 = $this->createSpecimen([
|
||||
'SID' => $specimenID1,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID1,
|
||||
'ConDefID' => 9,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $twoDaysAgo
|
||||
]);
|
||||
|
||||
// Create specimen status progression
|
||||
$this->createSpecimenStatus($specimenID1, $internalOID1, 'PENDING', $twoDaysAgo);
|
||||
$this->createSpecimenStatus($specimenID1, $internalOID1, 'COLLECTED', date('Y-m-d H:i:s', strtotime('-2 days +30 minutes')));
|
||||
$this->createSpecimenStatus($specimenID1, $internalOID1, 'RECEIVED', date('Y-m-d H:i:s', strtotime('-2 days +1 hour')));
|
||||
$this->createSpecimenStatus($specimenID1, $internalOID1, 'PROCESSING', date('Y-m-d H:i:s', strtotime('-2 days +2 hours')));
|
||||
$this->createSpecimenStatus($specimenID1, $internalOID1, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +8 hours')));
|
||||
|
||||
echo " Created Specimen: {$specimenID1} (EDTA)\n";
|
||||
|
||||
// Create CBC test results
|
||||
$cbcTests = [
|
||||
['code' => 'HB', 'result' => '14.2'],
|
||||
['code' => 'HCT', 'result' => '42.5'],
|
||||
['code' => 'RBC', 'result' => '4.85'],
|
||||
['code' => 'WBC', 'result' => '7.2'],
|
||||
['code' => 'PLT', 'result' => '285'],
|
||||
['code' => 'MCV', 'result' => '87.6'],
|
||||
['code' => 'MCH', 'result' => '29.3'],
|
||||
['code' => 'MCHC', 'result' => '33.4'],
|
||||
];
|
||||
|
||||
foreach ($cbcTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID1,
|
||||
'InternalSID' => $internalSID1,
|
||||
'SID' => $specimenID1,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => $test['result'],
|
||||
'SampleType' => 'Whole Blood',
|
||||
'ResultDateTime' => date('Y-m-d H:i:s', strtotime('-1 day +8 hours')),
|
||||
'CreateDate' => date('Y-m-d H:i:s', strtotime('-1 day +8 hours'))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo " Created " . count($cbcTests) . " CBC results\n";
|
||||
|
||||
// Create order status
|
||||
$this->createOrderStatus($internalOID1, 'ORDERED', $twoDaysAgo);
|
||||
$this->createOrderStatus($internalOID1, 'SPECIMEN_COLLECTED', date('Y-m-d H:i:s', strtotime('-2 days +30 minutes')));
|
||||
$this->createOrderStatus($internalOID1, 'IN_LAB', date('Y-m-d H:i:s', strtotime('-2 days +2 hours')));
|
||||
$this->createOrderStatus($internalOID1, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +8 hours')));
|
||||
$this->createOrderStatus($internalOID1, 'REPORTED', date('Y-m-d H:i:s', strtotime('-1 day +9 hours')));
|
||||
|
||||
// Create order comment
|
||||
$this->createOrderComment($internalOID1, 'Routine CBC ordered for annual checkup', $twoDaysAgo);
|
||||
|
||||
// ========================================
|
||||
// ORDER 2: Patient 2 - Lipid Profile + Liver Function
|
||||
// ========================================
|
||||
$orderID2 = '001' . date('ymd', strtotime('-1 day')) . '00002';
|
||||
$internalOID2 = $this->createOrder([
|
||||
'OrderID' => $orderID2,
|
||||
'PlacerID' => 'PLC002',
|
||||
'InternalPID' => 2,
|
||||
'SiteID' => '1',
|
||||
'PVADTID' => 3,
|
||||
'ReqApp' => 'HIS',
|
||||
'Priority' => 'R',
|
||||
'TrnDate' => $yesterday,
|
||||
'EffDate' => $yesterday,
|
||||
'CreateDate' => $yesterday
|
||||
]);
|
||||
|
||||
echo "\nCreated Order 2: {$orderID2} (InternalOID: {$internalOID2})\n";
|
||||
|
||||
// Create specimen for Lipid/Liver (SST tube - ConDefID = 1)
|
||||
$specimenID2 = $orderID2 . '-S01';
|
||||
$internalSID2 = $this->createSpecimen([
|
||||
'SID' => $specimenID2,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID2,
|
||||
'ConDefID' => 1,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $yesterday
|
||||
]);
|
||||
|
||||
$this->createSpecimenStatus($specimenID2, $internalOID2, 'PENDING', $yesterday);
|
||||
$this->createSpecimenStatus($specimenID2, $internalOID2, 'COLLECTED', date('Y-m-d H:i:s', strtotime('-1 day +30 minutes')));
|
||||
$this->createSpecimenStatus($specimenID2, $internalOID2, 'RECEIVED', date('Y-m-d H:i:s', strtotime('-1 day +2 hours')));
|
||||
$this->createSpecimenStatus($specimenID2, $internalOID2, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +6 hours')));
|
||||
|
||||
echo " Created Specimen: {$specimenID2} (SST)\n";
|
||||
|
||||
// Create Lipid Profile results
|
||||
$lipidTests = [
|
||||
['code' => 'CHOL', 'result' => '195'],
|
||||
['code' => 'TG', 'result' => '145'],
|
||||
['code' => 'HDL', 'result' => '55'],
|
||||
['code' => 'LDL', 'result' => '115'],
|
||||
];
|
||||
|
||||
foreach ($lipidTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID2,
|
||||
'InternalSID' => $internalSID2,
|
||||
'SID' => $specimenID2,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => $test['result'],
|
||||
'SampleType' => 'Serum',
|
||||
'ResultDateTime' => date('Y-m-d H:i:s', strtotime('-1 day +6 hours')),
|
||||
'CreateDate' => date('Y-m-d H:i:s', strtotime('-1 day +6 hours'))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Liver Function results
|
||||
$liverTests = [
|
||||
['code' => 'SGOT', 'result' => '28'],
|
||||
['code' => 'SGPT', 'result' => '32'],
|
||||
];
|
||||
|
||||
foreach ($liverTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID2,
|
||||
'InternalSID' => $internalSID2,
|
||||
'SID' => $specimenID2,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => $test['result'],
|
||||
'SampleType' => 'Serum',
|
||||
'ResultDateTime' => date('Y-m-d H:i:s', strtotime('-1 day +6 hours')),
|
||||
'CreateDate' => date('Y-m-d H:i:s', strtotime('-1 day +6 hours'))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo " Created " . (count($lipidTests) + count($liverTests)) . " chemistry results\n";
|
||||
|
||||
// Create order status
|
||||
$this->createOrderStatus($internalOID2, 'ORDERED', $yesterday);
|
||||
$this->createOrderStatus($internalOID2, 'SPECIMEN_COLLECTED', date('Y-m-d H:i:s', strtotime('-1 day +30 minutes')));
|
||||
$this->createOrderStatus($internalOID2, 'IN_LAB', date('Y-m-d H:i:s', strtotime('-1 day +2 hours')));
|
||||
$this->createOrderStatus($internalOID2, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +6 hours')));
|
||||
|
||||
// ========================================
|
||||
// ORDER 3: Patient 3 - Renal Function + Glucose (URGENT)
|
||||
// ========================================
|
||||
$orderID3 = '001' . date('ymd', strtotime('-1 day')) . '00003';
|
||||
$internalOID3 = $this->createOrder([
|
||||
'OrderID' => $orderID3,
|
||||
'PlacerID' => 'PLC003',
|
||||
'InternalPID' => 3,
|
||||
'SiteID' => '1',
|
||||
'PVADTID' => 4,
|
||||
'ReqApp' => 'EMR',
|
||||
'Priority' => 'U',
|
||||
'TrnDate' => $yesterday,
|
||||
'EffDate' => $yesterday,
|
||||
'CreateDate' => $yesterday
|
||||
]);
|
||||
|
||||
echo "\nCreated Order 3: {$orderID3} (InternalOID: {$internalOID3}) [URGENT]\n";
|
||||
|
||||
// Create specimen for Renal/Glucose (SST tube)
|
||||
$specimenID3 = $orderID3 . '-S01';
|
||||
$internalSID3 = $this->createSpecimen([
|
||||
'SID' => $specimenID3,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID3,
|
||||
'ConDefID' => 1,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $yesterday
|
||||
]);
|
||||
|
||||
$this->createSpecimenStatus($specimenID3, $internalOID3, 'PENDING', $yesterday);
|
||||
$this->createSpecimenStatus($specimenID3, $internalOID3, 'COLLECTED', date('Y-m-d H:i:s', strtotime('-1 day +20 minutes')));
|
||||
$this->createSpecimenStatus($specimenID3, $internalOID3, 'RECEIVED', date('Y-m-d H:i:s', strtotime('-1 day +30 minutes')));
|
||||
$this->createSpecimenStatus($specimenID3, $internalOID3, 'PROCESSING', date('Y-m-d H:i:s', strtotime('-1 day +45 minutes')));
|
||||
$this->createSpecimenStatus($specimenID3, $internalOID3, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +2 hours')));
|
||||
|
||||
echo " Created Specimen: {$specimenID3} (SST)\n";
|
||||
|
||||
// Create Renal Function results
|
||||
$renalTests = [
|
||||
['code' => 'CREA', 'result' => '1.1'],
|
||||
['code' => 'UREA', 'result' => '18'],
|
||||
];
|
||||
|
||||
foreach ($renalTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID3,
|
||||
'InternalSID' => $internalSID3,
|
||||
'SID' => $specimenID3,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => $test['result'],
|
||||
'SampleType' => 'Serum',
|
||||
'ResultDateTime' => date('Y-m-d H:i:s', strtotime('-1 day +2 hours')),
|
||||
'CreateDate' => date('Y-m-d H:i:s', strtotime('-1 day +2 hours'))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Glucose result
|
||||
if (isset($testIDs['GLU'])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID3,
|
||||
'InternalSID' => $internalSID3,
|
||||
'SID' => $specimenID3,
|
||||
'TestSiteID' => $testIDs['GLU'],
|
||||
'TestSiteCode' => 'GLU',
|
||||
'Result' => '95',
|
||||
'SampleType' => 'Serum',
|
||||
'ResultDateTime' => date('Y-m-d H:i:s', strtotime('-1 day +2 hours')),
|
||||
'CreateDate' => date('Y-m-d H:i:s', strtotime('-1 day +2 hours'))
|
||||
]);
|
||||
}
|
||||
|
||||
echo " Created " . (count($renalTests) + 1) . " urgent chemistry results\n";
|
||||
|
||||
// Create order status (fast-tracked for urgent)
|
||||
$this->createOrderStatus($internalOID3, 'ORDERED', $yesterday);
|
||||
$this->createOrderStatus($internalOID3, 'SPECIMEN_COLLECTED', date('Y-m-d H:i:s', strtotime('-1 day +20 minutes')));
|
||||
$this->createOrderStatus($internalOID3, 'IN_LAB', date('Y-m-d H:i:s', strtotime('-1 day +30 minutes')));
|
||||
$this->createOrderStatus($internalOID3, 'COMPLETED', date('Y-m-d H:i:s', strtotime('-1 day +2 hours')));
|
||||
$this->createOrderStatus($internalOID3, 'REPORTED', date('Y-m-d H:i:s', strtotime('-1 day +2 hours +15 minutes')));
|
||||
|
||||
// Create order comment
|
||||
$this->createOrderComment($internalOID3, 'STAT order for suspected kidney dysfunction. Patient has diabetes history.', $yesterday);
|
||||
|
||||
// ========================================
|
||||
// ORDER 4: Patient 1 - Urinalysis (PENDING)
|
||||
// ========================================
|
||||
$orderID4 = '001' . date('ymd') . '00004';
|
||||
$internalOID4 = $this->createOrder([
|
||||
'OrderID' => $orderID4,
|
||||
'PlacerID' => 'PLC004',
|
||||
'InternalPID' => 1,
|
||||
'SiteID' => '1',
|
||||
'PVADTID' => 2,
|
||||
'ReqApp' => 'HIS',
|
||||
'Priority' => 'R',
|
||||
'TrnDate' => $now,
|
||||
'EffDate' => $now,
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
|
||||
echo "\nCreated Order 4: {$orderID4} (InternalOID: {$internalOID4})\n";
|
||||
|
||||
// Create urine specimen (Pot Urin - ConDefID = 12)
|
||||
$specimenID4 = $orderID4 . '-S01';
|
||||
$internalSID4 = $this->createSpecimen([
|
||||
'SID' => $specimenID4,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID4,
|
||||
'ConDefID' => 12,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'container',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
|
||||
$this->createSpecimenStatus($specimenID4, $internalOID4, 'PENDING', $now);
|
||||
|
||||
echo " Created Specimen: {$specimenID4} (Urine)\n";
|
||||
|
||||
// Create Urinalysis results (pending - use current time for ResultDateTime)
|
||||
$urineTests = [
|
||||
['code' => 'UCOLOR'],
|
||||
['code' => 'UGLUC'],
|
||||
['code' => 'UPROT'],
|
||||
['code' => 'PH'],
|
||||
];
|
||||
|
||||
foreach ($urineTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID4,
|
||||
'InternalSID' => $internalSID4,
|
||||
'SID' => $specimenID4,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => null,
|
||||
'SampleType' => 'Urine',
|
||||
'ResultDateTime' => $now,
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo " Created " . count($urineTests) . " pending urinalysis results\n";
|
||||
|
||||
// Create order status
|
||||
$this->createOrderStatus($internalOID4, 'ORDERED', $now);
|
||||
|
||||
// ========================================
|
||||
// ORDER 5: Patient 3 - Multiple tubes (CBC + Chemistry)
|
||||
// ========================================
|
||||
$orderID5 = '001' . date('ymd') . '00005';
|
||||
$internalOID5 = $this->createOrder([
|
||||
'OrderID' => $orderID5,
|
||||
'PlacerID' => 'PLC005',
|
||||
'InternalPID' => 3,
|
||||
'SiteID' => '1',
|
||||
'PVADTID' => 4,
|
||||
'ReqApp' => 'HIS',
|
||||
'Priority' => 'R',
|
||||
'TrnDate' => $now,
|
||||
'EffDate' => $now,
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
|
||||
echo "\nCreated Order 5: {$orderID5} (InternalOID: {$internalOID5}) [Multi-container]\n";
|
||||
|
||||
// Create EDTA specimen for CBC
|
||||
$specimenID5a = $orderID5 . '-S01';
|
||||
$internalSID5a = $this->createSpecimen([
|
||||
'SID' => $specimenID5a,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID5,
|
||||
'ConDefID' => 9,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
|
||||
$this->createSpecimenStatus($specimenID5a, $internalOID5, 'PENDING', $now);
|
||||
|
||||
echo " Created Specimen: {$specimenID5a} (EDTA)\n";
|
||||
|
||||
// Create SST specimen for Chemistry
|
||||
$specimenID5b = $orderID5 . '-S02';
|
||||
$internalSID5b = $this->createSpecimen([
|
||||
'SID' => $specimenID5b,
|
||||
'SiteID' => '1',
|
||||
'OrderID' => $internalOID5,
|
||||
'ConDefID' => 1,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
|
||||
$this->createSpecimenStatus($specimenID5b, $internalOID5, 'PENDING', $now);
|
||||
|
||||
echo " Created Specimen: {$specimenID5b} (SST)\n";
|
||||
|
||||
// Create pending results for CBC
|
||||
foreach ($cbcTests as $test) {
|
||||
if (isset($testIDs[$test['code']])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID5,
|
||||
'InternalSID' => $internalSID5a,
|
||||
'SID' => $specimenID5a,
|
||||
'TestSiteID' => $testIDs[$test['code']],
|
||||
'TestSiteCode' => $test['code'],
|
||||
'Result' => null,
|
||||
'SampleType' => 'Whole Blood',
|
||||
'ResultDateTime' => $now,
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create pending results for Chemistry
|
||||
$chemTests = ['GLU', 'CREA', 'UREA', 'SGOT', 'SGPT'];
|
||||
foreach ($chemTests as $testCode) {
|
||||
if (isset($testIDs[$testCode])) {
|
||||
$this->createPatRes([
|
||||
'OrderID' => $internalOID5,
|
||||
'InternalSID' => $internalSID5b,
|
||||
'SID' => $specimenID5b,
|
||||
'TestSiteID' => $testIDs[$testCode],
|
||||
'TestSiteCode' => $testCode,
|
||||
'Result' => null,
|
||||
'SampleType' => 'Serum',
|
||||
'ResultDateTime' => $now,
|
||||
'CreateDate' => $now
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo " Created " . (count($cbcTests) + count($chemTests)) . " pending results\n";
|
||||
|
||||
// Create order status
|
||||
$this->createOrderStatus($internalOID5, 'ORDERED', $now);
|
||||
|
||||
// ========================================
|
||||
// SUMMARY
|
||||
// ========================================
|
||||
echo "\n";
|
||||
echo "========================================\n";
|
||||
echo "ORDER SEEDING COMPLETED SUCCESSFULLY!\n";
|
||||
echo "========================================\n";
|
||||
echo "Orders Created: 5\n";
|
||||
echo " - Order 1: CBC (Complete Blood Count) - COMPLETED\n";
|
||||
echo " - Order 2: Lipid + Liver Profile - COMPLETED\n";
|
||||
echo " - Order 3: Renal + Glucose - COMPLETED (URGENT)\n";
|
||||
echo " - Order 4: Urinalysis - PENDING\n";
|
||||
echo " - Order 5: CBC + Chemistry - PENDING (Multi-container)\n";
|
||||
echo "----------------------------------------\n";
|
||||
echo "Specimens Created: 6\n";
|
||||
echo " - SST tubes: 4\n";
|
||||
echo " - EDTA tubes: 2\n";
|
||||
echo " - Urine containers: 1\n";
|
||||
echo "----------------------------------------\n";
|
||||
echo "Patient Results: 29 total\n";
|
||||
echo " - With results: 16\n";
|
||||
echo " - Pending: 13\n";
|
||||
echo "========================================\n";
|
||||
}
|
||||
|
||||
private function getTestIDs(): array
|
||||
{
|
||||
$tests = $this->db->table('testdefsite')
|
||||
->select('TestSiteID, TestSiteCode')
|
||||
->where('EndDate IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$testIDs = [];
|
||||
foreach ($tests as $test) {
|
||||
$testIDs[$test['TestSiteCode']] = $test['TestSiteID'];
|
||||
}
|
||||
|
||||
return $testIDs;
|
||||
}
|
||||
|
||||
private function createOrder(array $data): int
|
||||
{
|
||||
$this->db->table('ordertest')->insert($data);
|
||||
return $this->db->insertID();
|
||||
}
|
||||
|
||||
private function createSpecimen(array $data): int
|
||||
{
|
||||
$this->db->table('specimen')->insert($data);
|
||||
return $this->db->insertID();
|
||||
}
|
||||
|
||||
private function createSpecimenStatus(string $specimenID, int $orderID, string $status, string $dateTime): void
|
||||
{
|
||||
$this->db->table('specimenstatus')->insert([
|
||||
'SID' => $specimenID,
|
||||
'OrderID' => $orderID,
|
||||
'SpcStatus' => $status,
|
||||
'CreateDate' => $dateTime
|
||||
]);
|
||||
}
|
||||
|
||||
private function createPatRes(array $data): void
|
||||
{
|
||||
$this->db->table('patres')->insert($data);
|
||||
}
|
||||
|
||||
private function createOrderStatus(int $orderID, string $status, string $dateTime): void
|
||||
{
|
||||
$this->db->table('orderstatus')->insert([
|
||||
'InternalOID' => $orderID,
|
||||
'OrderStatus' => $status,
|
||||
'CreateDate' => $dateTime
|
||||
]);
|
||||
}
|
||||
|
||||
private function createOrderComment(int $orderID, string $comment, string $dateTime): void
|
||||
{
|
||||
$this->db->table('ordercom')->insert([
|
||||
'InternalOID' => $orderID,
|
||||
'Comment' => $comment,
|
||||
'CreateDate' => $dateTime
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -52,65 +52,180 @@ class OrderTestModel extends BaseModel {
|
||||
return $siteCode . $year . $month . $day . $seqStr;
|
||||
}
|
||||
|
||||
public function generateSpecimenID(string $orderID, int $seq): string {
|
||||
return $orderID . '-S' . str_pad($seq, 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
public function createOrder(array $data): string {
|
||||
$orderID = $data['OrderID'] ?? $this->generateOrderID($data['SiteCode'] ?? '00');
|
||||
$this->db->transStart();
|
||||
|
||||
$orderData = [
|
||||
'OrderID' => $orderID,
|
||||
'PlacerID' => $data['PlacerID'] ?? null,
|
||||
'InternalPID' => $data['InternalPID'],
|
||||
'SiteID' => $data['SiteID'] ?? '1',
|
||||
'PVADTID' => $data['PatVisitID'] ?? $data['PVADTID'] ?? 0,
|
||||
'ReqApp' => $data['ReqApp'] ?? null,
|
||||
'Priority' => $data['Priority'] ?? 'R',
|
||||
'TrnDate' => $data['OrderDateTime'] ?? $data['TrnDate'] ?? date('Y-m-d H:i:s'),
|
||||
'EffDate' => $data['EffDate'] ?? date('Y-m-d H:i:s'),
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
];
|
||||
try {
|
||||
$orderID = $data['OrderID'] ?? $this->generateOrderID($data['SiteCode'] ?? '00');
|
||||
|
||||
$internalOID = $this->insert($orderData);
|
||||
|
||||
// Handle Order Comments
|
||||
if (!empty($data['Comment'])) {
|
||||
$this->db->table('ordercom')->insert([
|
||||
'InternalOID' => $internalOID,
|
||||
'Comment' => $data['Comment'],
|
||||
$orderData = [
|
||||
'OrderID' => $orderID,
|
||||
'PlacerID' => $data['PlacerID'] ?? null,
|
||||
'InternalPID' => $data['InternalPID'],
|
||||
'SiteID' => $data['SiteID'] ?? '1',
|
||||
'PVADTID' => $data['PatVisitID'] ?? $data['PVADTID'] ?? 0,
|
||||
'ReqApp' => $data['ReqApp'] ?? null,
|
||||
'Priority' => $data['Priority'] ?? 'R',
|
||||
'TrnDate' => $data['OrderDateTime'] ?? $data['TrnDate'] ?? date('Y-m-d H:i:s'),
|
||||
'EffDate' => $data['EffDate'] ?? date('Y-m-d H:i:s'),
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
];
|
||||
|
||||
// Process Tests Expansion
|
||||
if (isset($data['Tests']) && is_array($data['Tests'])) {
|
||||
$testToOrder = [];
|
||||
$testModel = new \App\Models\Test\TestDefSiteModel();
|
||||
$grpModel = new \App\Models\Test\TestDefGrpModel();
|
||||
$calModel = new \App\Models\Test\TestDefCalModel();
|
||||
$internalOID = $this->insert($orderData);
|
||||
|
||||
foreach ($data['Tests'] as $test) {
|
||||
$testSiteID = $test['TestSiteID'] ?? $test['TestID'] ?? null;
|
||||
if ($testSiteID) {
|
||||
$this->expandTest($testSiteID, $testToOrder, $testModel, $grpModel, $calModel);
|
||||
}
|
||||
if (!$internalOID) {
|
||||
throw new \Exception('Failed to create order');
|
||||
}
|
||||
|
||||
// Insert unique tests into patres
|
||||
if (!empty($testToOrder)) {
|
||||
$resModel = new \App\Models\PatResultModel();
|
||||
// Handle Order Comments
|
||||
if (!empty($data['Comment'])) {
|
||||
$this->db->table('ordercom')->insert([
|
||||
'InternalOID' => $internalOID,
|
||||
'Comment' => $data['Comment'],
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
// Process Tests Expansion
|
||||
$testToOrder = [];
|
||||
$specimenConDefMap = []; // Map ConDefID to specimen info
|
||||
|
||||
if (isset($data['Tests']) && is_array($data['Tests'])) {
|
||||
$testModel = new \App\Models\Test\TestDefSiteModel();
|
||||
$grpModel = new \App\Models\Test\TestDefGrpModel();
|
||||
$calModel = new \App\Models\Test\TestDefCalModel();
|
||||
$testMapDetailModel = new \App\Models\Test\TestMapDetailModel();
|
||||
$containerDefModel = new \App\Models\Specimen\ContainerDefModel();
|
||||
|
||||
foreach ($data['Tests'] as $test) {
|
||||
$testSiteID = $test['TestSiteID'] ?? $test['TestID'] ?? null;
|
||||
if ($testSiteID) {
|
||||
$this->expandTest($testSiteID, $testToOrder, $testModel, $grpModel, $calModel);
|
||||
}
|
||||
}
|
||||
|
||||
// Group tests by container requirement
|
||||
$testsByContainer = [];
|
||||
foreach ($testToOrder as $tid => $tinfo) {
|
||||
$resModel->insert([
|
||||
// Find container requirement for this test
|
||||
$containerReq = $this->getContainerRequirement($tid, $testMapDetailModel, $containerDefModel);
|
||||
$conDefID = $containerReq['ConDefID'] ?? null;
|
||||
|
||||
if (!isset($testsByContainer[$conDefID])) {
|
||||
$testsByContainer[$conDefID] = [
|
||||
'tests' => [],
|
||||
'containerInfo' => $containerReq
|
||||
];
|
||||
}
|
||||
$testsByContainer[$conDefID]['tests'][$tid] = $tinfo;
|
||||
}
|
||||
|
||||
// Create specimens for each unique container requirement
|
||||
$specimenSeq = 1;
|
||||
foreach ($testsByContainer as $conDefID => $containerData) {
|
||||
$specimenID = $this->generateSpecimenID($orderID, $specimenSeq++);
|
||||
|
||||
$specimenData = [
|
||||
'SID' => $specimenID,
|
||||
'SiteID' => $data['SiteID'] ?? '1',
|
||||
'OrderID' => $internalOID,
|
||||
'TestSiteID' => $tid,
|
||||
'TestSiteCode' => $tinfo['TestSiteCode'],
|
||||
'SID' => $orderID,
|
||||
'SampleID' => $orderID,
|
||||
'ResultDateTime' => $orderData['TrnDate'],
|
||||
'ConDefID' => $conDefID,
|
||||
'Qty' => 1,
|
||||
'Unit' => 'tube',
|
||||
'GenerateBy' => 'ORDER',
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$this->db->table('specimen')->insert($specimenData);
|
||||
$internalSID = $this->db->insertID();
|
||||
|
||||
// Create specimen status
|
||||
$this->db->table('specimenstatus')->insert([
|
||||
'SID' => $specimenID,
|
||||
'OrderID' => $internalOID,
|
||||
'SpcStatus' => 'PENDING',
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Store mapping for patres creation
|
||||
foreach ($containerData['tests'] as $tid => $tinfo) {
|
||||
$specimenConDefMap[$tid] = [
|
||||
'InternalSID' => $internalSID,
|
||||
'SID' => $specimenID,
|
||||
'ConDefID' => $conDefID
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Insert unique tests into patres with specimen links
|
||||
if (!empty($testToOrder)) {
|
||||
$resModel = new \App\Models\PatResultModel();
|
||||
foreach ($testToOrder as $tid => $tinfo) {
|
||||
$specimenInfo = $specimenConDefMap[$tid] ?? null;
|
||||
|
||||
$patResData = [
|
||||
'OrderID' => $internalOID,
|
||||
'TestSiteID' => $tid,
|
||||
'TestSiteCode' => $tinfo['TestSiteCode'],
|
||||
'SID' => $orderID,
|
||||
'SampleID' => $orderID,
|
||||
'ResultDateTime' => $orderData['TrnDate'],
|
||||
'CreateDate' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
if ($specimenInfo) {
|
||||
$patResData['InternalSID'] = $specimenInfo['InternalSID'];
|
||||
}
|
||||
|
||||
$resModel->insert($patResData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if ($this->db->transStatus() === false) {
|
||||
throw new \Exception('Transaction failed');
|
||||
}
|
||||
|
||||
return $orderID;
|
||||
} catch (\Exception $e) {
|
||||
$this->db->transRollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function getContainerRequirement($testSiteID, $testMapDetailModel, $containerDefModel): array {
|
||||
// Try to find container requirement from test mapping
|
||||
$containerDef = $this->db->table('testmapdetail tmd')
|
||||
->select('tmd.ConDefID, cd.ConCode, cd.ConName')
|
||||
->join('containerdef cd', 'cd.ConDefID = tmd.ConDefID', 'left')
|
||||
->where('tmd.ClientTestCode', function($builder) use ($testSiteID) {
|
||||
return $builder->select('TestSiteCode')
|
||||
->from('testdefsite')
|
||||
->where('TestSiteID', $testSiteID);
|
||||
})
|
||||
->where('tmd.EndDate IS NULL')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($containerDef) {
|
||||
return [
|
||||
'ConDefID' => $containerDef['ConDefID'],
|
||||
'ConCode' => $containerDef['ConCode'],
|
||||
'ConName' => $containerDef['ConName']
|
||||
];
|
||||
}
|
||||
|
||||
return $orderID;
|
||||
return [
|
||||
'ConDefID' => null,
|
||||
'ConCode' => 'DEFAULT',
|
||||
'ConName' => 'Default Container'
|
||||
];
|
||||
}
|
||||
|
||||
private function expandTest($testSiteID, &$testToOrder, $testModel, $grpModel, $calModel) {
|
||||
|
||||
@ -17,7 +17,7 @@ class TestDefSiteModel extends BaseModel {
|
||||
'Unit1', 'Factor', 'Unit2', 'Decimal',
|
||||
'ReqQty', 'ReqQtyUnit', 'CollReq', 'Method', 'ExpectedTAT',
|
||||
'SeqScr', 'SeqRpt', 'IndentLeft', 'FontStyle', 'VisibleScr', 'VisibleRpt',
|
||||
'CountStat', 'Level',
|
||||
'CountStat', 'Level', 'Requestable',
|
||||
'CreateDate', 'StartDate','EndDate'
|
||||
];
|
||||
|
||||
@ -148,7 +148,7 @@ class TestDefSiteModel extends BaseModel {
|
||||
->get()->getResultArray();
|
||||
|
||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
|
||||
|
||||
} elseif (TestValidationService::isGroup($typeCode)) {
|
||||
$row['testdefgrp'] = $db->table('testdefgrp')
|
||||
@ -164,11 +164,11 @@ class TestDefSiteModel extends BaseModel {
|
||||
]);
|
||||
|
||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
|
||||
|
||||
} elseif (TestValidationService::isTitle($typeCode)) {
|
||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
|
||||
|
||||
} elseif (TestValidationService::isTechnicalTest($typeCode)) {
|
||||
// Technical details are now flattened into the main row
|
||||
@ -182,7 +182,7 @@ class TestDefSiteModel extends BaseModel {
|
||||
}
|
||||
|
||||
$testMapModel = new \App\Models\Test\TestMapModel();
|
||||
$row['testmap'] = $testMapModel->where('TestSiteID', $TestSiteID)->where('EndDate IS NULL')->findAll();
|
||||
$row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
|
||||
}
|
||||
|
||||
return $row;
|
||||
|
||||
@ -104,13 +104,4 @@ class TestMapModel extends BaseModel {
|
||||
->where('testmapdetail.EndDate IS NULL')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable test mappings by TestSiteID
|
||||
*/
|
||||
public function disableByTestSiteID($testSiteID) {
|
||||
$this->db->table('testmap')
|
||||
->where('TestSiteID', $testSiteID)
|
||||
->update(['EndDate' => date('Y-m-d H:i:s')]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1045,13 +1045,34 @@ paths:
|
||||
VER: Verified
|
||||
REV: Reviewed
|
||||
REP: Reported
|
||||
- name: include
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- details
|
||||
description: Include specimens and tests in response
|
||||
responses:
|
||||
'200':
|
||||
description: List of orders
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OrderTest'
|
||||
post:
|
||||
tags:
|
||||
- Orders
|
||||
summary: Create 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:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
@ -1061,12 +1082,22 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- PatientID
|
||||
- InternalPID
|
||||
- Tests
|
||||
properties:
|
||||
PatientID:
|
||||
OrderID:
|
||||
type: string
|
||||
VisitID:
|
||||
description: Optional custom order ID (auto-generated if not provided)
|
||||
InternalPID:
|
||||
type: integer
|
||||
description: Patient internal ID
|
||||
PatVisitID:
|
||||
type: integer
|
||||
description: Visit ID
|
||||
SiteID:
|
||||
type: integer
|
||||
default: 1
|
||||
PlacerID:
|
||||
type: string
|
||||
Priority:
|
||||
type: string
|
||||
@ -1074,26 +1105,48 @@ paths:
|
||||
- R
|
||||
- S
|
||||
- U
|
||||
default: R
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
SiteID:
|
||||
type: integer
|
||||
RequestingPhysician:
|
||||
ReqApp:
|
||||
type: string
|
||||
description: Requesting application
|
||||
Comment:
|
||||
type: string
|
||||
Tests:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- TestSiteID
|
||||
properties:
|
||||
TestSiteID:
|
||||
type: integer
|
||||
description: Test definition site ID
|
||||
TestID:
|
||||
type: integer
|
||||
SpecimenType:
|
||||
type: string
|
||||
description: Alias for TestSiteID
|
||||
responses:
|
||||
'201':
|
||||
description: Order created successfully
|
||||
description: Order created successfully with specimens and tests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: success
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/OrderTest'
|
||||
'400':
|
||||
description: Validation error
|
||||
'500':
|
||||
description: Server error
|
||||
patch:
|
||||
tags:
|
||||
- Orders
|
||||
@ -1105,16 +1158,64 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OrderTest'
|
||||
type: object
|
||||
required:
|
||||
- OrderID
|
||||
properties:
|
||||
OrderID:
|
||||
type: string
|
||||
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/OrderTest'
|
||||
delete:
|
||||
tags:
|
||||
- Orders
|
||||
summary: Delete order
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- OrderID
|
||||
properties:
|
||||
OrderID:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Order deleted
|
||||
@ -1156,11 +1257,23 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Order status updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/OrderTest'
|
||||
/api/ordertest/{id}:
|
||||
get:
|
||||
tags:
|
||||
- Orders
|
||||
summary: Get order by ID
|
||||
description: Returns order details with associated specimens and tests
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
@ -1169,9 +1282,21 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Order ID (e.g., 0025030300001)
|
||||
responses:
|
||||
'200':
|
||||
description: Order details
|
||||
description: Order details with specimens and tests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
$ref: '#/components/schemas/OrderTest'
|
||||
/api/organization/account/{id}:
|
||||
get:
|
||||
tags:
|
||||
@ -1885,6 +2010,38 @@ paths:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: InternalPID
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
description: Filter by internal patient ID (exact match)
|
||||
- name: PVID
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by visit ID (partial match)
|
||||
- name: PatientID
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by patient ID (partial match)
|
||||
- name: PatientName
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Search by patient name (searches in both first and last name)
|
||||
- name: CreateDateFrom
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Filter visits created on or after this date
|
||||
- name: CreateDateTo
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Filter visits created on or before this date
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
@ -4899,6 +5056,9 @@ components:
|
||||
SiteID:
|
||||
type: integer
|
||||
description: Site reference
|
||||
LastLocation:
|
||||
type: string
|
||||
description: Full name of the last/current location from patvisitadt
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
@ -5371,6 +5531,10 @@ components:
|
||||
default: 1
|
||||
Level:
|
||||
type: integer
|
||||
Requestable:
|
||||
type: integer
|
||||
default: 1
|
||||
description: Flag indicating if test can be requested (1=yes, 0=no)
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
@ -5715,13 +5879,48 @@ components:
|
||||
OrderTest:
|
||||
type: object
|
||||
properties:
|
||||
InternalOID:
|
||||
type: integer
|
||||
description: Internal order ID
|
||||
OrderID:
|
||||
type: string
|
||||
PatientID:
|
||||
description: Order ID (e.g., 0025030300001)
|
||||
PlacerID:
|
||||
type: string
|
||||
VisitID:
|
||||
nullable: true
|
||||
InternalPID:
|
||||
type: integer
|
||||
description: Patient internal ID
|
||||
SiteID:
|
||||
type: integer
|
||||
PVADTID:
|
||||
type: integer
|
||||
description: Visit ADT ID
|
||||
ReqApp:
|
||||
type: string
|
||||
OrderDate:
|
||||
nullable: true
|
||||
Priority:
|
||||
type: string
|
||||
enum:
|
||||
- R
|
||||
- S
|
||||
- U
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
PriorityLabel:
|
||||
type: string
|
||||
description: Priority display text
|
||||
TrnDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Transaction/Order date
|
||||
EffDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Effective date
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
OrderStatus:
|
||||
@ -5743,23 +5942,16 @@ components:
|
||||
OrderStatusLabel:
|
||||
type: string
|
||||
description: Order status display text
|
||||
Priority:
|
||||
type: string
|
||||
enum:
|
||||
- R
|
||||
- S
|
||||
- U
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
PriorityLabel:
|
||||
type: string
|
||||
description: Priority display text
|
||||
SiteID:
|
||||
type: integer
|
||||
RequestingPhysician:
|
||||
type: string
|
||||
Specimens:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OrderSpecimen'
|
||||
description: Associated specimens for this order
|
||||
Tests:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/OrderTestItem'
|
||||
description: Test results (patres) for this order
|
||||
OrderItem:
|
||||
type: object
|
||||
properties:
|
||||
@ -6061,6 +6253,93 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Occupation display text
|
||||
OrderSpecimen:
|
||||
type: object
|
||||
properties:
|
||||
InternalSID:
|
||||
type: integer
|
||||
description: Internal specimen ID
|
||||
SID:
|
||||
type: string
|
||||
description: Specimen ID (e.g., 0025030300001-S01)
|
||||
SiteID:
|
||||
type: integer
|
||||
OrderID:
|
||||
type: integer
|
||||
description: Reference to internal order ID
|
||||
ConDefID:
|
||||
type: integer
|
||||
description: Container Definition ID
|
||||
nullable: true
|
||||
ConCode:
|
||||
type: string
|
||||
description: Container code
|
||||
nullable: true
|
||||
ConName:
|
||||
type: string
|
||||
description: Container name
|
||||
nullable: true
|
||||
Qty:
|
||||
type: integer
|
||||
description: Quantity
|
||||
Unit:
|
||||
type: string
|
||||
description: Unit of measurement
|
||||
Status:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- COLLECTED
|
||||
- RECEIVED
|
||||
- PREPARED
|
||||
- REJECTED
|
||||
description: Current specimen status
|
||||
GenerateBy:
|
||||
type: string
|
||||
description: Source that generated this specimen
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
OrderTestItem:
|
||||
type: object
|
||||
properties:
|
||||
ResultID:
|
||||
type: integer
|
||||
description: Unique result ID
|
||||
OrderID:
|
||||
type: integer
|
||||
description: Reference to internal order ID
|
||||
InternalSID:
|
||||
type: integer
|
||||
description: Reference to specimen
|
||||
nullable: true
|
||||
TestSiteID:
|
||||
type: integer
|
||||
description: Test definition site ID
|
||||
TestSiteCode:
|
||||
type: string
|
||||
description: Test code
|
||||
TestSiteName:
|
||||
type: string
|
||||
description: Test name
|
||||
nullable: true
|
||||
SID:
|
||||
type: string
|
||||
description: Order ID reference
|
||||
SampleID:
|
||||
type: string
|
||||
description: Sample ID (same as OrderID)
|
||||
Result:
|
||||
type: string
|
||||
description: Test result value
|
||||
nullable: true
|
||||
ResultDateTime:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Result timestamp
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
TestMapDetail:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@ -1,13 +1,45 @@
|
||||
OrderTest:
|
||||
type: object
|
||||
properties:
|
||||
InternalOID:
|
||||
type: integer
|
||||
description: Internal order ID
|
||||
OrderID:
|
||||
type: string
|
||||
PatientID:
|
||||
description: Order ID (e.g., 0025030300001)
|
||||
PlacerID:
|
||||
type: string
|
||||
VisitID:
|
||||
nullable: true
|
||||
InternalPID:
|
||||
type: integer
|
||||
description: Patient internal ID
|
||||
SiteID:
|
||||
type: integer
|
||||
PVADTID:
|
||||
type: integer
|
||||
description: Visit ADT ID
|
||||
ReqApp:
|
||||
type: string
|
||||
OrderDate:
|
||||
nullable: true
|
||||
Priority:
|
||||
type: string
|
||||
enum: [R, S, U]
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
PriorityLabel:
|
||||
type: string
|
||||
description: Priority display text
|
||||
TrnDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Transaction/Order date
|
||||
EffDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Effective date
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
OrderStatus:
|
||||
@ -23,20 +55,100 @@ OrderTest:
|
||||
OrderStatusLabel:
|
||||
type: string
|
||||
description: Order status display text
|
||||
Priority:
|
||||
Specimens:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/OrderSpecimen'
|
||||
description: Associated specimens for this order
|
||||
Tests:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/OrderTestItem'
|
||||
description: Test results (patres) for this order
|
||||
|
||||
OrderSpecimen:
|
||||
type: object
|
||||
properties:
|
||||
InternalSID:
|
||||
type: integer
|
||||
description: Internal specimen ID
|
||||
SID:
|
||||
type: string
|
||||
enum: [R, S, U]
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
PriorityLabel:
|
||||
type: string
|
||||
description: Priority display text
|
||||
description: Specimen ID (e.g., 0025030300001-S01)
|
||||
SiteID:
|
||||
type: integer
|
||||
RequestingPhysician:
|
||||
OrderID:
|
||||
type: integer
|
||||
description: Reference to internal order ID
|
||||
ConDefID:
|
||||
type: integer
|
||||
description: Container Definition ID
|
||||
nullable: true
|
||||
ConCode:
|
||||
type: string
|
||||
description: Container code
|
||||
nullable: true
|
||||
ConName:
|
||||
type: string
|
||||
description: Container name
|
||||
nullable: true
|
||||
Qty:
|
||||
type: integer
|
||||
description: Quantity
|
||||
Unit:
|
||||
type: string
|
||||
description: Unit of measurement
|
||||
Status:
|
||||
type: string
|
||||
enum: [PENDING, COLLECTED, RECEIVED, PREPARED, REJECTED]
|
||||
description: Current specimen status
|
||||
GenerateBy:
|
||||
type: string
|
||||
description: Source that generated this specimen
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
OrderTestItem:
|
||||
type: object
|
||||
properties:
|
||||
ResultID:
|
||||
type: integer
|
||||
description: Unique result ID
|
||||
OrderID:
|
||||
type: integer
|
||||
description: Reference to internal order ID
|
||||
InternalSID:
|
||||
type: integer
|
||||
description: Reference to specimen
|
||||
nullable: true
|
||||
TestSiteID:
|
||||
type: integer
|
||||
description: Test definition site ID
|
||||
TestSiteCode:
|
||||
type: string
|
||||
description: Test code
|
||||
TestSiteName:
|
||||
type: string
|
||||
description: Test name
|
||||
nullable: true
|
||||
SID:
|
||||
type: string
|
||||
description: Order ID reference
|
||||
SampleID:
|
||||
type: string
|
||||
description: Sample ID (same as OrderID)
|
||||
Result:
|
||||
type: string
|
||||
description: Test result value
|
||||
nullable: true
|
||||
ResultDateTime:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Result timestamp
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
OrderItem:
|
||||
type: object
|
||||
|
||||
@ -16,6 +16,9 @@ PatientVisit:
|
||||
SiteID:
|
||||
type: integer
|
||||
description: Site reference
|
||||
LastLocation:
|
||||
type: string
|
||||
description: Full name of the last/current location from patvisitadt
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
@ -118,6 +118,10 @@ TestDefinition:
|
||||
default: 1
|
||||
Level:
|
||||
type: integer
|
||||
Requestable:
|
||||
type: integer
|
||||
default: 1
|
||||
description: Flag indicating if test can be requested (1=yes, 0=no)
|
||||
CreateDate:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
@ -30,13 +30,33 @@
|
||||
VER: Verified
|
||||
REV: Reviewed
|
||||
REP: Reported
|
||||
- name: include
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [details]
|
||||
description: Include specimens and tests in response
|
||||
responses:
|
||||
'200':
|
||||
description: List of orders
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../components/schemas/orders.yaml#/OrderTest'
|
||||
|
||||
post:
|
||||
tags: [Orders]
|
||||
summary: Create 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:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
@ -46,36 +66,68 @@
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- PatientID
|
||||
- InternalPID
|
||||
- Tests
|
||||
properties:
|
||||
PatientID:
|
||||
OrderID:
|
||||
type: string
|
||||
VisitID:
|
||||
description: Optional custom order ID (auto-generated if not provided)
|
||||
InternalPID:
|
||||
type: integer
|
||||
description: Patient internal ID
|
||||
PatVisitID:
|
||||
type: integer
|
||||
description: Visit ID
|
||||
SiteID:
|
||||
type: integer
|
||||
default: 1
|
||||
PlacerID:
|
||||
type: string
|
||||
Priority:
|
||||
type: string
|
||||
enum: [R, S, U]
|
||||
default: R
|
||||
description: |
|
||||
R: Routine
|
||||
S: Stat
|
||||
U: Urgent
|
||||
SiteID:
|
||||
type: integer
|
||||
RequestingPhysician:
|
||||
ReqApp:
|
||||
type: string
|
||||
description: Requesting application
|
||||
Comment:
|
||||
type: string
|
||||
Tests:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- TestSiteID
|
||||
properties:
|
||||
TestSiteID:
|
||||
type: integer
|
||||
description: Test definition site ID
|
||||
TestID:
|
||||
type: integer
|
||||
SpecimenType:
|
||||
type: string
|
||||
description: Alias for TestSiteID
|
||||
responses:
|
||||
'201':
|
||||
description: Order created successfully
|
||||
description: Order created successfully with specimens and tests
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: success
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
$ref: '../components/schemas/orders.yaml#/OrderTest'
|
||||
'400':
|
||||
description: Validation error
|
||||
'500':
|
||||
description: Server error
|
||||
|
||||
patch:
|
||||
tags: [Orders]
|
||||
@ -87,16 +139,55 @@
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/orders.yaml#/OrderTest'
|
||||
type: object
|
||||
required:
|
||||
- OrderID
|
||||
properties:
|
||||
OrderID:
|
||||
type: string
|
||||
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'
|
||||
|
||||
delete:
|
||||
tags: [Orders]
|
||||
summary: Delete order
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- OrderID
|
||||
properties:
|
||||
OrderID:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Order deleted
|
||||
@ -132,11 +223,23 @@
|
||||
responses:
|
||||
'200':
|
||||
description: Order status updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
data:
|
||||
$ref: '../components/schemas/orders.yaml#/OrderTest'
|
||||
|
||||
/api/ordertest/{id}:
|
||||
get:
|
||||
tags: [Orders]
|
||||
summary: Get order by ID
|
||||
description: Returns order details with associated specimens and tests
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
@ -145,6 +248,18 @@
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Order ID (e.g., 0025030300001)
|
||||
responses:
|
||||
'200':
|
||||
description: Order details
|
||||
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'
|
||||
|
||||
@ -5,6 +5,38 @@
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: InternalPID
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
description: Filter by internal patient ID (exact match)
|
||||
- name: PVID
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by visit ID (partial match)
|
||||
- name: PatientID
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by patient ID (partial match)
|
||||
- name: PatientName
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Search by patient name (searches in both first and last name)
|
||||
- name: CreateDateFrom
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Filter visits created on or after this date
|
||||
- name: CreateDateTo
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Filter visits created on or before this date
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
|
||||
210
tests/feature/Orders/OrderCreateTest.php
Normal file
210
tests/feature/Orders/OrderCreateTest.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Orders;
|
||||
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Faker\Factory;
|
||||
|
||||
class OrderCreateTest extends CIUnitTestCase
|
||||
{
|
||||
use FeatureTestTrait;
|
||||
|
||||
protected $endpoint = 'api/ordertest';
|
||||
|
||||
public function testCreateOrderSuccess()
|
||||
{
|
||||
$faker = Factory::create('id_ID');
|
||||
|
||||
// First create a patient using the same approach as PatientCreateTest
|
||||
$patientPayload = [
|
||||
"PatientID" => "ORD" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
|
||||
"AlternatePID" => "DMY" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
|
||||
"Prefix" => $faker->title,
|
||||
"NameFirst" => "Order",
|
||||
"NameMiddle" => $faker->firstName,
|
||||
"NameMaiden" => $faker->firstName,
|
||||
"NameLast" => "Test",
|
||||
"Suffix" => "S.Kom",
|
||||
"NameAlias" => $faker->userName,
|
||||
"Sex" => $faker->numberBetween(5, 6),
|
||||
"PlaceOfBirth" => $faker->city,
|
||||
"Birthdate" => "1990-01-01",
|
||||
"ZIP" => $faker->postcode,
|
||||
"Street_1" => $faker->streetAddress,
|
||||
"Street_2" => "RT " . $faker->numberBetween(1, 10) . " RW " . $faker->numberBetween(1, 10),
|
||||
"Street_3" => "Blok " . $faker->numberBetween(1, 20),
|
||||
"City" => $faker->city,
|
||||
"Province" => $faker->state,
|
||||
"EmailAddress1" => "A" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com',
|
||||
"EmailAddress2" => "B" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com',
|
||||
"Phone" => $faker->numerify('08##########'),
|
||||
"MobilePhone" => $faker->numerify('08##########'),
|
||||
"Race" => (string) $faker->numberBetween(175, 205),
|
||||
"Country" => (string) $faker->numberBetween(221, 469),
|
||||
"MaritalStatus" => (string) $faker->numberBetween(8, 15),
|
||||
"Religion" => (string) $faker->numberBetween(206, 212),
|
||||
"Ethnic" => (string) $faker->numberBetween(213, 220),
|
||||
"Citizenship" => "WNI",
|
||||
"DeathIndicator" => (string) $faker->numberBetween(16, 17),
|
||||
"LinkTo" => (string) $faker->numberBetween(2, 3),
|
||||
"Custodian" => 1,
|
||||
"PatIdt" => [
|
||||
"IdentifierType" => "KTP",
|
||||
"Identifier" => $faker->nik() ?? $faker->numerify('################')
|
||||
],
|
||||
"PatAtt" => [
|
||||
[ "Address" => "/api/upload/" . $faker->uuid . ".jpg" ]
|
||||
],
|
||||
"PatCom" => $faker->sentence,
|
||||
];
|
||||
|
||||
if($patientPayload['DeathIndicator'] == '16') {
|
||||
$patientPayload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
|
||||
} else {
|
||||
$patientPayload['DeathDateTime'] = null;
|
||||
}
|
||||
|
||||
$patientResult = $this->withBodyFormat('json')->call('post', 'api/patient', $patientPayload);
|
||||
|
||||
// Check patient creation succeeded
|
||||
$patientResult->assertStatus(201);
|
||||
|
||||
$patientBody = json_decode($patientResult->getBody(), true);
|
||||
$internalPID = $patientBody['data']['InternalPID'] ?? null;
|
||||
|
||||
$this->assertNotNull($internalPID, 'Failed to create test patient. Response: ' . print_r($patientBody, true));
|
||||
|
||||
// Get available tests from testdefsite
|
||||
$testsResult = $this->call('get', 'api/test');
|
||||
$testsBody = json_decode($testsResult->getBody(), true);
|
||||
$availableTests = $testsBody['data'] ?? [];
|
||||
|
||||
// Skip if no tests available
|
||||
if (empty($availableTests)) {
|
||||
$this->markTestSkipped('No tests available in testdefsite table');
|
||||
}
|
||||
|
||||
$testSiteID = $availableTests[0]['TestSiteID'];
|
||||
|
||||
// Create order with tests
|
||||
$payload = [
|
||||
'InternalPID' => $internalPID,
|
||||
'Priority' => 'R',
|
||||
'Tests' => [
|
||||
['TestSiteID' => $testSiteID]
|
||||
]
|
||||
];
|
||||
|
||||
$result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
|
||||
|
||||
// Assertions
|
||||
$result->assertStatus(201);
|
||||
|
||||
$body = json_decode($result->getBody(), true);
|
||||
|
||||
$this->assertEquals('success', $body['status']);
|
||||
$this->assertArrayHasKey('data', $body);
|
||||
$this->assertArrayHasKey('OrderID', $body['data']);
|
||||
$this->assertArrayHasKey('Specimens', $body['data']);
|
||||
$this->assertArrayHasKey('Tests', $body['data']);
|
||||
$this->assertIsArray($body['data']['Specimens']);
|
||||
$this->assertIsArray($body['data']['Tests']);
|
||||
$this->assertNotEmpty($body['data']['Tests'], 'Tests array should not be empty');
|
||||
|
||||
return $body['data']['OrderID'];
|
||||
}
|
||||
|
||||
public function testCreateOrderValidationFailsWithoutInternalPID()
|
||||
{
|
||||
$payload = [
|
||||
'Tests' => [
|
||||
['TestSiteID' => 1]
|
||||
]
|
||||
];
|
||||
|
||||
$result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$body = json_decode($result->getBody(), true);
|
||||
$this->assertIsArray($body);
|
||||
$this->assertArrayHasKey('errors', $body);
|
||||
}
|
||||
|
||||
public function testCreateOrderFailsWithInvalidPatient()
|
||||
{
|
||||
$payload = [
|
||||
'InternalPID' => 999999,
|
||||
'Tests' => [
|
||||
['TestSiteID' => 1]
|
||||
]
|
||||
];
|
||||
|
||||
$result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$body = json_decode($result->getBody(), true);
|
||||
$this->assertIsArray($body);
|
||||
$this->assertArrayHasKey('errors', $body);
|
||||
}
|
||||
|
||||
public function testCreateOrderWithMultipleTests()
|
||||
{
|
||||
$faker = Factory::create('id_ID');
|
||||
|
||||
// First create a patient
|
||||
$patientPayload = [
|
||||
"PatientID" => "ORDM" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000),
|
||||
"NameFirst" => "Multi",
|
||||
"NameLast" => "Test",
|
||||
"Sex" => "2",
|
||||
"Birthdate" => "1985-05-15",
|
||||
"PatIdt" => [
|
||||
"IdentifierType" => "KTP",
|
||||
"Identifier" => $faker->numerify('################')
|
||||
]
|
||||
];
|
||||
|
||||
$patientResult = $this->withBodyFormat('json')->call('post', 'api/patient', $patientPayload);
|
||||
|
||||
$patientBody = json_decode($patientResult->getBody(), true);
|
||||
$internalPID = $patientBody['data']['InternalPID'] ?? null;
|
||||
|
||||
$this->assertNotNull($internalPID, 'Failed to create test patient');
|
||||
|
||||
// Get available tests
|
||||
$testsResult = $this->call('get', 'api/test');
|
||||
$testsBody = json_decode($testsResult->getBody(), true);
|
||||
$availableTests = $testsBody['data'] ?? [];
|
||||
|
||||
if (count($availableTests) < 2) {
|
||||
$this->markTestSkipped('Need at least 2 tests for this test');
|
||||
}
|
||||
|
||||
$testSiteID1 = $availableTests[0]['TestSiteID'];
|
||||
$testSiteID2 = $availableTests[1]['TestSiteID'];
|
||||
|
||||
// Create order with multiple tests
|
||||
$payload = [
|
||||
'InternalPID' => $internalPID,
|
||||
'Priority' => 'S',
|
||||
'Comment' => 'Urgent order for multiple tests',
|
||||
'Tests' => [
|
||||
['TestSiteID' => $testSiteID1],
|
||||
['TestSiteID' => $testSiteID2]
|
||||
]
|
||||
];
|
||||
|
||||
$result = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
|
||||
|
||||
$result->assertStatus(201);
|
||||
|
||||
$body = json_decode($result->getBody(), true);
|
||||
|
||||
$this->assertEquals('success', $body['status']);
|
||||
$this->assertGreaterThanOrEqual(1, count($body['data']['Specimens']), 'Should have at least one specimen');
|
||||
$this->assertGreaterThanOrEqual(2, count($body['data']['Tests']), 'Should have at least two tests');
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user