diff --git a/application/controllers/tools/Birt_proxy_nonlab.php b/application/controllers/tools/Birt_proxy_nonlab.php new file mode 100644 index 00000000..5010f399 --- /dev/null +++ b/application/controllers/tools/Birt_proxy_nonlab.php @@ -0,0 +1,450 @@ +db_onedev = $this->load->database('onedev', true); + $this->load->library('ibl_encryptor'); + } + + public function stream_by_code() + { + $this->_stream_by_code(); + } + + public function stream() + { + $this->_stream_by_code(); + } + + public function get_url() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $this->_resolve_report_code($prm); + $pid = $this->_resolve_pid($prm); + + if ($pid <= 0) { + $this->sys_error('PID wajib diisi'); + return; + } + + $order_id = $this->_resolve_order_id_by_result_entry($pid); + if ($order_id > 0) { + $this->_populate_cache($order_id); + } + + $url = $this->_build_birt_url_by_code($report_code, $pid); + if ($url === false) { + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $this->sys_ok(['url' => $url]); + } + + public function header_json() + { + $pid = intval($this->input->get('PID') ?? $this->input->get('So_ResultEntryID') ?? 0); + if ($pid <= 0) { + echo json_encode(['error' => 'PID required']); + exit; + } + + $row = $this->db_onedev->query(" + SELECT + T_OrderHeaderID, + DATE_FORMAT(T_OrderHeaderDate, '%d-%m-%Y') AS T_OrderHeaderDate, + T_OrderHeaderLabNumber, + M_TitleName, + M_PatientName, + M_PatientName_enc, + m_sexname AS Gender, + M_PatientNoReg, + M_PatientDOB, + M_PatientDOB_enc, + T_OrderHeaderM_PatientAge, + M_CompanyName AS CorporateName, + M_PatientHp, + M_PatientHP_enc, + M_PatientEmail, + M_PatientEmail_enc, + M_CompanyAddress AS CorporateAddress, + M_CompanyEmail AS CorporateEmail, + M_CompanyPhone AS CorporatePhone, + M_CompanyAddressCity AS CorporateAddressCity, + TRIM(CONCAT(IFNULL(pj.M_DoctorPrefix, ''), ' ', IFNULL(pj.M_DoctorPrefix2, ''), ' ', IFNULL(pj.M_DoctorName, ''), ' ', IFNULL(pj.M_DoctorSufix, ''), ' ', IFNULL(pj.M_DoctorSufix2, ''))) AS M_DoctorName, + TRIM(CONCAT(IFNULL(pjj.M_DoctorPrefix, ''), ' ', IFNULL(pjj.M_DoctorPrefix2, ''), ' ', IFNULL(pjj.M_DoctorName, ''), ' ', IFNULL(pjj.M_DoctorSufix, ''), ' ', IFNULL(pjj.M_DoctorSufix2, ''))) AS M_DoctorName2, + M_PatientID, + M_PatientNIP, + M_PatientJob, + M_PatientPosisi, + M_PatientDivisi, + M_PatientLocation, + CONCAT(IFNULL(M_PatientDepartement, ''), ' - ', IFNULL(M_PatientNIP, '')) AS M_PatientDepartement + FROM t_orderheader + JOIN so_resultentry ON So_ResultEntryT_OrderHeaderID = T_OrderHeaderID + AND So_ResultEntryIsActive = 'Y' + LEFT JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID + AND M_PatientIsActive = 'Y' + LEFT JOIN m_title ON M_PatientM_TitleID = M_TitleID + AND M_TitleIsActive = 'Y' + LEFT JOIN m_company ON T_OrderHeaderM_CompanyID = M_CompanyID + AND M_CompanyIsActive = 'Y' + LEFT JOIN m_sex ON M_PatientM_SexID = M_SexID + LEFT JOIN m_doctor pjj ON T_OrderHeaderPj2M_DoctorID = pjj.M_DoctorID + AND pjj.M_DoctorIsActive = 'Y' + LEFT JOIN m_doctor pj ON T_OrderHeaderPjM_DoctorID = pj.M_DoctorID + AND pj.M_DoctorIsActive = 'Y' + WHERE So_ResultEntryID = ? + AND T_OrderHeaderIsActive = 'Y' + GROUP BY T_OrderHeaderID + LIMIT 1 + ", [$pid])->row_array(); + + if (!$row) { + echo json_encode(['error' => 'result entry not found']); + exit; + } + + $enc = $this->ibl_encryptor; + $name = $enc->decrypt($row['M_PatientName_enc'] ?? '') ?: ($row['M_PatientName'] ?? ''); + $dob = $enc->decrypt($row['M_PatientDOB_enc'] ?? '') ?: date('d-m-Y', strtotime($row['M_PatientDOB'] ?? 'now')); + $hp = $enc->decrypt($row['M_PatientHP_enc'] ?? '') ?: ($row['M_PatientHp'] ?? ''); + $email = $enc->decrypt($row['M_PatientEmail_enc'] ?? '') ?: ($row['M_PatientEmail'] ?? ''); + + $addr_row = $this->db_onedev->query(" + SELECT + CONCAT( + IFNULL(M_PatientAddressDescription, ''), ' ', + IFNULL(M_DistrictName, ''), ' ', + IFNULL(M_CityName, '') + ) AS addr, + M_PatientAddressDescription_enc + FROM m_patientaddress AS p + LEFT JOIN ( + SELECT regional_cd, regional_nm AS M_KelurahanName, pro_cd, kab_cd, kec_cd + FROM regional + ) reg_kel ON NULLIF(TRIM(p.M_PatientAddressRegionalCd), '') = reg_kel.regional_cd + LEFT JOIN ( + SELECT regional_cd, regional_nm AS M_DistrictName + FROM regional + ) reg_kec ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, reg_kel.kec_cd, '000') = reg_kec.regional_cd + LEFT JOIN ( + SELECT regional_cd, regional_nm AS M_CityName + FROM regional + ) reg_kab ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, '000000') = reg_kab.regional_cd + WHERE M_PatientAddressM_PatientID = ? + AND M_PatientAddressIsActive = 'Y' + ORDER BY (M_PatientAddressNote = 'Utama') DESC, M_PatientAddressID + LIMIT 1 + ", [$row['M_PatientID']])->row_array(); + + $address = ''; + if ($addr_row) { + $address = $enc->decrypt($addr_row['M_PatientAddressDescription_enc'] ?? '') ?: trim($addr_row['addr'] ?? ''); + } + + if (!empty($row['T_OrderHeaderID'])) { + $this->_populate_cache((int) $row['T_OrderHeaderID']); + } + + $data = [ + 'T_OrderHeaderID' => (int) ($row['T_OrderHeaderID'] ?? 0), + 'T_OrderHeaderDate' => $row['T_OrderHeaderDate'] ?? '', + 'T_OrderHeaderLabNumber' => $row['T_OrderHeaderLabNumber'] ?? '', + 'M_PatientName' => trim(($row['M_TitleName'] ?? '') . '. ' . $name), + 'Gender' => $row['Gender'] ?? '', + 'M_PatientNoReg' => $row['M_PatientNoReg'] ?? '', + 'M_PatientDOB' => $dob, + 'T_OrderHeaderM_PatientAge' => $row['T_OrderHeaderM_PatientAge'] ?? '', + 'CorporateName' => $row['CorporateName'] ?? '', + 'M_PatientAddress' => $address, + 'M_PatientHp' => $hp, + 'M_PatientEmail' => $email, + 'M_PatientAddressCity' => '', + 'M_PatientAddressState' => $address, + 'CorporateAddress' => $row['CorporateAddress'] ?? '', + 'CorporateEmail' => $row['CorporateEmail'] ?? '', + 'CorporatePhone' => $row['CorporatePhone'] ?? '', + 'CorporateAddressCity' => $row['CorporateAddressCity'] ?? '', + 'CorporateAddressState' => '', + 'M_DoctorName' => $row['M_DoctorName'] ?? '', + 'M_DoctorName2' => $row['M_DoctorName2'] ?? '', + 'Umur' => $dob . ' / ' . ($row['T_OrderHeaderM_PatientAge'] ?? ''), + 'M_PatientNIP' => $row['M_PatientNIP'] ?? '', + 'M_PatientJob' => $row['M_PatientJob'] ?? '', + 'M_PatientPosisi' => $row['M_PatientPosisi'] ?? '', + 'M_PatientDivisi' => $row['M_PatientDivisi'] ?? '', + 'M_PatientLocation' => $row['M_PatientLocation'] ?? '', + 'M_PatientDepartement' => $row['M_PatientDepartement'] ?? '', + ]; + + header('Content-Type: application/json'); + echo json_encode($data); + exit; + } + + private function _stream_by_code() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $this->_resolve_report_code($prm); + $pid = $this->_resolve_pid($prm); + + if ($pid <= 0) { + $this->sys_error('PID wajib diisi'); + return; + } + + $cache_id = null; + $order_id = $this->_resolve_order_id_by_result_entry($pid); + if ($order_id > 0) { + $cache_id = $this->_populate_cache($order_id); + } + + $url = $this->_build_birt_url_by_code($report_code, $pid); + if ($url === false) { + $this->_delete_cache($cache_id); + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $context = stream_context_create([ + 'http' => [ + 'timeout' => 120, + 'method' => 'GET', + ], + ]); + + $pdf = @file_get_contents($this->_resolve_fetch_url($url), false, $context); + $this->_delete_cache($cache_id); + + if ($pdf === false) { + $this->sys_error('Gagal generate report dari BIRT server'); + return; + } + + $filename = $report_code . '_' . $pid . '_' . date('Ymd') . '.pdf'; + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($pdf)); + echo $pdf; + exit; + } + + private function _resolve_report_code($prm) + { + $report_code = trim($prm['report_code'] ?? $prm['code_report'] ?? $prm['code'] ?? ''); + + return $report_code !== '' ? $report_code : 'RONTGEN-RESULT-P-01'; + } + + private function _resolve_pid($prm) + { + return intval( + $prm['PID'] ?? + $prm['PSo_ResultEntryID'] ?? + $prm['So_ResultEntryID'] ?? + $prm['result_entry_id'] ?? + $prm['PT_OrderHeaderID'] ?? + 0 + ); + } + + private function _build_birt_url_by_code($report_code, $pid) + { + $row = $this->db_onedev->query( + "SELECT Print_TransactionUrl + FROM print_transaction + WHERE Print_TransactionCode = ? + LIMIT 1", + [$report_code] + )->row_array(); + + if (!$row) { + return false; + } + + $username = $this->_resolve_report_username(); + $tm = round(microtime(true) * 1000); + $url = $row['Print_TransactionUrl']; + + $replacements = [ + 'PUsername' => $this->_format_report_string_param($username), + 'PT_OrderHeaderID' => $pid, + 'PSo_ResultEntryID' => $pid, + 'TS' => $tm, + ]; + + foreach ($replacements as $placeholder => $value) { + $url = str_replace($placeholder, $value, $url); + } + + $url = preg_replace('/([?&]PID=)PID([&#]|$)/', '${1}' . $pid . '$2', $url); + + return $url; + } + + private function _resolve_fetch_url($url) + { + $url = trim((string) $url); + + if ($url === '') { + return ''; + } + + if (preg_match('#^https?://#i', $url)) { + return $url; + } + + if (strpos($url, '/birt/') === 0) { + return $this->birt_base . $url; + } + + if (strpos($url, '/one-api-lab/') === 0) { + $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'; + + return $scheme . '://' . $host . $url; + } + + if (strpos($url, '/tools/') === 0 || strpos($url, '/index.php/') === 0) { + $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'; + + return $scheme . '://' . $host . '/one-api-lab' . $url; + } + + return $this->birt_base . $url; + } + + private function _resolve_order_id_by_result_entry($result_entry_id) + { + $row = $this->db_onedev->query( + "SELECT So_ResultEntryT_OrderHeaderID + FROM so_resultentry + WHERE So_ResultEntryID = ? + AND So_ResultEntryIsActive = 'Y' + LIMIT 1", + [$result_entry_id] + )->row_array(); + + return intval($row['So_ResultEntryT_OrderHeaderID'] ?? 0); + } + + private function _populate_cache($order_id) + { + $patient = $this->db_onedev->query( + "SELECT M_PatientID, + M_PatientName_enc, M_PatientDOB_enc, M_PatientHP_enc, + M_PatientEmail_enc, M_PatientDOB + FROM t_orderheader + JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID + WHERE T_OrderHeaderID = ? + AND T_OrderHeaderIsActive = 'Y' + LIMIT 1", + [$order_id] + )->row_array(); + + if (!$patient) { + return null; + } + + $addr = $this->db_onedev->query( + "SELECT M_PatientAddressDescription_enc + FROM m_patientaddress + WHERE M_PatientAddressM_PatientID = ? + AND M_PatientAddressIsActive = 'Y' + ORDER BY (M_PatientAddressNote = 'Utama') DESC, M_PatientAddressID + LIMIT 1", + [$patient['M_PatientID']] + )->row_array(); + + $enc = $this->ibl_encryptor; + $name = $enc->decrypt($patient['M_PatientName_enc'] ?? '') ?? ''; + $dob = $enc->decrypt($patient['M_PatientDOB_enc'] ?? '') ?? date('d-m-Y', strtotime($patient['M_PatientDOB'] ?? 'now')); + $hp = $enc->decrypt($patient['M_PatientHP_enc'] ?? '') ?? ''; + $email = $enc->decrypt($patient['M_PatientEmail_enc'] ?? '') ?? ''; + $address = $enc->decrypt($addr['M_PatientAddressDescription_enc'] ?? '') ?? ''; + + $this->db_onedev->query( + "DELETE FROM patient_print_cache + WHERE ppc_order_id = ? + OR ppc_created < NOW() - INTERVAL 5 MINUTE", + [$order_id] + ); + + $this->db_onedev->query( + "INSERT INTO patient_print_cache + (ppc_order_id, ppc_patient_id, ppc_name, ppc_dob, ppc_hp, ppc_email, ppc_address, ppc_created) + VALUES (?, ?, ?, ?, ?, ?, ?, NOW())", + [$order_id, $patient['M_PatientID'], $name, $dob, $hp, $email, $address] + ); + + return $this->db_onedev->insert_id(); + } + + private function _delete_cache($cache_id) + { + if ($cache_id) { + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_id = ?", + [$cache_id] + ); + } + + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_created < NOW() - INTERVAL 5 MINUTE" + ); + } + + private function _resolve_report_username() + { + if (!empty($this->sys_user['M_StaffName'])) { + return trim($this->sys_user['M_StaffName']); + } + + if (!empty($this->sys_user['M_UserUsername'])) { + return trim($this->sys_user['M_UserUsername']); + } + + if (!empty($this->sys_user['userName'])) { + return trim($this->sys_user['userName']); + } + + return 'ADMIN'; + } + + private function _format_report_string_param($value) + { + return rawurlencode("'" . (string) $value . "'"); + } +} diff --git a/sql/manual_changes/2026-06-12-update-sp-rpt-hasil-header-nonlab-patient-cache.sql b/sql/manual_changes/2026-06-12-update-sp-rpt-hasil-header-nonlab-patient-cache.sql new file mode 100644 index 00000000..fd6c25b1 --- /dev/null +++ b/sql/manual_changes/2026-06-12-update-sp-rpt-hasil-header-nonlab-patient-cache.sql @@ -0,0 +1,84 @@ +-- PDP: Radiology/nonlab BIRT header reads decrypted patient data from patient_print_cache. +-- Birt_proxy_nonlab populates this cache before fetching BIRT, then deletes it after streaming. + +USE `one_lab`; + +DROP PROCEDURE IF EXISTS `sp_rpt_hasil_header_nonLab`; + +DELIMITER $$ + +CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_rpt_hasil_header_nonLab`( + IN `PID` int, + IN `username` varchar(100) +) +BEGIN + DELETE FROM patient_print_cache + WHERE ppc_created < NOW() - INTERVAL 5 MINUTE; + + SELECT + DATE_FORMAT(T_OrderHeaderDate, "%d-%m-%Y") AS T_OrderHeaderDate, + T_OrderHeaderLabNumber, + CONCAT(M_TitleName, ". ", COALESCE(NULLIF(ppc.ppc_name, ''), M_PatientName)) AS M_PatientName, + m_sexname AS Gender, + M_PatientNoReg, + COALESCE(NULLIF(ppc.ppc_dob, ''), DATE_FORMAT(M_PatientDOB, "%d-%m-%Y")) AS M_PatientDOB, + T_OrderHeaderM_PatientAge, + M_CompanyName AS CorporateName, + COALESCE(NULLIF(ppc.ppc_address, ''), ( + SELECT CONCAT(M_PatientAddressDescription, ' ', M_DistrictName, ' ', M_CityName) + FROM m_patientaddress AS p + LEFT JOIN (SELECT regional_cd, regional_nm AS M_KelurahanName, pro_cd, kab_cd, kec_cd FROM regional) reg_kel + ON NULLIF(TRIM(p.M_PatientAddressRegionalCd), '') = reg_kel.regional_cd + LEFT JOIN (SELECT regional_cd, regional_nm AS M_DistrictName FROM regional) reg_kec + ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, reg_kel.kec_cd, '000') = reg_kec.regional_cd + LEFT JOIN (SELECT regional_cd, regional_nm AS M_CityName FROM regional) reg_kab + ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, '000000') = reg_kab.regional_cd + WHERE M_PatientAddressM_PatientID = M_PatientID + ORDER BY M_PatientAddressM_PatientID + LIMIT 1 + )) AS M_PatientAddress, + COALESCE(NULLIF(ppc.ppc_hp, ''), M_PatientHp) AS M_PatientHp, + COALESCE(NULLIF(ppc.ppc_email, ''), M_PatientEmail) AS M_PatientEmail, + '' AS M_PatientAddressCity, + COALESCE(NULLIF(ppc.ppc_address, ''), ( + SELECT CONCAT(M_PatientAddressDescription, ' ', M_DistrictName, ' ', M_CityName) + FROM m_patientaddress AS p + LEFT JOIN (SELECT regional_cd, regional_nm AS M_KelurahanName, pro_cd, kab_cd, kec_cd FROM regional) reg_kel + ON NULLIF(TRIM(p.M_PatientAddressRegionalCd), '') = reg_kel.regional_cd + LEFT JOIN (SELECT regional_cd, regional_nm AS M_DistrictName FROM regional) reg_kec + ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, reg_kel.kec_cd, '000') = reg_kec.regional_cd + LEFT JOIN (SELECT regional_cd, regional_nm AS M_CityName FROM regional) reg_kab + ON CONCAT(reg_kel.pro_cd, reg_kel.kab_cd, '000000') = reg_kab.regional_cd + WHERE M_PatientAddressM_PatientID = M_PatientID + ORDER BY M_PatientAddressM_PatientID + LIMIT 1 + )) AS M_PatientAddressState, + M_CompanyAddress AS CorporateAddress, + M_CompanyEmail, + M_CompanyPhone, + M_CompanyAddressCity, + '' AS CorporateAddressState, + CONCAT(IFNULL(pj.M_DoctorPrefix, ''), ' ', IFNULL(pj.M_DoctorPrefix2, ''), ' ', pj.M_DoctorName, ' ', IFNULL(pj.M_DoctorSufix, ''), ' ', IFNULL(pj.M_DoctorSufix2, '')) AS M_DoctorName, + CONCAT(IFNULL(pjj.M_DoctorPrefix, ''), ' ', IFNULL(pjj.M_DoctorPrefix2, ''), ' ', pjj.M_DoctorName, ' ', IFNULL(pjj.M_DoctorSufix, ''), ' ', IFNULL(pjj.M_DoctorSufix2, '')) AS M_DoctorName2, + CONCAT(COALESCE(NULLIF(ppc.ppc_dob, ''), DATE_FORMAT(M_PatientDOB, "%d-%m-%Y")), ' / ', T_OrderHeaderM_PatientAge) AS Umur, + M_PatientNIP, + M_PatientJob, + M_PatientPosisi, + M_PatientDivisi, + M_PatientLocation, + CONCAT(M_PatientDepartement, ' - ', M_PatientNIP) AS M_PatientDepartement + FROM t_orderheader + JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID AND M_PatientIsActive = 'Y' + JOIN m_title ON M_PatientM_TitleID = M_TitleID AND M_TitleIsActive = 'Y' + JOIN m_company ON T_OrderHeaderM_CompanyID = M_CompanyID AND M_CompanyIsActive = 'Y' + LEFT JOIN m_sex ON M_PatientM_SexID = M_SexID + LEFT JOIN m_doctor pjj ON T_OrderHeaderPj2M_DoctorID = pjj.M_DoctorID AND pjj.M_DoctorIsActive = 'Y' + LEFT JOIN m_doctor pj ON T_OrderHeaderPjM_DoctorID = pj.M_DoctorID AND pj.M_DoctorIsActive = 'Y' + JOIN so_resultentry ON So_ResultEntryT_OrderHeaderID = T_OrderHeaderID AND So_ResultEntryIsActive = 'Y' + LEFT JOIN patient_print_cache ppc ON ppc.ppc_order_id = T_OrderHeaderID + WHERE So_ResultEntryID = PID + AND T_OrderHeaderIsActive = 'Y' + GROUP BY T_OrderHeaderID; +END$$ + +DELIMITER ;