931 lines
37 KiB
PHP
931 lines
37 KiB
PHP
<?php
|
|
class Workorder extends MY_Controller
|
|
{
|
|
var $db_onedev;
|
|
private $pacs_host = "http://152.42.173.210";
|
|
private $ohif_port = "3000";
|
|
private $pacs_api_url;
|
|
private $pacs_api_import;
|
|
private $pacs_api_progress;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->db_onedev = $this->load->database("onedev", true);
|
|
$this->pacs_api_url = "{$this->pacs_host}/ApiPacs.php/sendOrder";
|
|
$this->pacs_api_import = "{$this->pacs_host}/ApiResultPacs.php";
|
|
$this->pacs_api_progress = "{$this->pacs_host}/ApiProgressPacs.php";
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
header('Content-Type: application/json');
|
|
$this->sys_ok([
|
|
"message" => "API for send Workorder to PACS is working",
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Main entry point to process workorder
|
|
*/
|
|
public function order()
|
|
{
|
|
try {
|
|
if (!$this->isLogin) {
|
|
$this->sys_error("Invalid Token");
|
|
exit;
|
|
}
|
|
|
|
$prm = $this->sys_input;
|
|
|
|
// TODO: Refactor `order()` agar bisa menerima beberapa test lalu kirim beberapa order sekaligus
|
|
|
|
$this->validate_input($prm);
|
|
|
|
$this->db_onedev->trans_start();
|
|
|
|
// Query additional data and compose JSON
|
|
$workorderData = $this->query_data($prm);
|
|
|
|
// Check existing workorder in t_mwl_order
|
|
$isSent = $this->check_existing_workorder($workorderData);
|
|
if ($isSent) {
|
|
throw new Exception("Workorder already sent to PACS");
|
|
}
|
|
// Send data to PACS system
|
|
$response = $this->send_to_pacs($workorderData);
|
|
|
|
$this->store($workorderData, $response);
|
|
|
|
$this->db_onedev->trans_complete();
|
|
|
|
$this->sys_ok([
|
|
"orderData" => $workorderData['orderData'],
|
|
"sendToPacs" => $workorderData['apiPacsJson'],
|
|
"response" => $response,
|
|
]);
|
|
} catch (Exception $e) {
|
|
$this->db_onedev->trans_rollback();
|
|
$this->sys_error($e->getMessage());
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate required input parameters
|
|
*/
|
|
private function validate_input($prm)
|
|
{
|
|
$required_fields = [
|
|
'T_OrderHeaderID',
|
|
'stationId',
|
|
'test_code'
|
|
];
|
|
|
|
foreach ($required_fields as $field) {
|
|
if (!isset($prm[$field]) || empty($prm[$field])) {
|
|
throw new Exception("Missing required parameter: $field");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query for data completions and compose JSON
|
|
*/
|
|
private function query_data($prm)
|
|
{
|
|
$orderHeaderId = $prm['T_OrderHeaderID'];
|
|
$stationId = $prm['stationId'];
|
|
$procedureCode = $prm['test_code'];
|
|
|
|
$sql = "SELECT
|
|
T_OrderHeaderLabNumber, T_OrderHeaderID, T_OrderHeaderSenderM_DoctorID,
|
|
T_OrderHeaderPjM_DoctorID, M_PatientNoReg, M_PatientName, M_PatientDOB,
|
|
M_PatientM_SexID, M_SexCode, T_OrderHeaderLabNumberExt,
|
|
CONCAT(snd.M_DoctorPrefix, snd.M_DoctorPrefix2,' ', snd.M_DoctorName,' ',snd.M_DoctorSufix, snd.M_DoctorSufix2, snd.M_DoctorSufix3) AS SenderDoctorName,
|
|
CONCAT(pj.M_DoctorPrefix, pj.M_DoctorPrefix2,' ', pj.M_DoctorName,' ',pj.M_DoctorSufix, pj.M_DoctorSufix2, pj.M_DoctorSufix3) AS PjDoctorName
|
|
FROM t_orderheader
|
|
JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID
|
|
JOIN m_sex ON M_PatientM_SexID = M_SexID
|
|
JOIN m_doctor AS snd ON T_OrderHeaderSenderM_DoctorID = snd.M_DoctorID
|
|
JOIN m_doctor AS pj ON T_OrderHeaderPjM_DoctorID = pj.M_DoctorID
|
|
WHERE T_OrderHeaderID = ?
|
|
AND T_OrderHeaderIsActive = 'Y'";
|
|
|
|
$query = $this->db_onedev->query($sql, [$orderHeaderId]);
|
|
if (!$query) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Error query order data" . json_encode($this->db_onedev->error()));
|
|
}
|
|
$rst = $query->row();
|
|
$nolab = $rst->T_OrderHeaderLabNumber;
|
|
$patientNoReg = $rst->M_PatientNoReg;
|
|
$patientName = $rst->M_PatientName;
|
|
$patientBod = $rst->M_PatientDOB;
|
|
$genderCode = $rst->M_SexCode;
|
|
$doctorSenderId = $rst->T_OrderHeaderSenderM_DoctorID;
|
|
$doctorSenderName = $rst->SenderDoctorName;
|
|
$doctorPjId = $rst->T_OrderHeaderPjM_DoctorID;
|
|
$doctorPjName = $rst->PjDoctorName;
|
|
|
|
$tOrderHeaderLabNumberExt = $rst->T_OrderHeaderLabNumberExt;
|
|
|
|
// Set common values
|
|
$admissionId = $orderHeaderId . "." . $procedureCode;
|
|
$nowDttm = date("Y-m-d\TH:i:sP"); // now dttm in ISO8601 format
|
|
$admissionDttm = $orderDttm = $nowDttm;
|
|
|
|
// Get station details
|
|
$sqlStation = "SELECT * FROM t_samplestation WHERE T_SampleStationID = ? LIMIT 1";
|
|
$queryStation = $this->db_onedev->query($sqlStation, intval($stationId));
|
|
if (!$queryStation) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Error query t_samplestation" . json_encode($this->db_onedev->error()));
|
|
}
|
|
$station = $queryStation->result();
|
|
$stationCode = $station[0]->T_SampleStationCode;
|
|
$stationName = $station[0]->T_SampleStationName;
|
|
|
|
// Get t_orderdetailId, So_resultentryId, natgorupID
|
|
$sqlCombined = "SELECT T_OrderDetailID, So_ResultEntryID, T_TestNat_GroupID, T_TestName, T_TestNat_TestID
|
|
FROM t_orderdetail
|
|
JOIN so_resultentry ON So_ResultEntryT_OrderDetailID = T_OrderDetailID
|
|
JOIN t_test ON T_OrderDetailT_TestID = T_TestID
|
|
WHERE T_OrderDetailT_OrderHeaderID = ?
|
|
AND T_OrderDetailT_TestCode = ?
|
|
AND T_OrderDetailT_TestIsResult = 'Y'
|
|
AND T_TestIsResult = 'Y'
|
|
AND T_OrderDetailIsActive = 'Y'
|
|
AND So_ResultEntryIsActive = 'Y'";
|
|
$queryCombined = $this->db_onedev->query($sqlCombined, [$orderHeaderId, $procedureCode]);
|
|
if (!$queryCombined) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Error query orderdetail, resultentryid, dan natgroupid" . json_encode($this->db_onedev->error()));
|
|
}
|
|
$rst = $queryCombined->row();
|
|
|
|
$orderDetailId = $rst->T_OrderDetailID;
|
|
$soResultEntryId = $rst->So_ResultEntryID;
|
|
$natGroupId = $rst->T_TestNat_GroupID;
|
|
$procedureName = $rst->T_TestName;
|
|
|
|
// Jika So_ResultEntryID tidak ada, maka throw error karena pasien belum di call dan proses
|
|
if (empty($soResultEntryId)) {
|
|
throw new Exception("So_ResultEntryID not found. Kayaknya pasien belum di panggil dan proses nih...");
|
|
}
|
|
|
|
// Determine modality code
|
|
$modalityCode = $this->modality_mapping($natGroupId, $rst->T_TestNat_TestID);
|
|
|
|
// Encounter 3 Digit AccessinNumber (Reset ke 1 tiap hari)
|
|
$sqlCounter = "SELECT fn_numbering('PACS') as encounter";
|
|
$queryCounter = $this->db_onedev->query($sqlCounter);
|
|
$encounter = $queryCounter->row()->encounter;
|
|
$accessionNo = $modalityCode . $tOrderHeaderLabNumberExt . '.' . $encounter;
|
|
|
|
if (empty($modalityCode)) {
|
|
throw new Exception("Modality code not found for NatGroupID: $natGroupId");
|
|
}
|
|
|
|
// Generate required IDs
|
|
$studyInstanceUid = "1.2.826.0.1.3680043.0.1252.1." . date("Ymd.His") . "." . $orderDetailId . "." . $natGroupId;
|
|
$trxLayananId = $requestedProcId = $placerOrder = $fillerOrder = $orderDetailId;
|
|
|
|
// Compose JSON
|
|
$apiPacsJson = [
|
|
"message_seq_id" => "2006 - 1010",
|
|
"scheduled_proc_step_id" => "SPSID0001",
|
|
"departemen_id" => "BISONE",
|
|
"nama_departemen" => "BISONE DEPT",
|
|
|
|
"scheduled_station_ae" => $stationCode,
|
|
"scheduled_station_name" => $stationName,
|
|
"patient_mrn" => $patientNoReg,
|
|
"patient_nm" => $patientName,
|
|
"patient_birth_dt" => $patientBod,
|
|
"patient_gender" => $genderCode,
|
|
|
|
"admission_dttm" => $admissionDttm,
|
|
"admission_id" => $admissionId,
|
|
"procedure_cd" => $procedureCode,
|
|
"procedure_nm" => $procedureName,
|
|
"order_dttm" => $orderDttm,
|
|
|
|
"referring_physician_id" => $doctorSenderId,
|
|
"referring_physician_nm" => $doctorSenderName,
|
|
"ordering_physician_id" => $doctorPjId,
|
|
"ordering_physician_nm" => $doctorPjName,
|
|
|
|
"trx_layanan_id" => $trxLayananId,
|
|
"study_instance_uid" => $studyInstanceUid,
|
|
"modality_cd" => $modalityCode,
|
|
"accession_no" => $accessionNo,
|
|
|
|
"requested_proc_id" => $requestedProcId,
|
|
"placer_order_id" => $placerOrder,
|
|
"filler_order_id" => $fillerOrder,
|
|
];
|
|
|
|
$jsonString = json_encode($apiPacsJson, JSON_PRETTY_PRINT);
|
|
|
|
return [
|
|
'apiPacsJson' => $apiPacsJson,
|
|
'apiPacsjsonString' => $jsonString,
|
|
'orderData' => [
|
|
'T_OrderHeaderID' => $orderHeaderId,
|
|
'T_OrderDetailID' => $orderDetailId,
|
|
'T_OrderHeaderLabNumber' => $nolab,
|
|
'So_ResultEntryID' => $soResultEntryId,
|
|
'T_SampleStationID' => $stationId,
|
|
'M_PatientNoReg' => $patientNoReg,
|
|
'T_TestCode' => $procedureCode,
|
|
'T_TestName' => $procedureName,
|
|
'T_TestNat_GroupID' => $natGroupId,
|
|
'PjM_DoctorID' => $doctorPjId,
|
|
'PjM_DoctorName' => $doctorPjName,
|
|
'SenderM_DoctorID' => $doctorSenderId,
|
|
'SenderM_DoctorName' => $doctorSenderName,
|
|
'TrxLayananID' => $trxLayananId,
|
|
'AccessionNo' => $accessionNo,
|
|
'StudyIUID' => $studyInstanceUid,
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Mapping modality berdasarkan test_code dan nat_testId
|
|
*
|
|
* @return string $modality_code
|
|
*/
|
|
// TODO: tambahkan Nat_TestID untuk map Modality
|
|
private function modality_mapping($natGroupId, $natTestId)
|
|
{
|
|
// Query dulu ke m_modality_map
|
|
// Kalau ada maka pakai modality itu
|
|
// Kalau tidak ada, maka pakai natgroupid
|
|
$modality_code = "";
|
|
|
|
$sql = "SELECT * FROM m_modality_map
|
|
WHERE M_ModalityMapNat_GroupID = ?
|
|
AND M_ModalityMapNat_TestID = ?
|
|
AND M_ModalityMapIsActive = 'Y'";
|
|
$query = $this->db_onedev->query($sql, [$natGroupId, $natTestId]);
|
|
$rst = $query->row();
|
|
|
|
if ($rst) {
|
|
$modality_code = $rst->M_ModalityMapM_ModalityCode;
|
|
return $modality_code;
|
|
} else {
|
|
switch ($natGroupId) {
|
|
case 2:
|
|
$modality_code = "EEG";
|
|
break;
|
|
case 3:
|
|
$modality_code = "CR";
|
|
break;
|
|
default:
|
|
throw new Exception("Invalid NatGroupID: $natGroupId");
|
|
}
|
|
return $modality_code;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if workorder has already been sent
|
|
*/
|
|
private function check_existing_workorder($queryData)
|
|
{
|
|
$sql = "SELECT * FROM t_mwl_order
|
|
WHERE T_MwlOrder_AccessionNo = ?
|
|
AND T_MwlOrder_TrxLayananID = ?
|
|
AND T_MwlOrderIsActive = 'Y'";
|
|
|
|
$params = [
|
|
$queryData['apiPacsJson']['accession_no'],
|
|
$queryData['apiPacsJson']['trx_layanan_id']
|
|
];
|
|
|
|
$query = $this->db_onedev->query($sql, $params);
|
|
|
|
if (!$query) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Error query when check existing workorder" . json_encode($this->db_onedev->error()));
|
|
}
|
|
return $query->num_rows() > 0;
|
|
}
|
|
|
|
/**
|
|
* Send data to PACS system via HTTP POST
|
|
*/
|
|
private function send_to_pacs($data)
|
|
{
|
|
$ch = curl_init($this->pacs_api_url);
|
|
curl_setopt($ch, CURLOPT_POST, 1);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data['apiPacsjsonString']);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/json',
|
|
'Content-Length: ' . strlen($data['apiPacsjsonString'])
|
|
]);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Connection timeout in seconds
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Total timeout in seconds
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
// Process response
|
|
$responseData = json_decode($response, true);
|
|
if ($httpCode != 200 || !$responseData || isset($responseData['error'])) {
|
|
$errorMsg = isset($responseData['error']) ? $responseData['error'] : 'Unknown error';
|
|
throw new Exception("PACS server returned error: $errorMsg (HTTP code: $httpCode)");
|
|
}
|
|
|
|
return $responseData;
|
|
}
|
|
|
|
/**
|
|
* Store transaction data and response after successful API call
|
|
*/
|
|
private function store($data, $response)
|
|
{
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
$orderData = $data['orderData'];
|
|
|
|
$insertData = [
|
|
'T_MwlOrderT_OrderHeaderID' => $orderData['T_OrderHeaderID'],
|
|
'T_MwlOrderT_OrderDetailID' => $orderData['T_OrderDetailID'],
|
|
'T_MwlOrderT_OrderHeaderLabNumber' => $orderData['T_OrderHeaderLabNumber'],
|
|
'T_MwlOrderSo_ResultEntryID' => $orderData['So_ResultEntryID'],
|
|
'T_MwlOrderT_SampleStationID' => $orderData['T_SampleStationID'],
|
|
'T_MwlOrderM_PatientNoReg' => $orderData['M_PatientNoReg'],
|
|
'T_MwlOrderT_TestCode' => $orderData['T_TestCode'],
|
|
|
|
'T_MwlOrderPjM_DoctorID' => $orderData['PjM_DoctorID'],
|
|
'T_MwlOrderSenderM_DoctorID' => $orderData['SenderM_DoctorID'],
|
|
'T_MwlOrder_TrxLayananID' => $orderData['TrxLayananID'],
|
|
'T_MwlOrder_AccessionNo' => $orderData['AccessionNo'],
|
|
'T_MwlOrder_StudyIUID' => $orderData['StudyIUID'],
|
|
|
|
'T_MwlOrder_ScheduledProcStepID' => "SPSID0001",
|
|
'T_MwlOrder_PacsResponse' => json_encode($response),
|
|
'T_MwlOrder_Progress' => '0',
|
|
'T_MwlOrderIsActive' => 'Y',
|
|
'T_MwlOrderCreated' => $now,
|
|
'T_MwlOrderLastUpdated' => $now
|
|
];
|
|
|
|
$this->db_onedev->insert('t_mwl_order', $insertData);
|
|
|
|
if ($this->db_onedev->affected_rows() <= 0) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Failed to store workorder data" . json_encode($this->db_onedev->error()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API untuk OHIF lihat expertise
|
|
* @param $studyIUID
|
|
* (no auth)
|
|
* */
|
|
// TODO: Cek apakah 1 order radio (1 nomorlab) bisa lebih dari 1 test yang menyebabkan jadi ada studyIUID yang sama?
|
|
public function get_expertise($studyIUID)
|
|
{
|
|
try {
|
|
if (empty($studyIUID)) {
|
|
throw new Exception("Missing required parameter: studyIUID");
|
|
}
|
|
|
|
$sql = "SELECT So_ResultEntryDetailSo_TemplateDetailID,
|
|
So_ResultEntryDetailSo_TemplateDetailName,
|
|
So_ResultEntryDetailSo_TemplateDetailPriority,
|
|
So_ResultEntryDetailFlagPrint,
|
|
So_ResultEntryDetailResult,
|
|
So_ResultEntryLastUpdated,
|
|
T_MwlOrderSenderM_DoctorID,
|
|
T_MwlOrderPjM_DoctorID
|
|
FROM so_resultentrydetail
|
|
JOIN t_mwl_order ON So_ResultEntryDetailSo_ResultEntryID = T_MwlOrderSo_ResultEntryID
|
|
JOIN so_resultentry ON So_ResultEntryID = T_MwlOrderSo_ResultEntryID
|
|
WHERE T_MwlOrder_StudyIUID = ?
|
|
AND So_ResultEntryDetailIsActive = 'Y'
|
|
-- AND So_ResultEntryValidation1 = 'Y' Kalau mau ambil yang valid aja
|
|
-- AND So_ResultEntryValidation2 = 'Y'
|
|
";
|
|
$query = $this->db_onedev->query($sql, [$studyIUID]);
|
|
if (!$query) {
|
|
throw new Exception("Error retrieving expertise data: Database error" . json_encode($this->db_onedev->error()));
|
|
}
|
|
$exp = $query->result();
|
|
if (!$exp) {
|
|
throw new Exception("No expertise found for StudyIUID: $studyIUID");
|
|
}
|
|
|
|
$expContent = [];
|
|
|
|
// Sort by priority
|
|
usort($exp, function ($a, $b) {
|
|
return $a->So_ResultEntryDetailSo_TemplateDetailPriority - $b->So_ResultEntryDetailSo_TemplateDetailPriority;
|
|
});
|
|
|
|
foreach ($exp as $item) {
|
|
$templateName = $item->So_ResultEntryDetailSo_TemplateDetailName;
|
|
$resultValue = $item->So_ResultEntryDetailResult;
|
|
$expContent[$templateName] = $resultValue;
|
|
}
|
|
|
|
// Query doctor name dan so_resultentryLastUpdated
|
|
$sql = "SELECT CONCAT(snd.M_DoctorPrefix, snd.M_DoctorPrefix2,' ', snd.M_DoctorName,' ',snd.M_DoctorSufix, snd.M_DoctorSufix2, snd.M_DoctorSufix3) AS SenderDoctorName,
|
|
CONCAT(pj.M_DoctorPrefix, pj.M_DoctorPrefix2,' ', pj.M_DoctorName,' ',pj.M_DoctorSufix, pj.M_DoctorSufix2, pj.M_DoctorSufix3) AS PjDoctorName,
|
|
So_ResultEntryLastUpdated
|
|
FROM so_resultentry
|
|
JOIN t_mwl_order ON So_ResultEntryID = T_MwlOrderSo_ResultEntryID
|
|
JOIN m_doctor AS snd ON T_MwlOrderSenderM_DoctorID = snd.M_DoctorID
|
|
JOIN m_doctor AS pj ON T_MwlOrderPjM_DoctorID = pj.M_DoctorID
|
|
WHERE T_MwlOrder_StudyIUID = ?
|
|
AND So_ResultEntryIsActive = 'Y'";
|
|
|
|
$query = $this->db_onedev->query($sql, [$studyIUID]);
|
|
if (!$query) {
|
|
throw new Exception("Error query get_expertise" . json_encode($this->db_onedev->error()));
|
|
}
|
|
$rst = $query->row();
|
|
if (!$rst) {
|
|
throw new Exception("No doctor found for StudyIUID: $studyIUID");
|
|
}
|
|
$expTime = $rst->So_ResultEntryLastUpdated;
|
|
$senderDoctorName = $rst->SenderDoctorName;
|
|
$pjDcotorName = $rst->PjDoctorName;
|
|
|
|
$result = [
|
|
'senderDoctorName' => $senderDoctorName,
|
|
'pjDoctorName' => $pjDcotorName,
|
|
'expertise' => $expContent,
|
|
'expTime' => $expTime
|
|
];
|
|
|
|
$this->sys_ok($result);
|
|
} catch (Exception $e) {
|
|
$this->sys_error($e->getMessage());
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Untuk Video PoC saja biar Cepat
|
|
* */
|
|
public function get_dummy_expertise()
|
|
{
|
|
try {
|
|
// Get accession number from request
|
|
$accessionNo = $this->input->get('accessionNo');
|
|
|
|
if (empty($accessionNo)) {
|
|
throw new Exception("Missing required parameter: accessionNo");
|
|
}
|
|
|
|
// Create variant based on the last digit of the accession number
|
|
$lastChar = substr($accessionNo, -1);
|
|
$variant = intval($lastChar) % 3;
|
|
|
|
// Prepare doctor names
|
|
$senderDoctorNames = [
|
|
'dr. Agus Sulistyo, Sp.PD',
|
|
'dr. Siti Nurfadilah, Sp.JP',
|
|
'dr. Bambang Prakoso, Sp.OG'
|
|
];
|
|
|
|
$pjDoctorNames = [
|
|
'dr. Bambang Sutejo, Sp.Rad',
|
|
'dr. Ratih Wijayanti, Sp.Rad',
|
|
'dr. Hendra Kusuma, Sp.Rad'
|
|
];
|
|
|
|
// Create timestamp with some randomization
|
|
$expTime = date('Y-m-d H:i:s', time() - rand(3600, 86400 * 14));
|
|
|
|
// Prepare expertise content based on variant
|
|
$expertiseContent = [];
|
|
|
|
switch ($variant) {
|
|
case 0:
|
|
$expertiseContent = [
|
|
'Indikasi' => 'Nyeri dada kanan.',
|
|
'Teknik' => 'PA dan Lateral Thorax.',
|
|
'Deskripsi' => "Jantung tampak normal batas dan ukurannya\nPulmo kanan dan kiri aerasi baik\nTampak infiltrat minimal di apex paru kanan\nHilus kanan dan kiri normal\nSinus costophrenicus kanan dan kiri tajam\nDiafragma kanan dan kiri normal",
|
|
'Kesan' => 'Infiltrat minimal di apex paru kanan, kemungkinan TB.'
|
|
];
|
|
break;
|
|
case 1:
|
|
$expertiseContent = [
|
|
'Indikasi' => 'Sesak nafas dan batuk berdahak.',
|
|
'Teknik' => 'PA dan Lateral Thorax.',
|
|
'Deskripsi' => "Jantung tampak membesar dengan CTR 60%\nTampak kongesti vaskular di kedua lapang paru\nPulmo kanan dan kiri aerasi cukup\nHilus kanan dan kiri prominen\nSinus costophrenicus kanan dan kiri tertutup\nDiafragma kanan dan kiri normal",
|
|
'Kesan' => 'Kardiomegali dengan tanda kongesti paru, sesuai dengan gagal jantung.'
|
|
];
|
|
break;
|
|
case 2:
|
|
$expertiseContent = [
|
|
'Indikasi' => 'Kontrol pasca operasi fraktur femur.',
|
|
'Teknik' => 'AP dan Lateral Femur Kanan.',
|
|
'Deskripsi' => "Tampak fraktur pada shaft femur kanan bagian 1/3 tengah\nTampak pemasangan fixasi interna berupa plate and screw\nPosisi plate and screw baik\nAlignment tulang femur baik\nTampak pembentukan kalus tulang\nTidak tampak tanda komplikasi",
|
|
'Kesan' => 'Status post ORIF fraktur shaft femur kanan dengan pembentukan kalus tulang, posisi implant baik.'
|
|
];
|
|
break;
|
|
}
|
|
|
|
// Create response structure matching the actual get_expertise() response
|
|
$result = [
|
|
'senderDoctorName' => $senderDoctorNames[$variant],
|
|
'pjDoctorName' => $pjDoctorNames[$variant],
|
|
'expertise' => $expertiseContent,
|
|
'expTime' => $expTime
|
|
];
|
|
|
|
// Return response in the same format as the main method
|
|
$this->sys_ok($result);
|
|
exit;
|
|
} catch (Exception $e) {
|
|
$this->sys_error($e->getMessage());
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function period_update_mwl_progress()
|
|
{
|
|
try {
|
|
// Get status updates from POST payload instead of querying by date range
|
|
$statusUpdates = $this->sys_input;
|
|
|
|
if (empty($statusUpdates) || !is_array($statusUpdates)) {
|
|
throw new Exception("Invalid or empty input: expected array of status updates");
|
|
}
|
|
|
|
$this->db_onedev->trans_start();
|
|
|
|
$results = [
|
|
'updated' => [],
|
|
'skipped' => [],
|
|
'not_found' => []
|
|
];
|
|
|
|
foreach ($statusUpdates as $update) {
|
|
// Validate required fields in each update item
|
|
if (empty($update['study_iuid']) || !isset($update['sps_status']) || empty($update['accession_number'])) {
|
|
$results['skipped'][] = [
|
|
'data' => $update,
|
|
'reason' => 'Missing required fields (study_iuid, accession_number, or sps_status)'
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$studyIuid = $update['study_iuid'];
|
|
$accessionNumber = $update['accession_number'];
|
|
$spsStatus = $update['sps_status'];
|
|
|
|
// Get the existing record to check current status
|
|
$sql = "SELECT T_MwlOrderID, T_MwlOrder_Progress FROM t_mwl_order
|
|
WHERE T_MwlOrder_StudyIUID = ? AND T_MwlOrder_AccessionNo = ?
|
|
AND T_MwlOrderIsActive = 'Y'";
|
|
|
|
$query = $this->db_onedev->query($sql, [$studyIuid, $accessionNumber]);
|
|
|
|
if (!$query || $query->num_rows() === 0) {
|
|
// Record not found
|
|
$results['not_found'][] = [
|
|
'study_iuid' => $studyIuid,
|
|
'accession_number' => $accessionNumber
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$order = $query->row();
|
|
|
|
// Skip update if status is already the same
|
|
if ($order->T_MwlOrder_Progress == $spsStatus) {
|
|
$results['skipped'][] = [
|
|
'study_iuid' => $studyIuid,
|
|
'accession_number' => $accessionNumber,
|
|
'status' => $spsStatus,
|
|
'reason' => 'Status already up to date'
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// Update the record with new status
|
|
$updateData = [
|
|
'T_MwlOrder_Progress' => $spsStatus,
|
|
'T_MwlOrderLastUpdated' => date('Y-m-d H:i:s')
|
|
];
|
|
|
|
$this->db_onedev->where('T_MwlOrderID', $order->T_MwlOrderID);
|
|
$this->db_onedev->update('t_mwl_order', $updateData);
|
|
|
|
if ($this->db_onedev->affected_rows() > 0) {
|
|
$results['updated'][] = [
|
|
'study_iuid' => $studyIuid,
|
|
'accession_number' => $accessionNumber,
|
|
'previous_status' => $order->T_MwlOrder_Progress,
|
|
'new_status' => $spsStatus
|
|
];
|
|
}
|
|
}
|
|
|
|
$this->db_onedev->trans_complete();
|
|
|
|
// Return results
|
|
$this->sys_ok([
|
|
'message' => "MWL progress update completed",
|
|
'summary' => [
|
|
'total_received' => count($statusUpdates),
|
|
'updated' => count($results['updated']),
|
|
'skipped' => count($results['skipped']),
|
|
'not_found' => count($results['not_found'])
|
|
],
|
|
'details' => $results
|
|
]);
|
|
} catch (Exception $e) {
|
|
if ($this->db_onedev->trans_status() === FALSE) {
|
|
$this->db_onedev->trans_rollback();
|
|
}
|
|
$this->sys_error($e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API untuk cek hasil ke Server PACS dan store ke t_mwl_result kalau ada
|
|
* @param $orderDetailId
|
|
*/
|
|
public function check_result($orderDetailId)
|
|
{
|
|
try {
|
|
if (!$this->isLogin) {
|
|
$this->sys_error("Invalid Token");
|
|
exit;
|
|
}
|
|
if (empty($orderDetailId)) {
|
|
throw new Exception("Missing required parameter: orderDetailId");
|
|
}
|
|
|
|
$repull = $this->input->get('repull') == 'true' ? true : false;
|
|
|
|
$this->db_onedev->trans_start();
|
|
// Get AccessionNumber from t_mwl_order by orderDetailId
|
|
$sql = "SELECT *
|
|
FROM t_mwl_order
|
|
WHERE T_MwlOrderT_OrderDetailID = ?
|
|
AND T_MwlOrderIsActive = 'Y'";
|
|
|
|
$qry = $this->db_onedev->query($sql, [$orderDetailId]);
|
|
if (!$qry) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Error query t_mwl_order" . json_encode($this->db_onedev->error()));
|
|
}
|
|
|
|
$rst = $qry->row();
|
|
|
|
if (!$rst) {
|
|
throw new Exception("No result found for OrderDetailID: $orderDetailId");
|
|
}
|
|
|
|
$accessionNo = $rst->T_MwlOrder_AccessionNo;
|
|
$trxLayananId = $rst->T_MwlOrder_TrxLayananID;
|
|
$patientNoReg = $rst->T_MwlOrderM_PatientNoReg;
|
|
$labNumber = $rst->T_MwlOrderT_OrderHeaderLabNumber;
|
|
|
|
// Cek apakah ada di t_mwl_result
|
|
$sql = "SELECT * FROM t_mwl_result
|
|
WHERE T_MwlResult_AccessionNo = ? AND T_MwlResult_TrxLayananID = ?
|
|
AND T_MwlResultIsActive = 'Y'";
|
|
$qry = $this->db_onedev->query($sql, [$accessionNo, $trxLayananId]);
|
|
|
|
// Jika ada di t_mwl_result dan repull = false, maka kembalikan data dari tabel
|
|
if ($qry->num_rows() > 0 && !$repull) {
|
|
$result = $qry->result();
|
|
$formattedResults = [
|
|
'study_iuid' => $result[0]->T_MwlResult_StudyIUID,
|
|
'accession_no' => $accessionNo,
|
|
'viewer_url' => $this->pacs_host . ":3000/viewer?StudyInstanceUIDs=" . $result[0]->T_MwlResult_StudyIUID,
|
|
'series' => []
|
|
];
|
|
foreach ($result as $series) {
|
|
$formattedResults['series'][] = [
|
|
'series_number' => $series->T_MwlResult_SeriesNumber,
|
|
'series_description' => $series->T_MwlResult_SeriesDescription,
|
|
'number_of_instances' => $series->T_MwlResult_NumberOfInstance,
|
|
'thumbnail' => $series->T_MwlResult_Thumbnail
|
|
];
|
|
}
|
|
|
|
$this->sys_ok($formattedResults);
|
|
exit;
|
|
}
|
|
|
|
$resp = $this->send_import_request($accessionNo);
|
|
|
|
if ($resp['response'] === null) {
|
|
throw new Exception("Belum ada hasil dari PACS untuk AccessionNumber: $accessionNo");
|
|
}
|
|
|
|
$orderData = [
|
|
'T_MwlOrder_AccessionNo' => $accessionNo,
|
|
'T_MwlOrder_TrxLayananID' => $trxLayananId,
|
|
'T_MwlOrderM_PatientNoReg' => $patientNoReg,
|
|
'T_MwlOrderT_OrderHeaderLabNumber' => $labNumber,
|
|
];
|
|
|
|
// Jika ada response, maka parse dan simpan ke t_mwl_result
|
|
$store = $this->store_pacs_results($resp, $orderData);
|
|
|
|
if (!$store) {
|
|
throw new Exception("Failed to store PACS results for AccessionNumber: $accessionNo");
|
|
}
|
|
|
|
$formattedResults = [
|
|
'study_iuid' => $resp['response']['study_iuid'],
|
|
'accession_no' => $accessionNo,
|
|
'series' => []
|
|
];
|
|
|
|
foreach ($resp['response']['series'] as $seriesNumber => $series) {
|
|
$formattedResults['series'][] = [
|
|
'series_number' => $seriesNumber,
|
|
'series_description' => $series['series_description'] ?? '',
|
|
'number_of_instances' => $series['number_of_instances'],
|
|
'thumbnail' => $series['thumbnail'],
|
|
'viewer_url' => $this->pacs_host . ":". $this->ohif_port . "/viewer?StudyInstanceUIDs=" . $resp['response']['study_iuid']
|
|
];
|
|
}
|
|
|
|
$this->db_onedev->trans_complete();
|
|
$this->sys_ok($formattedResults);
|
|
} catch (Exception $e) {
|
|
$this->db_onedev->trans_rollback();
|
|
$this->sys_error($e->getMessage());
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send import request to PACS API and validate the response
|
|
* @param string $accessionNo The accession number to query
|
|
* @return array Validated response data
|
|
*/
|
|
private function send_import_request($accessionNo)
|
|
{
|
|
// Validate input parameter
|
|
if (empty($accessionNo)) {
|
|
throw new Exception("Missing accession number for import request");
|
|
}
|
|
|
|
$url = $this->pacs_api_import . "?AccessionNumber=" . urlencode($accessionNo);
|
|
|
|
// Initialize cURL session
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Connection timeout in seconds
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Total timeout in seconds
|
|
|
|
// Execute the request
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curlError = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
// Check for cURL errors
|
|
if ($response === false) {
|
|
throw new Exception("PACS API request failed: " . $curlError);
|
|
}
|
|
|
|
// Check HTTP status code
|
|
if ($httpCode != 200) {
|
|
throw new Exception("PACS server returned error: HTTP code $httpCode");
|
|
}
|
|
|
|
// Decode JSON response
|
|
$responseData = json_decode($response, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new Exception("Invalid JSON response from PACS server: " . json_last_error_msg());
|
|
}
|
|
|
|
// Validate response structure
|
|
if (!isset($responseData['response'])) {
|
|
throw new Exception("Unexpected response format from PACS server (missing 'response' field)");
|
|
}
|
|
|
|
// Case 1: Empty response - {"response": null}
|
|
if ($responseData['response'] === null) {
|
|
// This is a valid response indicating no data found
|
|
return ['response' => null];
|
|
}
|
|
|
|
// Case 2: Full response - validate required fields
|
|
$requiredFields = ['accession_no', 'study_iuid', 'modality', 'series'];
|
|
foreach ($requiredFields as $field) {
|
|
if (!isset($responseData['response'][$field])) {
|
|
throw new Exception("Incomplete response from PACS server (missing '$field')");
|
|
}
|
|
}
|
|
|
|
// Validate that the returned accession number matches our request
|
|
if ($responseData['response']['accession_no'] !== $accessionNo) {
|
|
throw new Exception("PACS server returned data for a different accession number");
|
|
}
|
|
|
|
// Validate series data
|
|
if (!is_array($responseData['response']['series'])) {
|
|
throw new Exception("Invalid series data in PACS response");
|
|
}
|
|
|
|
// Return validated response
|
|
return $responseData;
|
|
}
|
|
|
|
/**
|
|
* Store PACS query results in the t_mwl_result table
|
|
* @param array $responseData Data returned from PACS API
|
|
* @param string $accessionNo Accession number used for the query
|
|
* @param string $trxLayananId Transaction layanan ID
|
|
* @return bool True if successful, otherwise an exception is thrown
|
|
*/
|
|
private function store_pacs_results($responseData, $orderData)
|
|
{
|
|
if (empty($responseData) || empty($responseData['response']) || !is_array($responseData['response'])) {
|
|
throw new Exception("Invalid response data format");
|
|
}
|
|
|
|
$response = $responseData['response'];
|
|
$accessionNo = $orderData['T_MwlOrder_AccessionNo'];
|
|
$trxLayananId = $orderData['T_MwlOrder_TrxLayananID'];
|
|
$patientNoReg = $orderData['T_MwlOrderM_PatientNoReg'];
|
|
$labNumber = $orderData['T_MwlOrderT_OrderHeaderLabNumber'];
|
|
|
|
// Get the necessary data to populate all records
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
// Prepare common data for all series records
|
|
$commonData = [
|
|
'T_MwlResultM_PatientNoReg' => $patientNoReg,
|
|
'T_MwlResultT_OrderHeaderLabNumber' => $labNumber,
|
|
'T_MwlResult_TrxLayananID' => $trxLayananId,
|
|
'T_MwlResult_AccessionNo' => $accessionNo,
|
|
'T_MwlResult_StudyIUID' => $response['study_iuid'],
|
|
'T_MwlResult_StudyDescription' => $response['study_description'] ?? '',
|
|
'T_MwlResult_Viewer' => $this->pacs_host . ":3000/viewer?StudyInstanceUIDs=" . $response['study_iuid'],
|
|
'T_MwlResultIsActive' => 'Y',
|
|
'T_MwlResultCreated' => $now,
|
|
'T_MwlResultLastUpdated' => $now
|
|
];
|
|
|
|
// Format study date time if available
|
|
if (!empty($response['study_datetime'])) {
|
|
// Try to parse the date format that comes from PACS (yyyyMMddHHmmss)
|
|
$studyDate = DateTime::createFromFormat('YmdHis', $response['study_datetime']);
|
|
if ($studyDate) {
|
|
$commonData['T_MwlResult_StudyDateTime'] = $studyDate->format('Y-m-d H:i:s');
|
|
}
|
|
}
|
|
|
|
// Start transaction if not already started
|
|
if (!$this->db_onedev->trans_status()) {
|
|
$this->db_onedev->trans_start();
|
|
}
|
|
|
|
// First, deactivate existing entries for this accession number
|
|
$this->db_onedev->where('T_MwlResult_AccessionNo', $accessionNo)
|
|
->where('T_MwlResult_TrxLayananID', $trxLayananId)
|
|
->update('t_mwl_result', ['T_MwlResultIsActive' => 'N']);
|
|
|
|
// Insert one record for each series in the response
|
|
$seriesCount = 0;
|
|
foreach ($response['series'] as $seriesNumber => $series) {
|
|
$seriesData = $commonData;
|
|
$seriesData['T_MwlResult_SeriesIUID'] = $series['series_iuid'];
|
|
$seriesData['T_MwlResult_SeriesNumber'] = $seriesNumber;
|
|
$seriesData['T_MwlResult_SeriesDescription'] = $series['series_description'] ?? '';
|
|
$seriesData['T_MwlResult_NumberOfInstance'] = $series['number_of_instances'];
|
|
$seriesData['T_MwlResult_SopIUID'] = $series['sop_iuid'];
|
|
$seriesData['T_MwlResult_Thumbnail'] = $series['thumbnail'];
|
|
|
|
$this->db_onedev->insert('t_mwl_result', $seriesData);
|
|
|
|
if ($this->db_onedev->affected_rows() <= 0) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Failed to insert series record for accession number: $accessionNo");
|
|
}
|
|
|
|
$seriesCount++;
|
|
}
|
|
|
|
// Ensure that we processed the correct number of series
|
|
if ($seriesCount != $response['number_of_series']) {
|
|
$this->db_onedev->trans_rollback();
|
|
throw new Exception("Number of series processed ($seriesCount) does not match expected number ({$response['number_of_series']})");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|