clqms-be/app/Models/OrderTest/OrderTestModel.php
mahdahar 40ecb4e6e8 feat(api): transition to headless architecture and enhance order management
This commit marks a significant architectural shift, transitioning the CLQMS backend to a fully headless REST API. All view-related components have been removed to focus solely on providing a robust, stateless API for clinical laboratory workflows.

### Architectural Changes

- **Headless API Transition:**
    - Removed all view files (`app/Views/v2`), associated page controllers (`PagesController`), and routes (`Routes.php`). The application no longer serves a front-end UI.
    - The root endpoint (`/`) now returns a simple "Backend Running" status message.

- **Developer Tooling & Guidance:**
    - Replaced `CLAUDE.md` with `GEMINI.md` to provide updated context and instructional guidelines for Gemini agents.
    - Updated `.serena/project.yml` with project configuration.

### Feature Enhancements

- **Advanced Order Management (`OrderTestModel`):**
    - **Test Expansion:** The `createOrder` process now automatically expands `GROUP` (panel) tests into their individual components and recursively includes all parameter dependencies for `CALC` (calculated) tests.
    - **Order Comments:** Added support for attaching comments to an order via the `ordercom` table.
    - **Status Tracking:** Order status updates are now correctly recorded in the `orderstatus` table.
    - **Schema Alignment:** Switched from `OrderID` to `InternalOID` as the primary key for internal operations.

- **Reference Range Refactor (`TestsController`):**
    - Simplified reference range logic by consolidating `refthold` and `refvset` into the main `refnum` and `reftxt` tables.
    - Standardized `RefType` handling to support `NMRC`, `TEXT`, `THOLD`, and `VSET` codes from the `reference_type` ValueSet.

### Other Changes

- **Documentation:**
    - `PRD.md`, `README.md`, and `TODO.md` were updated to reflect the headless architecture, refined scope, and current project priorities.
- **Database:**
    - Removed obsolete `RefTHoldID` and `RefVSetID` columns from the `patres` table migration.
- **Testing:**
    - Added new feature tests for `ContactController`, `OrganizationController`, and `TestsController`.
2026-01-31 09:27:32 +07:00

183 lines
6.2 KiB
PHP

<?php
namespace App\Models\OrderTest;
use App\Models\BaseModel;
class OrderTestModel extends BaseModel {
protected $table = 'ordertest';
protected $primaryKey = 'InternalOID';
protected $useAutoIncrement = true;
protected $allowedFields = [
'InternalOID',
'OrderID',
'PlacerID',
'InternalPID',
'SiteID',
'PVADTID',
'ReqApp',
'Priority',
'TrnDate',
'EffDate',
'CreateDate',
'EndDate',
'ArchiveDate',
'DelDate'
];
public function generateOrderID(string $siteCode = '00'): string {
$date = new \DateTime();
$year = $date->format('y');
$month = $date->format('m');
$day = $date->format('d');
$counter = $this->db->table('counter')
->where('CounterName', 'ORDER')
->get()
->getRow();
if (!$counter) {
$this->db->table('counter')->insert([
'CounterName' => 'ORDER',
'CounterValue' => 1
]);
$seq = 1;
} else {
$seq = $counter->CounterValue + 1;
$this->db->table('counter')
->where('CounterName', 'ORDER')
->update(['CounterValue' => $seq]);
}
$seqStr = str_pad($seq, 5, '0', STR_PAD_LEFT);
return $siteCode . $year . $month . $day . $seqStr;
}
public function createOrder(array $data): string {
$orderID = $data['OrderID'] ?? $this->generateOrderID($data['SiteCode'] ?? '00');
$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')
];
$internalOID = $this->insert($orderData);
// 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
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();
foreach ($data['Tests'] as $test) {
$testSiteID = $test['TestSiteID'] ?? $test['TestID'] ?? null;
if ($testSiteID) {
$this->expandTest($testSiteID, $testToOrder, $testModel, $grpModel, $calModel);
}
}
// Insert unique tests into patres
if (!empty($testToOrder)) {
$resModel = new \App\Models\PatResultModel();
foreach ($testToOrder as $tid => $tinfo) {
$resModel->insert([
'OrderID' => $internalOID,
'TestSiteID' => $tid,
'TestSiteCode' => $tinfo['TestSiteCode'],
'SID' => $orderID,
'SampleID' => $orderID,
'ResultDateTime' => $orderData['TrnDate'],
'CreateDate' => date('Y-m-d H:i:s')
]);
}
}
}
return $orderID;
}
private function expandTest($testSiteID, &$testToOrder, $testModel, $grpModel, $calModel) {
if (isset($testToOrder[$testSiteID])) return;
$testInfo = $testModel->find($testSiteID);
if (!$testInfo) return;
$testToOrder[$testSiteID] = [
'TestSiteCode' => $testInfo['TestSiteCode'],
'TestType' => $testInfo['TestType']
];
// Handle Group Expansion
if ($testInfo['TestType'] === 'GROUP') {
$members = $grpModel->where('TestSiteID', $testSiteID)->findAll();
foreach ($members as $m) {
$this->expandTest($m['Member'], $testToOrder, $testModel, $grpModel, $calModel);
}
}
// Handle Calculated Test Dependencies
if ($testInfo['TestType'] === 'CALC') {
$calDetail = $calModel->where('TestSiteID', $testSiteID)->first();
if ($calDetail && !empty($calDetail['FormulaInput'])) {
$inputs = explode(',', $calDetail['FormulaInput']);
foreach ($inputs as $inputCode) {
$inputCode = trim($inputCode);
$inputTest = $testModel->where('TestSiteCode', $inputCode)->first();
if ($inputTest) {
$this->expandTest($inputTest['TestSiteID'], $testToOrder, $testModel, $grpModel, $calModel);
}
}
}
}
}
public function getOrder(string $orderID): ?array {
return $this->select('*')
->where('OrderID', $orderID)
->where('DelDate', null)
->get()
->getRowArray();
}
public function getOrdersByPatient(int $internalPID): array {
return $this->select('*')
->where('InternalPID', $internalPID)
->where('DelDate', null)
->orderBy('TrnDate', 'DESC')
->get()
->getResultArray();
}
public function updateStatus(string $orderID, string $status): bool {
$order = $this->getOrder($orderID);
if (!$order) return false;
return (bool)$this->db->table('orderstatus')->insert([
'InternalOID' => $order['InternalOID'],
'OrderStatus' => $status,
'CreateDate' => date('Y-m-d H:i:s')
]);
}
public function softDelete(string $orderID): bool {
return $this->where('OrderID', $orderID)->update(null, ['DelDate' => date('Y-m-d H:i:s')]);
}
}