Files
BE_IBL/application/controllers/mockup/pacsmwl/Workorder.php
2026-04-15 15:24:12 +07:00

1136 lines
45 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 = "EG";
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 $accessionNumber
* (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($accessionNo)
{
try {
if (empty($accessionNo)) {
throw new Exception("Missing required parameter: accessionNo");
}
$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_AccessionNo = ?
AND So_ResultEntryDetailIsActive = 'Y'
-- AND So_ResultEntryValidation1 = 'Y' Kalau mau ambil yang valid aja
-- AND So_ResultEntryValidation2 = 'Y'
";
$query = $this->db_onedev->query($sql, [$accessionNo]);
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 Accession Number: $accessionNo");
}
$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_AccessionNo = ?
AND So_ResultEntryIsActive = 'Y'";
$query = $this->db_onedev->query($sql, [$accessionNo]);
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 Accession Number: $accessionNo");
}
$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
* Version 2: Hapus dulu kalau ada existing record
* Tanpa param ?repull=true
*/
public function check_result($accesionNumber)
{
try {
if (!$this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
if (empty($accesionNumber)) {
throw new Exception("Missing required parameter: AccessionNumber");
}
$this->db_onedev->trans_start();
$sql = "SELECT *
FROM t_mwl_order
WHERE T_MwlOrder_AccessionNo = ?
AND T_MwlOrderIsActive = 'Y'";
$qry = $this->db_onedev->query($sql, [$accesionNumber]);
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 AccessionNumber: $accesionNumber");
}
$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]);
$exist = false;
if ($qry->num_rows() > 0) {
$exist = true;
}
// If Exist, hard delete the existing record
if ($exist) {
$sql = "DELETE FROM t_mwl_result
WHERE T_MwlResult_AccessionNo = ?";
$qry = $this->db_onedev->query($sql, [$accessionNo]);
if (!$qry) {
$this->db_onedev->trans_rollback();
throw new Exception("Error deleting existing records: " . json_encode($this->db_onedev->error()));
}
}
// Send import request to PACS
$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' => []
];
$ohif = $this->getOhifHostPort();
$ohifIp = $ohif['ip'];
$ohifPort = $ohif['port'];
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' => "http://" . $ohifIp . ":" . $ohifPort . "/viewer?StudyInstanceUIDs=" . $resp['response']['study_iuid']
];
}
$this->db_onedev->trans_complete();
$this->sys_ok($formattedResults);
}
catch (Exception $e) {
if ($this->db_onedev->trans_status() === FALSE) {
$this->db_onedev->trans_rollback();
}
$this->sys_error($e->getMessage());
exit;
}
}
private function getOhifHostPort()
{
// OHIF Host from m_ohif_host
$ohifsql = "SELECT * FROM m_ohif_host
WHERE M_OhifHostType = 'in'
AND M_OhifHostIsActive = 'Y'
LIMIT 1";
$ohifqry = $this->db_onedev->query($ohifsql);
if (!$ohifqry) {
return [
'ip' => '',
'port' => ''
];
}
$ohifIp = $ohifqry->row()->M_OhifHostIP;
$ohifPort = $ohifqry->row()->M_OhifHostPort;
return [
'ip' => $ohifIp,
'port' => $ohifPort
];
}
/**
* API untuk cek hasil ke Server PACS dan store ke t_mwl_result kalau ada
* Version 1: Kalau ada dan tidak ?repull, maka ambil existing. Kalau repull, update existing
*/
public function check_result_old($accesionNumber)
{
try {
if (!$this->isLogin) {
$this->sys_error("Invalid Token");
exit;
}
if (empty($accesionNumber)) {
throw new Exception("Missing required parameter: AccessionNumber");
}
$repull = $this->input->get('repull') == 'true' ? true : false;
$this->db_onedev->trans_start();
$sql = "SELECT *
FROM t_mwl_order
WHERE T_MwlOrder_AccessionNo = ?
AND T_MwlOrderIsActive = 'Y'";
$qry = $this->db_onedev->query($sql, [$accesionNumber]);
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 AccessionNumber: $accesionNumber");
}
$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]);
$exist = false;
if ($qry->num_rows() > 0) {
$exist = true;
}
// Jika ada di t_mwl_result dan repull = false, maka kembalikan data dari tabel
if ($exist && !$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 sudah ada tapi repull = true, maka hapus sebelumnya lalu insert baru
if ($exist && $repull) {
// Soft delete (update IsActive to 'N') for all existing records with this accession number
$sql = "UPDATE t_mwl_result SET
T_MwlResultIsActive = 'N',
T_MwlResultLastUpdated = NOW()
WHERE T_MwlResult_AccessionNo = ?
AND T_MwlResult_TrxLayananID = ?";
$qry = $this->db_onedev->query($sql, [$accessionNo, $trxLayananId]);
if (!$qry) {
$this->db_onedev->trans_rollback();
throw new Exception("Error soft deleting existing records: " . json_encode($this->db_onedev->error()));
}
// Check if soft delete was successful
if ($this->db_onedev->affected_rows() <= 0) {
// Log warning but continue - might be a race condition where records were already deleted
error_log("Warning: No records were soft deleted for AccessionNo: $accessionNo");
}
}
// 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");
}
// Update t_mwl_order_progress to 4. Return boolean
$updateOrderProgres = $this->update_order_progress($accessionNo, $trxLayananId);
$formattedResults = [
'study_iuid' => $resp['response']['study_iuid'],
'accession_no' => $accessionNo,
'series' => [],
'update_order_progress' => $updateOrderProgres
];
$ohif = $this->getOhifHostPort();
$ohifIp = $ohif['ip'];
$ohifPort = $ohif['port'];
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' => "http://" . $ohifIp . ":" . $ohifPort . "/viewer?StudyInstanceUIDs=" . $resp['response']['study_iuid']
];
}
$this->db_onedev->trans_complete();
$this->sys_ok($formattedResults);
} catch (Exception $e) {
if ($this->db_onedev->trans_status() === FALSE) {
$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');
// OHIF Host from m_ohif_host
$ohif = $this->getOhifHostPort();
$ohifIp = $ohif['ip'];
$ohifPort = $ohif['port'];
// 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' => "http://$ohifIp" . ":$ohifPort/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;
}
private function update_order_progress($accessionNo, $trxLayananId)
{
$sql = "UPDATE t_mwl_order SET
T_MwlOrder_Progress = '4',
T_MwlOrderLastUpdated = NOW()
WHERE T_MwlOrder_AccessionNo = ?
AND T_MwlOrder_TrxLayananID = ?";
$qry = $this->db_onedev->query($sql, [$accessionNo, $trxLayananId]);
if (!$qry) {
$this->db_onedev->trans_rollback();
throw new Exception("Error updating t_mwl_progress: " . json_encode($this->db_onedev->error()));
}
// if any affected rows, return true
if ($this->db_onedev->affected_rows() > 0) {
return true;
} else {
return false;
}
}
}