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'] ?? ''); $this->_populate_cache($order_id); $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 . "'"); } }