diff --git a/application/controllers/tools/Birt_proxy.php b/application/controllers/tools/Birt_proxy.php index 7aa7bf68..3278a408 100644 --- a/application/controllers/tools/Birt_proxy.php +++ b/application/controllers/tools/Birt_proxy.php @@ -539,6 +539,8 @@ class Birt_proxy extends MY_Controller $umur = $dob . ' / ' . ($row['T_OrderHeaderM_PatientAge'] ?? ''); + $this->_populate_cache($order_id); + $data = [ 'T_OrderHeaderDate' => $row['T_OrderHeaderDate'] ?? '', 'T_OrderHeaderLabNumber' => $row['T_OrderHeaderLabNumber'] ?? '', diff --git a/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134355 b/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134355 new file mode 100644 index 00000000..7aa7bf68 --- /dev/null +++ b/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134355 @@ -0,0 +1,598 @@ +db_onedev = $this->load->database('onedev', true); + $this->load->library('ibl_encryptor'); + } + + // GET/POST /tools/birt_proxy/stream_by_code + // Gunakan ini untuk flow browser print yang butuh URL langsung, + // tapi cache PDP harus tetap dihapus segera setelah PDF di-stream. + public function stream_by_code() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = trim($prm['report_code'] ?? $prm['code_report'] ?? $prm['code'] ?? ''); + $order_id = intval($prm['PT_OrderHeaderID'] ?? $prm['order_id'] ?? 0); + $payment_id = intval($prm['PPaymentID'] ?? $prm['payment_id'] ?? 0); + + if (!$report_code) { + $this->sys_error('report_code wajib diisi'); + return; + } + + if ($order_id <= 0 && $payment_id > 0) { + $order_id = $this->_resolve_order_id_by_payment($payment_id); + } + + if ($payment_id <= 0 && $order_id > 0) { + $payment_id = $this->_resolve_payment_id_by_order($order_id); + } + + if ($order_id <= 0) { + $this->sys_error('order_id tidak ditemukan'); + return; + } + + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name); + if ($url === false) { + $this->_delete_cache($cache_id); + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $full_url = $this->_resolve_fetch_url($url); + $context = stream_context_create([ + 'http' => [ + 'timeout' => 120, + 'method' => 'GET', + ] + ]); + + $pdf = @file_get_contents($full_url, false, $context); + $this->_delete_cache($cache_id); + + if ($pdf === false) { + $this->sys_error('Gagal generate report dari BIRT server'); + return; + } + + $filename = $report_code . '_' . $order_id . '_' . date('Ymd') . '.pdf'; + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($pdf)); + echo $pdf; + exit; + } + + // POST /tools/birt_proxy/stream + public function stream() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $prm['report_code'] ?? ''; + $order_id = intval($prm['PT_OrderHeaderID'] ?? 0); + $payment_id = intval($prm['PPaymentID'] ?? 0); + + if (!$report_code) { + $this->sys_error('report_code wajib diisi'); + return; + } + + if ($payment_id <= 0 && $order_id > 0) { + $payment_id = $this->_resolve_payment_id_by_order($order_id); + } + + $patient_name = ''; + if ($order_id > 0) { + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + } else { + $cache_id = null; + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name); + if ($url === false) { + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $full_url = $this->_resolve_fetch_url($url); + + $context = stream_context_create([ + 'http' => [ + 'timeout' => 120, + 'method' => 'GET', + ] + ]); + + $pdf = @file_get_contents($full_url, false, $context); + + if ($cache_id) { + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_id = ?", + [$cache_id] + ); + } + + if ($pdf === false) { + $this->sys_error('Gagal generate report dari BIRT server'); + return; + } + + $filename = $report_code . '_' . $order_id . '_' . date('Ymd') . '.pdf'; + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($pdf)); + echo $pdf; + exit; + } + + // Hanya return URL (untuk iframe/window.open) — tanpa stream + // Frontend membuka URL ini secara langsung + public function get_url() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $prm['report_code'] ?? ''; + $order_id = intval($prm['PT_OrderHeaderID'] ?? 0); + $patient_name = ''; + if ($order_id > 0) { + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, 0, $patient_name); + if ($url === false) { + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $this->sys_ok(['url' => $url]); + } + + // Decrypt patient PII dan simpan ke cache + private function _populate_cache($order_id) + { + // Ambil _enc columns dari m_patient via t_orderheader + $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 = ? 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' + AND M_PatientAddressNote = 'Utama' + 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'] ?? '') ?? ''; + + // Hapus cache lama untuk order ini + cleanup expired + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_order_id = ? OR ppc_created < NOW() - INTERVAL 5 MINUTE", + [$order_id] + ); + + // Insert cache baru + $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_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_payment($payment_id) + { + $row = $this->db_onedev->query( + "SELECT F_PaymentT_OrderHeaderID + FROM f_payment + WHERE F_PaymentID = ? + LIMIT 1", + [$payment_id] + )->row_array(); + + return intval($row['F_PaymentT_OrderHeaderID'] ?? 0); + } + + private function _resolve_payment_id_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT F_PaymentID + FROM f_payment + WHERE F_PaymentT_OrderHeaderID = ? + ORDER BY F_PaymentID DESC + LIMIT 1", + [$order_id] + )->row_array(); + + return intval($row['F_PaymentID'] ?? 0); + } + + private function _resolve_patient_name_by_cache($cache_id) + { + if (!$cache_id) { + return ''; + } + + $row = $this->db_onedev->query( + "SELECT ppc_name + FROM patient_print_cache + WHERE ppc_id = ? + LIMIT 1", + [$cache_id] + )->row_array(); + + return trim($row['ppc_name'] ?? ''); + } + + private function _resolve_patient_name_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT ppc_name + FROM patient_print_cache + WHERE ppc_order_id = ? + ORDER BY ppc_id DESC + LIMIT 1", + [$order_id] + )->row_array(); + + return trim($row['ppc_name'] ?? ''); + } + + private function _resolve_patient_name_from_enc_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT M_PatientName_enc + FROM t_orderheader + JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID + WHERE T_OrderHeaderID = ? + LIMIT 1", + [$order_id] + )->row_array(); + + return trim($this->ibl_encryptor->decrypt($row['M_PatientName_enc'] ?? '') ?? ''); + } + + 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 _build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name) + { + $row = $this->db_onedev->query( + "SELECT Print_TransactionUrl + FROM print_transaction + WHERE Print_TransactionCode = ? + LIMIT 1", + [$report_code] + )->row_array(); + + if (!$row) { + return false; + } + + $url_template = $this->_apply_report_template_hotfix($report_code, $row['Print_TransactionUrl']); + $username = $this->_resolve_report_username(); + $tm = round(microtime(true) * 1000); + $resolved_payment_id = $payment_id > 0 ? $payment_id : $this->_resolve_payment_id_by_order($order_id); + $is_internal_app_url = $this->_is_internal_app_url($url_template); + + $replacements = [ + 'PUsername' => $this->_format_report_string_param($username, $is_internal_app_url), + 'PT_OrderHeaderID' => $order_id, + 'PPaymentID' => $resolved_payment_id, + 'PAn' => $this->_format_report_string_param($patient_name, $is_internal_app_url), + 'TS' => $tm, + ]; + + $url = $url_template; + foreach ($replacements as $placeholder => $value) { + if ($value === null) { + $value = ''; + } + + $url = str_replace($placeholder, $value, $url); + } + + return $url; + } + + private function _apply_report_template_hotfix($report_code, $url_template) + { + $print_report_hotfix = [ + 'LAB-RESULT-P-01' => [ + 'from' => 'rpt_test.rptdesign', + 'to' => 'rpt_test_bkp020626.rptdesign', + ], + 'MIKROO-RESULT-P-01' => [ + 'from' => 'rpt_test.rptdesign', + 'to' => 'rpt_test_bkp020626.rptdesign', + ], + ]; + + if (!isset($print_report_hotfix[$report_code])) { + return $url_template; + } + + $hotfix = $print_report_hotfix[$report_code]; + + $resolved_url = str_replace($hotfix['from'], $hotfix['to'], $url_template); + + if (strpos($resolved_url, 'username=') === false) { + $resolved_url .= (strpos($resolved_url, '?') === false ? '?' : '&') . 'username=PUsername'; + } + + return $resolved_url; + } + + // GET /tools/birt_proxy/header_json?PID= + // Hanya bisa diakses dari localhost (127.0.0.1) — dipanggil oleh BIRT scripted dataset + // Return JSON semua kolom sp_rpt_hasil_header, PII sudah di-decrypt + public function header_json() + { + $order_id = intval($this->input->get('PID') ?? 0); + if ($order_id <= 0) { + echo json_encode(['error' => 'PID required']); + exit; + } + + $row = $this->db_onedev->query(" + SELECT + 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, + '' AS M_PatientAddressCity, + '' AS M_PatientAddressState, + M_CompanyName AS CorporateAddress, + M_CompanyEmail AS CorporateEmail, + M_CompanyPhone AS CorporatePhone, + M_CompanyAddressCity AS CorporateAddressCity, + '' AS CorporateAddressState, + 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 + LEFT JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID AND M_PatientIsActive = 'Y' + LEFT JOIN m_sex ON M_PatientM_SexID = M_SexID + LEFT 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_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 T_OrderHeaderID = ? AND T_OrderHeaderIsActive = 'Y' + ", [$order_id])->row_array(); + + if (!$row) { + echo json_encode(['error' => 'order 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((SELECT regional_nm FROM regional WHERE regional_cd = NULLIF(TRIM(M_PatientAddressRegionalCd),'') LIMIT 1),'') + ) AS addr, + M_PatientAddressDescription_enc + FROM m_patientaddress + WHERE M_PatientAddressM_PatientID = ? AND M_PatientAddressIsActive = 'Y' + ORDER BY 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'] ?? ''); + } + + $umur = $dob . ' / ' . ($row['T_OrderHeaderM_PatientAge'] ?? ''); + + $data = [ + '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' => '', + 'CorporateAddress' => $row['CorporateAddress'] ?? '', + 'CorporateEmail' => $row['CorporateEmail'] ?? '', + 'CorporatePhone' => $row['CorporatePhone'] ?? '', + 'CorporateAddressCity' => $row['CorporateAddressCity'] ?? '', + 'CorporateAddressState' => '', + 'M_DoctorName' => $row['M_DoctorName'] ?? '', + 'M_DoctorName2' => $row['M_DoctorName2'] ?? '', + 'Umur' => $umur, + '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 _is_internal_app_url($url) + { + $url = (string) $url; + + return ( + strpos($url, '/one-api-lab/') === 0 || + strpos($url, '/tools/') === 0 || + strpos($url, '/index.php/') === 0 + ); + } + + private function _format_report_string_param($value, $is_internal_app_url = false) + { + $value = (string) $value; + + if ($is_internal_app_url) { + return rawurlencode($value); + } + + return rawurlencode("'" . $value . "'"); + } +} diff --git a/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134536 b/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134536 new file mode 100644 index 00000000..7aa7bf68 --- /dev/null +++ b/application/controllers/tools/Birt_proxy.php.bak-before-header-json-cache-20260615134536 @@ -0,0 +1,598 @@ +db_onedev = $this->load->database('onedev', true); + $this->load->library('ibl_encryptor'); + } + + // GET/POST /tools/birt_proxy/stream_by_code + // Gunakan ini untuk flow browser print yang butuh URL langsung, + // tapi cache PDP harus tetap dihapus segera setelah PDF di-stream. + public function stream_by_code() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = trim($prm['report_code'] ?? $prm['code_report'] ?? $prm['code'] ?? ''); + $order_id = intval($prm['PT_OrderHeaderID'] ?? $prm['order_id'] ?? 0); + $payment_id = intval($prm['PPaymentID'] ?? $prm['payment_id'] ?? 0); + + if (!$report_code) { + $this->sys_error('report_code wajib diisi'); + return; + } + + if ($order_id <= 0 && $payment_id > 0) { + $order_id = $this->_resolve_order_id_by_payment($payment_id); + } + + if ($payment_id <= 0 && $order_id > 0) { + $payment_id = $this->_resolve_payment_id_by_order($order_id); + } + + if ($order_id <= 0) { + $this->sys_error('order_id tidak ditemukan'); + return; + } + + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name); + if ($url === false) { + $this->_delete_cache($cache_id); + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $full_url = $this->_resolve_fetch_url($url); + $context = stream_context_create([ + 'http' => [ + 'timeout' => 120, + 'method' => 'GET', + ] + ]); + + $pdf = @file_get_contents($full_url, false, $context); + $this->_delete_cache($cache_id); + + if ($pdf === false) { + $this->sys_error('Gagal generate report dari BIRT server'); + return; + } + + $filename = $report_code . '_' . $order_id . '_' . date('Ymd') . '.pdf'; + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($pdf)); + echo $pdf; + exit; + } + + // POST /tools/birt_proxy/stream + public function stream() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $prm['report_code'] ?? ''; + $order_id = intval($prm['PT_OrderHeaderID'] ?? 0); + $payment_id = intval($prm['PPaymentID'] ?? 0); + + if (!$report_code) { + $this->sys_error('report_code wajib diisi'); + return; + } + + if ($payment_id <= 0 && $order_id > 0) { + $payment_id = $this->_resolve_payment_id_by_order($order_id); + } + + $patient_name = ''; + if ($order_id > 0) { + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + } else { + $cache_id = null; + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name); + if ($url === false) { + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $full_url = $this->_resolve_fetch_url($url); + + $context = stream_context_create([ + 'http' => [ + 'timeout' => 120, + 'method' => 'GET', + ] + ]); + + $pdf = @file_get_contents($full_url, false, $context); + + if ($cache_id) { + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_id = ?", + [$cache_id] + ); + } + + if ($pdf === false) { + $this->sys_error('Gagal generate report dari BIRT server'); + return; + } + + $filename = $report_code . '_' . $order_id . '_' . date('Ymd') . '.pdf'; + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($pdf)); + echo $pdf; + exit; + } + + // Hanya return URL (untuk iframe/window.open) — tanpa stream + // Frontend membuka URL ini secara langsung + public function get_url() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + return; + } + + $prm = $this->sys_input; + $report_code = $prm['report_code'] ?? ''; + $order_id = intval($prm['PT_OrderHeaderID'] ?? 0); + $patient_name = ''; + if ($order_id > 0) { + $cache_id = $this->_populate_cache($order_id); + $patient_name = $this->_resolve_patient_name_by_cache($cache_id); + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_by_order($order_id); + } + if ($patient_name === '') { + $patient_name = $this->_resolve_patient_name_from_enc_by_order($order_id); + } + } + + $url = $this->_build_birt_url_by_code($report_code, $order_id, 0, $patient_name); + if ($url === false) { + $this->sys_error("Report code tidak ditemukan: {$report_code}"); + return; + } + + $this->sys_ok(['url' => $url]); + } + + // Decrypt patient PII dan simpan ke cache + private function _populate_cache($order_id) + { + // Ambil _enc columns dari m_patient via t_orderheader + $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 = ? 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' + AND M_PatientAddressNote = 'Utama' + 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'] ?? '') ?? ''; + + // Hapus cache lama untuk order ini + cleanup expired + $this->db_onedev->query( + "DELETE FROM patient_print_cache WHERE ppc_order_id = ? OR ppc_created < NOW() - INTERVAL 5 MINUTE", + [$order_id] + ); + + // Insert cache baru + $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_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_payment($payment_id) + { + $row = $this->db_onedev->query( + "SELECT F_PaymentT_OrderHeaderID + FROM f_payment + WHERE F_PaymentID = ? + LIMIT 1", + [$payment_id] + )->row_array(); + + return intval($row['F_PaymentT_OrderHeaderID'] ?? 0); + } + + private function _resolve_payment_id_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT F_PaymentID + FROM f_payment + WHERE F_PaymentT_OrderHeaderID = ? + ORDER BY F_PaymentID DESC + LIMIT 1", + [$order_id] + )->row_array(); + + return intval($row['F_PaymentID'] ?? 0); + } + + private function _resolve_patient_name_by_cache($cache_id) + { + if (!$cache_id) { + return ''; + } + + $row = $this->db_onedev->query( + "SELECT ppc_name + FROM patient_print_cache + WHERE ppc_id = ? + LIMIT 1", + [$cache_id] + )->row_array(); + + return trim($row['ppc_name'] ?? ''); + } + + private function _resolve_patient_name_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT ppc_name + FROM patient_print_cache + WHERE ppc_order_id = ? + ORDER BY ppc_id DESC + LIMIT 1", + [$order_id] + )->row_array(); + + return trim($row['ppc_name'] ?? ''); + } + + private function _resolve_patient_name_from_enc_by_order($order_id) + { + $row = $this->db_onedev->query( + "SELECT M_PatientName_enc + FROM t_orderheader + JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID + WHERE T_OrderHeaderID = ? + LIMIT 1", + [$order_id] + )->row_array(); + + return trim($this->ibl_encryptor->decrypt($row['M_PatientName_enc'] ?? '') ?? ''); + } + + 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 _build_birt_url_by_code($report_code, $order_id, $payment_id, $patient_name) + { + $row = $this->db_onedev->query( + "SELECT Print_TransactionUrl + FROM print_transaction + WHERE Print_TransactionCode = ? + LIMIT 1", + [$report_code] + )->row_array(); + + if (!$row) { + return false; + } + + $url_template = $this->_apply_report_template_hotfix($report_code, $row['Print_TransactionUrl']); + $username = $this->_resolve_report_username(); + $tm = round(microtime(true) * 1000); + $resolved_payment_id = $payment_id > 0 ? $payment_id : $this->_resolve_payment_id_by_order($order_id); + $is_internal_app_url = $this->_is_internal_app_url($url_template); + + $replacements = [ + 'PUsername' => $this->_format_report_string_param($username, $is_internal_app_url), + 'PT_OrderHeaderID' => $order_id, + 'PPaymentID' => $resolved_payment_id, + 'PAn' => $this->_format_report_string_param($patient_name, $is_internal_app_url), + 'TS' => $tm, + ]; + + $url = $url_template; + foreach ($replacements as $placeholder => $value) { + if ($value === null) { + $value = ''; + } + + $url = str_replace($placeholder, $value, $url); + } + + return $url; + } + + private function _apply_report_template_hotfix($report_code, $url_template) + { + $print_report_hotfix = [ + 'LAB-RESULT-P-01' => [ + 'from' => 'rpt_test.rptdesign', + 'to' => 'rpt_test_bkp020626.rptdesign', + ], + 'MIKROO-RESULT-P-01' => [ + 'from' => 'rpt_test.rptdesign', + 'to' => 'rpt_test_bkp020626.rptdesign', + ], + ]; + + if (!isset($print_report_hotfix[$report_code])) { + return $url_template; + } + + $hotfix = $print_report_hotfix[$report_code]; + + $resolved_url = str_replace($hotfix['from'], $hotfix['to'], $url_template); + + if (strpos($resolved_url, 'username=') === false) { + $resolved_url .= (strpos($resolved_url, '?') === false ? '?' : '&') . 'username=PUsername'; + } + + return $resolved_url; + } + + // GET /tools/birt_proxy/header_json?PID= + // Hanya bisa diakses dari localhost (127.0.0.1) — dipanggil oleh BIRT scripted dataset + // Return JSON semua kolom sp_rpt_hasil_header, PII sudah di-decrypt + public function header_json() + { + $order_id = intval($this->input->get('PID') ?? 0); + if ($order_id <= 0) { + echo json_encode(['error' => 'PID required']); + exit; + } + + $row = $this->db_onedev->query(" + SELECT + 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, + '' AS M_PatientAddressCity, + '' AS M_PatientAddressState, + M_CompanyName AS CorporateAddress, + M_CompanyEmail AS CorporateEmail, + M_CompanyPhone AS CorporatePhone, + M_CompanyAddressCity AS CorporateAddressCity, + '' AS CorporateAddressState, + 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 + LEFT JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID AND M_PatientIsActive = 'Y' + LEFT JOIN m_sex ON M_PatientM_SexID = M_SexID + LEFT 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_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 T_OrderHeaderID = ? AND T_OrderHeaderIsActive = 'Y' + ", [$order_id])->row_array(); + + if (!$row) { + echo json_encode(['error' => 'order 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((SELECT regional_nm FROM regional WHERE regional_cd = NULLIF(TRIM(M_PatientAddressRegionalCd),'') LIMIT 1),'') + ) AS addr, + M_PatientAddressDescription_enc + FROM m_patientaddress + WHERE M_PatientAddressM_PatientID = ? AND M_PatientAddressIsActive = 'Y' + ORDER BY 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'] ?? ''); + } + + $umur = $dob . ' / ' . ($row['T_OrderHeaderM_PatientAge'] ?? ''); + + $data = [ + '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' => '', + 'CorporateAddress' => $row['CorporateAddress'] ?? '', + 'CorporateEmail' => $row['CorporateEmail'] ?? '', + 'CorporatePhone' => $row['CorporatePhone'] ?? '', + 'CorporateAddressCity' => $row['CorporateAddressCity'] ?? '', + 'CorporateAddressState' => '', + 'M_DoctorName' => $row['M_DoctorName'] ?? '', + 'M_DoctorName2' => $row['M_DoctorName2'] ?? '', + 'Umur' => $umur, + '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 _is_internal_app_url($url) + { + $url = (string) $url; + + return ( + strpos($url, '/one-api-lab/') === 0 || + strpos($url, '/tools/') === 0 || + strpos($url, '/index.php/') === 0 + ); + } + + private function _format_report_string_param($value, $is_internal_app_url = false) + { + $value = (string) $value; + + if ($is_internal_app_url) { + return rawurlencode($value); + } + + return rawurlencode("'" . $value . "'"); + } +}