find($refNumID); if (!$ref) { return null; } // Get patient info from order $orderModel = new OrderTestModel(); $order = $orderModel->find($orderID); if (!$order) { return null; } $patientModel = new PatientModel(); $patient = $patientModel->find($order['InternalPID']); if (!$patient) { return null; } // Check if patient matches criteria (sex) if (!empty($ref['Sex']) && $ref['Sex'] !== 'ALL') { if ($patient['Sex'] !== $ref['Sex']) { return null; } } // Check age criteria if ($ref['AgeStart'] !== null || $ref['AgeEnd'] !== null) { $birthdate = new \DateTime($patient['Birthdate']); $today = new \DateTime(); $age = $birthdate->diff($today)->y; if ($ref['AgeStart'] !== null && $age < $ref['AgeStart']) { return null; } if ($ref['AgeEnd'] !== null && $age > $ref['AgeEnd']) { return null; } } $value = floatval($resultValue); $low = floatval($ref['Low']); $high = floatval($ref['High']); // Check low if ($ref['LowSign'] === '<=' && $value <= $low) { return 'L'; } if ($ref['LowSign'] === '<' && $value < $low) { return 'L'; } // Check high if ($ref['HighSign'] === '>=' && $value >= $high) { return 'H'; } if ($ref['HighSign'] === '>' && $value > $high) { return 'H'; } return null; // Normal } /** * Get all results for an order with test names and reference ranges * * @param int $orderID * @return array */ public function getByOrder(int $orderID): array { $builder = $this->db->table('patres pr'); $builder->select(' pr.ResultID, pr.OrderID, pr.TestSiteID, pr.TestSiteCode, pr.Result, pr.ResultDateTime, pr.RefNumID, pr.RefTxtID, pr.CreateDate, tds.TestSiteName, tds.Unit1, tds.Unit2, rn.Low, rn.High, rn.LowSign, rn.HighSign, rn.Display as RefDisplay '); $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left'); $builder->join('refnum rn', 'rn.RefNumID = pr.RefNumID', 'left'); $builder->where('pr.OrderID', $orderID); $builder->where('pr.DelDate', null); $builder->orderBy('tds.SeqScr', 'ASC'); return $builder->get()->getResultArray(); } /** * Get cumulative patient results across all orders * * @param int $internalPID * @return array */ public function getByPatient(int $internalPID): array { $builder = $this->db->table('patres pr'); $builder->select(' pr.ResultID, pr.OrderID, pr.TestSiteID, pr.TestSiteCode, pr.Result, pr.ResultDateTime, pr.RefNumID, tds.TestSiteName, tds.Unit1, tds.Unit2, ot.OrderID as OrderNumber, ot.TrnDate as OrderDate '); $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left'); $builder->join('ordertest ot', 'ot.InternalOID = pr.OrderID', 'left'); $builder->where('ot.InternalPID', $internalPID); $builder->where('pr.DelDate', null); $builder->orderBy('pr.ResultDateTime', 'DESC'); return $builder->get()->getResultArray(); } /** * Update result with validation and return flag (not stored) * * @param int $resultID * @param array $data * @return array ['success' => bool, 'flag' => string|null, 'message' => string] */ public function updateWithValidation(int $resultID, array $data): array { $this->db->transStart(); try { $result = $this->find($resultID); if (!$result) { throw new \Exception('Result not found'); } $flag = null; // If result value is being updated, validate it if (isset($data['Result']) && !empty($data['Result'])) { $refNumID = $data['RefNumID'] ?? $result['RefNumID']; if ($refNumID) { $flag = $this->validateAndFlag($data['Result'], $refNumID, $result['OrderID']); } } // Set update timestamp $data['StartDate'] = date('Y-m-d H:i:s'); $updated = $this->update($resultID, $data); if (!$updated) { throw new \Exception('Failed to update result'); } $this->db->transComplete(); // Fire result_updated rules (non-blocking) try { $fresh = $this->find($resultID); if (is_array($fresh) && !empty($fresh['OrderID']) && !empty($fresh['TestSiteID'])) { $orderModel = new \App\Models\OrderTest\OrderTestModel(); $order = $orderModel->find((int) $fresh['OrderID']); $patient = null; $age = null; if (is_array($order) && !empty($order['InternalPID'])) { $patientModel = new \App\Models\Patient\PatientModel(); $patient = $patientModel->find((int) $order['InternalPID']); 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; } } } $engine = new \App\Services\RuleEngineService(); $engine->run('result_updated', [ 'order' => [ 'InternalOID' => (int) ($fresh['OrderID'] ?? 0), 'Priority' => is_array($order) ? ($order['Priority'] ?? null) : null, 'TestSiteID' => (int) ($fresh['TestSiteID'] ?? 0), ], 'patient' => [ 'Sex' => is_array($patient) ? ($patient['Sex'] ?? null) : null, ], 'age' => $age, 'testSiteID' => (int) ($fresh['TestSiteID'] ?? 0), 'result' => $fresh, ]); } } catch (\Throwable $e) { log_message('error', 'PatResultModel::updateWithValidation rule engine error: ' . $e->getMessage()); } return [ 'success' => true, 'flag' => $flag, 'message' => 'Result updated successfully' ]; } catch (\Exception $e) { $this->db->transRollback(); return [ 'success' => false, 'flag' => null, 'message' => $e->getMessage() ]; } } /** * Get single result with related data * * @param int $resultID * @return array|null */ public function getWithRelations(int $resultID): ?array { $builder = $this->db->table('patres pr'); $builder->select(' pr.*, tds.TestSiteName, tds.TestSiteCode, tds.Unit1, tds.Unit2, rn.Low, rn.High, rn.LowSign, rn.HighSign, rn.Display as RefDisplay, ot.OrderID as OrderNumber, ot.InternalPID '); $builder->join('testdefsite tds', 'tds.TestSiteID = pr.TestSiteID', 'left'); $builder->join('refnum rn', 'rn.RefNumID = pr.RefNumID', 'left'); $builder->join('ordertest ot', 'ot.InternalOID = pr.OrderID', 'left'); $builder->where('pr.ResultID', $resultID); $builder->where('pr.DelDate', null); $result = $builder->get()->getRowArray(); return $result ?: null; } /** * Soft delete result * * @param int $resultID * @return bool */ public function softDelete(int $resultID): bool { return $this->update($resultID, ['DelDate' => date('Y-m-d H:i:s')]); } }