diff --git a/application/config/routes.php b/application/config/routes.php index 1b45740d..02fa61cc 100755 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -52,3 +52,4 @@ defined('BASEPATH') OR exit('No direct script access allowed'); $route['default_controller'] = 'welcome'; $route['404_override'] = ''; $route['translate_uri_dashes'] = FALSE; +$route['report/(:num)'] = 'tools/merge_report/preview/$1'; diff --git a/application/controllers/mockup/fo/mergeemailv1/Done.php b/application/controllers/mockup/fo/mergeemailv1/Done.php index 45c1323c..6883bfca 100644 --- a/application/controllers/mockup/fo/mergeemailv1/Done.php +++ b/application/controllers/mockup/fo/mergeemailv1/Done.php @@ -7,11 +7,12 @@ class Done extends MY_Controller echo "SampleStorage API"; } - public function __construct() - { - parent::__construct(); - $this->db_onedev = $this->load->database("onedev", true); - } + public function __construct() + { + parent::__construct(); + $this->db_onedev = $this->load->database("onedev", true); + $this->load->library('Ibl_merge_report_gateway'); + } public function search() { @@ -100,69 +101,52 @@ class Done extends MY_Controller exit; } - function mergereport() - { - if (!$this->isLogin) { - $this->sys_error("Invalid Token"); - exit; - } - $prm = $this->sys_input; - $userid = $this->sys_user['M_UserID']; - $name = $prm['name']; - $urls = $prm['urls']; - - $data = [ - "name" => $name, - "urls" => $urls - ]; - $jsonData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - - // Validasi URL - if (empty($urls) || !is_array($urls)) { - $this->sys_error("Invalid URLs"); - exit; - } - - // Escape string agar aman untuk query SQL - $jsonDataEscaped = $this->db_onedev->escape($jsonData); - - // Masukkan ke dalam query dengan string yang sudah di-escape - $sql = "INSERT INTO merge_request (mergeRequestPayload) VALUES ($jsonDataEscaped)"; - // Menyiapkan dan menjalankan query menggunakan parameter binding - $this->db_onedev->query($sql); - - // // Mengambil ID dari entri terakhir yang disisipkan - $lastInsertId = $this->db_onedev->insert_id(); - - - - $sql_gateway = "SELECT S_SystemsMergeReportGateway FROM conf_systems - WHERE S_SystemsIsActive = 'Y' LIMIT 1"; - $qry_gateway = $this->db_onedev->query($sql_gateway); - $rows = $qry_gateway->result_array(); - $new_url=''; - - - // $url_prefix = getUrlPrefixOnly(); - // $new_url = $url_prefix . "report/" . $lastInsertId; - if(!empty($rows[0]['S_SystemsMergeReportGateway'])){ - $new_url=$rows[0]['S_SystemsMergeReportGateway']. "report/". $lastInsertId; - - } - - $record = [ - "data" => $jsonDataEscaped, - "last_id" => $lastInsertId, - "url" => $new_url, - ]; - - $result = array( - "total" => 1, - "records" => array('status' => 'OK', 'records' => $record), - ); - $this->sys_ok($result); - // exit; - } + function mergereport() + { + if (!$this->isLogin) { + $this->sys_error("Invalid Token"); + exit; + } + $prm = $this->sys_input; + $userid = $this->sys_user['M_UserID']; + $labNumber = isset($prm['T_OrderHeaderLabNumber']) ? trim($prm['T_OrderHeaderLabNumber']) : ''; + if ($labNumber === '' && isset($prm['nolab'])) { + $labNumber = trim($prm['nolab']); + } + $name = isset($prm['name']) ? trim($prm['name']) : ''; + + if ($labNumber === '') { + $this->sys_error("LAB_NUMBER_REQUIRED - T_OrderHeaderLabNumber wajib diisi"); + exit; + } + + $resultMerge = $this->ibl_merge_report_gateway->create_merge_request_from_lab_number( + $labNumber, + $userid, + $name + ); + + if ($resultMerge['status'] !== 'OK') { + $this->sys_error($resultMerge['code'] . " - " . $resultMerge['message']); + exit; + } + + $record = [ + "last_id" => $resultMerge['data']['mergeRequestID'], + "url" => $resultMerge['data']['previewUrl'], + "T_OrderHeaderID" => $resultMerge['data']['T_OrderHeaderID'], + "T_OrderHeaderLabNumber" => $resultMerge['data']['T_OrderHeaderLabNumber'], + "snapshot" => $resultMerge['data']['snapshot'], + "go_payload_preview" => $resultMerge['data']['goPayloadPreview'], + ]; + + $result = array( + "total" => 1, + "records" => array('status' => 'OK', 'records' => $record), + ); + $this->sys_ok($result); + exit; + } function dosend() { if (!$this->isLogin) { @@ -294,4 +278,4 @@ class Done extends MY_Controller } } -} \ No newline at end of file +} diff --git a/application/controllers/tools/Ibl_merge_report_admin.http b/application/controllers/tools/Ibl_merge_report_admin.http new file mode 100644 index 00000000..5408625a --- /dev/null +++ b/application/controllers/tools/Ibl_merge_report_admin.http @@ -0,0 +1,44 @@ +### Operasional create merge_request dari nomor lab +POST http://10.9.20.31/one-api-lab/mockup/fo/mergeemailv1/done/mergereport +Content-Type: application/json + +{ + "token": "{{token}}", + "T_OrderHeaderLabNumber": "{{lab_number}}" +} + +### Admin create merge_request baru +POST http://10.9.20.31/one-api-lab/tools/ibl_merge_report_admin/create_request +Content-Type: application/json + +{ + "token": "{{token}}", + "T_OrderHeaderLabNumber": "{{lab_number}}" +} + +### Admin preview via backend tools +POST http://10.9.20.31/one-api-lab/tools/ibl_merge_report_admin/preview +Content-Type: application/json + +{ + "token": "{{token}}", + "T_OrderHeaderLabNumber": "{{lab_number}}", + "mergeRequestID": {{merge_request_id}} +} + +### Public preview stream via gateway +GET http://10.9.20.31/one-api-lab/report/{{merge_request_id}}?T_OrderHeaderLabNumber={{lab_number}} + +### Direct test ke service Go jika nanti sudah hidup +POST http://127.0.0.1:8005/merge +Content-Type: application/json +X-Internal-Secret: {{merge_secret}} + +{ + "name": "IBL12345-JOHN-DOE-merge-report.pdf", + "urls": [ + "https://devone.aplikasi.web.id/birt/frameset?__report=report/onelab/lab/rpt_test_email.rptdesign&__format=pdf&username=admin&PID=123&ts=1716963662" + ], + "mergeRequestID": 1036, + "T_OrderHeaderID": 123 +} diff --git a/application/controllers/tools/Ibl_merge_report_admin.php b/application/controllers/tools/Ibl_merge_report_admin.php new file mode 100644 index 00000000..e755af67 --- /dev/null +++ b/application/controllers/tools/Ibl_merge_report_admin.php @@ -0,0 +1,84 @@ +load->library('Ibl_merge_report_gateway'); + } + + public function create_request() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + exit; + } + + $auth = $this->ibl_merge_report_gateway->is_admin_group_allowed($this->sys_user['M_UserID']); + if ($auth['status'] !== 'OK') { + $this->sys_error($auth['code'] . ' - ' . $auth['message']); + exit; + } + + $labNumber = isset($this->sys_input['T_OrderHeaderLabNumber']) ? trim($this->sys_input['T_OrderHeaderLabNumber']) : ''; + if ($labNumber === '' && isset($this->sys_input['nolab'])) { + $labNumber = trim($this->sys_input['nolab']); + } + if ($labNumber === '') { + $this->sys_error('LAB_NUMBER_REQUIRED - T_OrderHeaderLabNumber wajib diisi.'); + exit; + } + + $customName = isset($this->sys_input['name']) ? trim($this->sys_input['name']) : ''; + $result = $this->ibl_merge_report_gateway->create_merge_request_from_lab_number( + $labNumber, + $this->sys_user['M_UserID'], + $customName + ); + + if ($result['status'] !== 'OK') { + $this->sys_error($result['code'] . ' - ' . $result['message']); + exit; + } + + $this->sys_ok($result['data']); + exit; + } + + public function preview() + { + if (!$this->isLogin) { + $this->sys_error('Invalid Token'); + exit; + } + + $auth = $this->ibl_merge_report_gateway->is_admin_group_allowed($this->sys_user['M_UserID']); + if ($auth['status'] !== 'OK') { + $this->sys_error($auth['code'] . ' - ' . $auth['message']); + exit; + } + + $mergeRequestId = isset($this->sys_input['mergeRequestID']) ? (int) $this->sys_input['mergeRequestID'] : 0; + $labNumber = isset($this->sys_input['T_OrderHeaderLabNumber']) ? trim($this->sys_input['T_OrderHeaderLabNumber']) : ''; + if ($labNumber === '' && isset($this->sys_input['nolab'])) { + $labNumber = trim($this->sys_input['nolab']); + } + if ($mergeRequestId <= 0) { + $this->sys_error('MERGE_REQUEST_ID_REQUIRED - mergeRequestID wajib diisi.'); + exit; + } + + $result = $this->ibl_merge_report_gateway->stream_merge_request($mergeRequestId, $labNumber); + if ($result['status'] !== 'OK') { + $this->sys_error($result['code'] . ' - ' . $result['message']); + exit; + } + + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $result['data']['payload']['name'] . '"'); + echo $result['data']['body']; + exit; + } +} diff --git a/application/controllers/tools/Merge_report.php b/application/controllers/tools/Merge_report.php index 7d903755..754ebbaa 100644 --- a/application/controllers/tools/Merge_report.php +++ b/application/controllers/tools/Merge_report.php @@ -5,43 +5,39 @@ class Merge_report extends MY_Controller { public function __construct() { parent::__construct(); - $this->load->database(); + $this->load->library('Ibl_merge_report_gateway'); } public function get($id = null) { - header('Content-Type: application/json'); + $this->preview($id); + } - try { - if ($id === null || !is_numeric($id)) { - throw new Exception('Invalid or missing ID parameter.'); - } - - $query = $this->db - ->select('mergeRequestPayload') - ->where('mergeRequestID', $id) - ->get('merge_request'); - - if ($query->num_rows() === 0) { - throw new Exception('Record not found.'); - } - - $row = $query->row(); - $payload = json_decode($row->mergeRequestPayload, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new Exception('Invalid JSON in mergeRequestPayload.'); - } - - echo json_encode([ - 'status' => 'OK', - 'data' => $payload - ]); - - } catch (Exception $e) { + public function preview($id = null) { + if ($id === null || !is_numeric($id)) { + header('Content-Type: application/json'); echo json_encode([ 'status' => 'ERR', - 'message' => $e->getMessage() + 'message' => 'MERGE_REQUEST_ID_INVALID - mergeRequestID tidak valid.' ]); + return; } + + $labNumber = isset($_GET['T_OrderHeaderLabNumber']) ? trim($_GET['T_OrderHeaderLabNumber']) : ''; + if ($labNumber === '' && isset($_GET['nolab'])) { + $labNumber = trim($_GET['nolab']); + } + $result = $this->ibl_merge_report_gateway->stream_merge_request((int) $id, $labNumber); + if ($result['status'] !== 'OK') { + header('Content-Type: application/json'); + echo json_encode([ + 'status' => 'ERR', + 'message' => $result['code'] . ' - ' . $result['message'] + ]); + return; + } + + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $result['data']['payload']['name'] . '"'); + echo $result['data']['body']; } } diff --git a/application/libraries/Ibl_merge_report_gateway.php b/application/libraries/Ibl_merge_report_gateway.php new file mode 100644 index 00000000..f17c0537 --- /dev/null +++ b/application/libraries/Ibl_merge_report_gateway.php @@ -0,0 +1,709 @@ +CI = &get_instance(); + $this->db_onedev = $this->CI->load->database('onedev', true); + } + + public function create_merge_request_from_lab_number($labNumber, $creatorUserId, $customName = '') + { + $order = $this->resolve_order_by_lab_number($labNumber); + if (!$order) { + return $this->error('ORDER_NOT_FOUND', 'Nomor lab tidak ditemukan.'); + } + + return $this->create_merge_request_from_order($order['T_OrderHeaderID'], $creatorUserId, $customName); + } + + public function create_merge_request_from_order($orderHeaderId, $creatorUserId, $customName = '') + { + $order = $this->get_order_header($orderHeaderId); + if (!$order) { + return $this->error('ORDER_NOT_FOUND', 'Order tidak ditemukan.'); + } + + $composition = $this->compose_merge_request_payload($orderHeaderId, $customName); + if ($composition['status'] !== 'OK') { + return $composition; + } + + $payloadJson = json_encode($composition['data']['snapshot'], JSON_UNESCAPED_SLASHES); + $insert = $this->db_onedev->query( + "INSERT INTO merge_request ( + mergeRequestT_OrderHeaderID, + mergeRequestPayload, + mergeRequestCreatedUserID, + mergeRequestCreated + ) VALUES (?, ?, ?, NOW())", + array( + $orderHeaderId, + $payloadJson, + $creatorUserId + ) + ); + + if (!$insert) { + return $this->error('MERGE_REQUEST_INSERT_FAILED', 'Gagal menyimpan merge request.'); + } + + $mergeRequestId = (int) $this->db_onedev->insert_id(); + $previewUrl = $this->build_preview_url($mergeRequestId); + + $goPayload = $this->build_go_payload_from_snapshot( + $mergeRequestId, + $orderHeaderId, + $composition['data']['snapshot'] + ); + + return array( + 'status' => 'OK', + 'data' => array( + 'mergeRequestID' => $mergeRequestId, + 'T_OrderHeaderID' => (int) $orderHeaderId, + 'T_OrderHeaderLabNumber' => $order['T_OrderHeaderLabNumber'], + 'previewUrl' => $previewUrl, + 'snapshot' => $composition['data']['snapshot'], + 'goPayloadPreview' => $goPayload + ) + ); + } + + public function compose_merge_request_payload($orderHeaderId, $customName = '') + { + $order = $this->get_order_header($orderHeaderId); + if (!$order) { + return $this->error('ORDER_NOT_FOUND', 'Order tidak ditemukan.'); + } + + $summary = $this->get_merge_summary_by_order_id($orderHeaderId); + if ($summary['status'] !== 'OK') { + return $summary; + } + + if ($summary['data']['available_merge'] !== 'Y') { + return $this->error('MERGE_NOT_READY', 'Order belum siap untuk merge report.'); + } + + $sources = array(); + $sourceDedup = array(); + + foreach ($summary['data']['records'] as $group) { + if (empty($group['group_result_ids'])) { + continue; + } + + foreach ($group['group_result_ids'] as $groupResultId) { + $groupSources = $this->get_group_sources( + $order, + (int) $groupResultId + ); + + if ($groupSources['status'] !== 'OK') { + return $groupSources; + } + + foreach ($groupSources['data'] as $source) { + if ($source['relativeUrl'] === '') { + return $this->error('MERGE_SOURCE_MISSING', 'Salah satu sumber report belum tersedia.'); + } + + if (isset($sourceDedup[$source['absoluteUrl']])) { + continue; + } + + $sourceDedup[$source['absoluteUrl']] = true; + $sources[] = $source; + } + } + } + + if (count($sources) === 0) { + return $this->error('MERGE_SOURCE_EMPTY', 'Tidak ada sumber report yang bisa digabung.'); + } + + $fileName = $this->build_merge_filename($order, $customName); + $snapshot = array( + 'name' => $fileName, + 'T_OrderHeaderID' => (int) $order['T_OrderHeaderID'], + 'T_OrderHeaderLabNumber' => $order['T_OrderHeaderLabNumber'], + 'createdAt' => date('Y-m-d H:i:s'), + 'sources' => $sources + ); + + return array( + 'status' => 'OK', + 'data' => array( + 'snapshot' => $snapshot, + 'summary' => $summary['data'] + ) + ); + } + + public function get_merge_summary_by_lab_number($labNumber) + { + $order = $this->resolve_order_by_lab_number($labNumber); + if (!$order) { + return $this->error('ORDER_NOT_FOUND', 'Nomor lab tidak ditemukan.'); + } + + return $this->get_merge_summary_by_order_id($order['T_OrderHeaderID']); + } + + public function get_merge_summary_by_order_id($orderHeaderId) + { + $sql = "SELECT + T_OrderHeaderID, + T_OrderDetailID, + Group_ResultID, + Nat_GroupName, + Group_ResultName, + T_OrderDetailT_TestName, + T_OrderDetailT_TestIsResult, + So_ResultEntryID, + IF(T_OrderPromiseDateTime > NOW(), 'N', 'Y') AS statuspromise, + DATE_FORMAT(T_OrderPromiseDateTime,'%d-%m-%Y %H:%i') as promise_date + FROM t_orderheader + JOIN t_orderdetail ON T_OrderHeaderID = T_OrderDetailT_OrderHeaderID + AND T_OrderDetailIsActive = 'Y' + AND T_OrderHeaderID = ? + AND T_OrderHeaderIsActive = 'Y' + AND T_OrderDetailT_TestIsResult = 'Y' + JOIN group_resultdetail ON T_OrderDetailT_TestID = Group_ResultDetailT_TestID + AND Group_ResultDetailIsActive = 'Y' + JOIN group_result ON Group_ResultDetailGroup_ResultID = Group_ResultID + AND Group_ResultIsActive = 'Y' + JOIN t_orderpromise ON T_OrderHeaderID = T_OrderPromiseT_OrderHeaderID + AND T_OrderPromiseIsActive = 'Y' + AND T_OrderDetailT_OrderPromiseID = T_OrderPromiseID + JOIN t_test ON T_OrderDetailT_TestID = T_TestID + AND T_TestIsActive = 'Y' + JOIN nat_group ON T_TestNat_GroupID = Nat_GroupID + AND Nat_GroupIsActive = 'Y' + JOIN t_orderdelivery ON T_OrderDeliveryT_OrderHeaderID = T_OrderHeaderID + AND T_OrderDeliveryM_DeliveryTypeID = 3 + AND T_OrderDeliveryIsActive = 'Y' + LEFT JOIN so_resultentry ON T_OrderHeaderID = So_ResultEntryT_OrderHeaderID + AND T_OrderDetailID = So_ResultEntryT_OrderDetailID + AND So_ResultEntryIsActive = 'Y' + ORDER BY Nat_GroupID, promise_date"; + + $query = $this->db_onedev->query($sql, array($orderHeaderId)); + if (!$query) { + return $this->error('MERGE_SUMMARY_FAILED', 'Gagal membaca komposisi merge order.'); + } + + $rows = $query->result_array(); + if (count($rows) === 0) { + return $this->error('MERGE_SUMMARY_EMPTY', 'Order tidak memiliki komposisi merge.'); + } + + $grouped = array(); + foreach ($rows as $row) { + $groupName = $row['Nat_GroupName']; + if (!isset($grouped[$groupName])) { + $grouped[$groupName] = array( + 'group' => $groupName, + 'ohid' => (int) $row['T_OrderHeaderID'], + 'statuspromise' => true, + 'promises' => array(), + 'orderdetailid' => array(), + 'resultnames' => array(), + 'details' => array(), + 'group_result_ids' => array() + ); + } + + if ($row['statuspromise'] !== 'Y') { + $grouped[$groupName]['statuspromise'] = false; + } + + if (!in_array($row['promise_date'], $grouped[$groupName]['promises'])) { + $grouped[$groupName]['promises'][] = $row['promise_date']; + } + + $grouped[$groupName]['orderdetailid'][] = (int) $row['T_OrderDetailID']; + + if (!in_array($row['Group_ResultName'], $grouped[$groupName]['resultnames'])) { + $grouped[$groupName]['resultnames'][] = $row['Group_ResultName']; + } + + if (!in_array((int) $row['Group_ResultID'], $grouped[$groupName]['group_result_ids'])) { + $grouped[$groupName]['group_result_ids'][] = (int) $row['Group_ResultID']; + } + + if (!isset($grouped[$groupName]['details'][$row['Group_ResultName']])) { + $grouped[$groupName]['details'][$row['Group_ResultName']] = array( + 'name' => $row['Group_ResultName'], + 'resultentryid' => array() + ); + } + + if (!is_null($row['So_ResultEntryID'])) { + $grouped[$groupName]['details'][$row['Group_ResultName']]['resultentryid'][] = (int) $row['So_ResultEntryID']; + } + } + + $records = array(); + $countTotalGroup = count($grouped); + $countDoneGroup = 0; + foreach ($grouped as $group) { + if ($group['statuspromise']) { + $countDoneGroup++; + } + + $detailList = array(); + foreach ($group['details'] as $detail) { + $detailList[] = $detail; + } + $group['details'] = $detailList; + $records[] = $group; + } + + $availableMerge = 'Y'; + if ($countTotalGroup === 1) { + if ($countDoneGroup < 1) { + $availableMerge = 'N'; + } + } elseif ($countTotalGroup >= 2) { + if ($countDoneGroup < 2 || $countDoneGroup < $countTotalGroup) { + $availableMerge = 'N'; + } + } + + return array( + 'status' => 'OK', + 'data' => array( + 'total' => 0, + 'records' => $records, + 'available_merge' => $availableMerge + ) + ); + } + + public function stream_merge_request($mergeRequestId, $labNumber = '') + { + $mergeRequest = $this->get_merge_request($mergeRequestId); + if (!$mergeRequest) { + return $this->error('MERGE_REQUEST_NOT_FOUND', 'Merge request tidak ditemukan.'); + } + + $payload = json_decode($mergeRequest['mergeRequestPayload'], true); + if (!is_array($payload)) { + return $this->error('MERGE_REQUEST_INVALID', 'Payload merge request tidak valid.'); + } + + $order = $this->get_order_header($mergeRequest['mergeRequestT_OrderHeaderID']); + if (!$order) { + return $this->error('ORDER_NOT_FOUND', 'Order untuk merge request tidak ditemukan.'); + } + + if ($labNumber !== '') { + $resolvedOrder = $this->resolve_order_by_lab_number($labNumber); + if (!$resolvedOrder || (int) $resolvedOrder['T_OrderHeaderID'] !== (int) $mergeRequest['mergeRequestT_OrderHeaderID']) { + return $this->error('MERGE_REQUEST_ORDER_MISMATCH', 'Merge request tidak terkait dengan nomor lab tersebut.'); + } + } + + $goPayload = $this->build_go_payload_from_snapshot( + (int) $mergeRequest['mergeRequestID'], + (int) $mergeRequest['mergeRequestT_OrderHeaderID'], + $payload + ); + + return $this->call_merge_service($goPayload); + } + + public function build_go_payload_from_snapshot($mergeRequestId, $orderHeaderId, array $snapshot) + { + $urls = array(); + foreach ($snapshot['sources'] as $source) { + $urls[] = $this->make_absolute_url($source['relativeUrl']); + } + + return array( + 'name' => $snapshot['name'], + 'urls' => $urls, + 'mergeRequestID' => (int) $mergeRequestId, + 'T_OrderHeaderID' => (int) $orderHeaderId + ); + } + + public function get_current_user_group_id($userId) + { + $query = $this->db_onedev->query( + "SELECT M_UserM_UserGroupID FROM m_user WHERE M_UserID = ? AND M_UserIsActive = 'Y' LIMIT 1", + array($userId) + ); + + if (!$query || $query->num_rows() === 0) { + return 0; + } + + return (int) $query->row()->M_UserM_UserGroupID; + } + + public function is_admin_group_allowed($userId) + { + $config = $this->get_system_config(); + $allowed = array(); + if (!empty($config['S_SystemsMergeReportAdminGroupIDs'])) { + foreach (explode(',', $config['S_SystemsMergeReportAdminGroupIDs']) as $value) { + $value = (int) trim($value); + if ($value > 0) { + $allowed[] = $value; + } + } + } + + if (count($allowed) === 0) { + return array( + 'status' => 'ERR', + 'code' => 'MERGE_ADMIN_GROUP_NOT_CONFIGURED', + 'message' => 'Akses admin merge report belum dikonfigurasi.' + ); + } + + $groupId = $this->get_current_user_group_id($userId); + if (!in_array($groupId, $allowed)) { + return array( + 'status' => 'ERR', + 'code' => 'MERGE_ADMIN_FORBIDDEN', + 'message' => 'User group tidak diizinkan mengakses tools merge report.' + ); + } + + return array( + 'status' => 'OK', + 'data' => array( + 'groupId' => $groupId, + 'allowedGroupIds' => $allowed + ) + ); + } + + public function get_system_config() + { + static $config = null; + if ($config !== null) { + return $config; + } + + $query = $this->db_onedev->query( + "SELECT + S_SystemsMergeReportGateway, + S_SystemIPAddressRegional, + S_SystemsMergeReportServiceBaseUrl, + S_SystemsMergeReportServiceSecret, + S_SystemsMergeReportAdminGroupIDs + FROM conf_systems + WHERE S_SystemsIsActive = 'Y' + LIMIT 1" + ); + + if ($query && $query->num_rows() > 0) { + $config = $query->row_array(); + } else { + $config = array(); + } + + return $config; + } + + protected function get_merge_request($mergeRequestId) + { + $query = $this->db_onedev->query( + "SELECT + mergeRequestID, + mergeRequestT_OrderHeaderID, + mergeRequestPayload, + mergeRequestCreatedUserID, + mergeRequestCreated + FROM merge_request + WHERE mergeRequestID = ? + LIMIT 1", + array($mergeRequestId) + ); + + if (!$query || $query->num_rows() === 0) { + return null; + } + + return $query->row_array(); + } + + protected function get_order_header($orderHeaderId) + { + $sql = "SELECT + T_OrderHeaderID, + T_OrderHeaderLabNumber, + T_OrderHeaderLabNumberExt, + DATE(T_OrderHeaderDate) AS order_date, + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(CONCAT(IFNULL(M_TitleName,''),' ',M_PatientName), ' ', '-'), '.', '_'), '(', ''), ')', ''), '\\'', '') AS patient_name + FROM t_orderheader + JOIN m_patient ON T_OrderHeaderM_PatientID = M_PatientID + LEFT JOIN m_title ON M_PatientM_TitleID = M_TitleID + WHERE T_OrderHeaderID = ? + AND T_OrderHeaderIsActive = 'Y' + LIMIT 1"; + $query = $this->db_onedev->query($sql, array($orderHeaderId)); + if (!$query || $query->num_rows() === 0) { + return null; + } + + return $query->row_array(); + } + + protected function resolve_order_by_lab_number($labNumber) + { + $query = $this->db_onedev->query( + "SELECT T_OrderHeaderID, T_OrderHeaderLabNumber + FROM t_orderheader + WHERE T_OrderHeaderLabNumber = ? + AND T_OrderHeaderIsActive = 'Y' + ORDER BY T_OrderHeaderID DESC + LIMIT 1", + array($labNumber) + ); + + if (!$query || $query->num_rows() === 0) { + return null; + } + + return $query->row_array(); + } + + protected function get_group_sources(array $order, $groupResultId) + { + $query = $this->db_onedev->query( + "SELECT DISTINCT + Group_ResultID, + Group_ResultName, + Group_ResultFlagNonLab, + IFNULL(T_EmailNonLabUrl,'-') AS EmailNonLabUrl, + IF(T_EmailNonLabUrl IS NULL AND Group_ResultFlagNonLab = 'Y',' [Belum Pilih Format Hasil]','') AS temail + FROM t_orderdetail + JOIN group_resultdetail + ON Group_ResultDetailT_TestID = T_OrderDetailT_TestID + AND T_OrderDetailIsActive = 'Y' + AND Group_ResultDetailIsActive = 'Y' + AND T_OrderDetailT_OrderHeaderID = ? + JOIN group_result + ON Group_ResultDetailGroup_ResultID = Group_ResultID + AND Group_ResultIsActive = 'Y' + AND Group_ResultID = ? + LEFT JOIN t_email_nonlab + ON T_EmailNonLabT_OrderHeaderID = T_OrderDetailT_OrderHeaderID + AND T_EmailNonLabType LIKE CONCAT('%', REPLACE(Group_ResultName, 'Elektromedik', 'electromedis'), '%')", + array($order['T_OrderHeaderID'], $groupResultId) + ); + + if (!$query) { + return $this->error('MERGE_SOURCE_QUERY_FAILED', 'Gagal menyusun sumber report.'); + } + + $rows = $query->result_array(); + $result = array(); + $ts = '&ts=' . time(); + + foreach ($rows as $row) { + $relativeUrl = ''; + $emailNonLabUrl = str_replace(' ', '', $row['EmailNonLabUrl']); + if (strpos($emailNonLabUrl, 'fisik') !== false) { + continue; + } + + switch ((int) $row['Group_ResultID']) { + case 1: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_test_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + case 2: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_hasil_papsmear_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + case 3: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_hasil_fna_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + case 12: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_hasil_lcprep_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + case 13: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_test_mikro_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + case 14: + $relativeUrl = '/birt/frameset?__report=report/onelab/lab/rpt_hasil_cytologi_email.rptdesign&__format=pdf&username=admin&PID=' . $order['T_OrderHeaderID'] . $ts; + break; + default: + $relativeUrl = $emailNonLabUrl; + break; + } + + if ($relativeUrl === '-' || $relativeUrl === '') { + continue; + } + + $result[] = array( + 'groupResultID' => (int) $row['Group_ResultID'], + 'name' => $row['Group_ResultName'], + 'relativeUrl' => $relativeUrl, + 'absoluteUrl' => $this->make_absolute_url($relativeUrl) + ); + } + + return array( + 'status' => 'OK', + 'data' => $result + ); + } + + protected function call_merge_service(array $payload) + { + $config = $this->get_system_config(); + $baseUrl = isset($config['S_SystemsMergeReportServiceBaseUrl']) ? trim($config['S_SystemsMergeReportServiceBaseUrl']) : ''; + $secret = isset($config['S_SystemsMergeReportServiceSecret']) ? trim($config['S_SystemsMergeReportServiceSecret']) : ''; + + if ($baseUrl === '' || $secret === '') { + return $this->error('MERGE_SERVICE_NOT_CONFIGURED', 'Konfigurasi merge report service belum lengkap.'); + } + + $url = rtrim($baseUrl, '/') . '/merge'; + $jsonPayload = json_encode($payload, JSON_UNESCAPED_SLASHES); + $lastError = array( + 'status' => 'ERR', + 'code' => 'MERGE_SERVICE_FAILED', + 'message' => 'Layanan merge internal gagal memproses permintaan.' + ); + + for ($attempt = 1; $attempt <= 3; $attempt++) { + $ch = curl_init($url); + curl_setopt_array($ch, array( + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $jsonPayload, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/json', + 'X-Internal-Secret: ' . $secret + ), + CURLOPT_TIMEOUT => 30, + CURLOPT_CONNECTTIMEOUT => 30 + )); + + $response = curl_exec($ch); + $curlError = curl_error($ch); + $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $headerSize = (int) curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + curl_close($ch); + + if ($response === false || $curlError !== '') { + $lastError = $this->error('MERGE_SERVICE_TIMEOUT', 'Layanan merge internal melebihi batas waktu.'); + continue; + } + + $headers = substr($response, 0, $headerSize); + $body = substr($response, $headerSize); + + if ($httpCode >= 200 && $httpCode < 300 && stripos($contentType, 'application/pdf') !== false) { + return array( + 'status' => 'OK', + 'data' => array( + 'headers' => $headers, + 'contentType' => $contentType, + 'body' => $body, + 'payload' => $payload + ) + ); + } + + $lastError = $this->map_service_error($httpCode); + } + + return $lastError; + } + + protected function map_service_error($httpCode) + { + if ($httpCode === 400 || $httpCode === 422) { + return $this->error('MERGE_SERVICE_REJECTED', 'Komposisi merge report tidak valid.'); + } + + if ($httpCode === 401 || $httpCode === 403) { + return $this->error('MERGE_SERVICE_UNAUTHORIZED', 'Layanan merge internal menolak permintaan.'); + } + + if ($httpCode === 404) { + return $this->error('MERGE_SOURCE_NOT_FOUND', 'Salah satu sumber report tidak ditemukan.'); + } + + if ($httpCode === 408 || $httpCode === 504) { + return $this->error('MERGE_SERVICE_TIMEOUT', 'Layanan merge internal melebihi batas waktu.'); + } + + return $this->error('MERGE_SERVICE_FAILED', 'Layanan merge internal gagal memproses permintaan.'); + } + + protected function build_preview_url($mergeRequestId) + { + return rtrim($this->get_public_base_url(), '/') . '/report/' . (int) $mergeRequestId; + } + + protected function make_absolute_url($url) + { + if ($url === '') { + return ''; + } + + if (preg_match('/^https?:\/\//i', $url)) { + return $url; + } + + return rtrim($this->get_public_base_url(), '/') . '/' . ltrim($url, '/'); + } + + protected function get_public_base_url() + { + $config = $this->get_system_config(); + if (!empty($config['S_SystemsMergeReportGateway'])) { + return rtrim($config['S_SystemsMergeReportGateway'], '/'); + } + + $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; + $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'; + $script = isset($_SERVER['SCRIPT_NAME']) ? dirname($_SERVER['SCRIPT_NAME']) : ''; + $script = trim(str_replace('\\', '/', $script), '/'); + if ($script === '' || $script === '.') { + return $https . '://' . $host; + } + + return $https . '://' . $host . '/' . $script; + } + + protected function build_merge_filename(array $order, $customName) + { + $name = trim($customName); + if ($name === '') { + $name = $order['T_OrderHeaderLabNumber'] . '-' . $order['patient_name'] . '-merge-report.pdf'; + } + + if (strtolower(substr($name, -4)) !== '.pdf') { + $name .= '.pdf'; + } + + return preg_replace('/[^A-Za-z0-9._-]/', '-', $name); + } + + protected function error($code, $message) + { + return array( + 'status' => 'ERR', + 'code' => $code, + 'message' => $message + ); + } +} diff --git a/docs/merge-report-service-setup.md b/docs/merge-report-service-setup.md new file mode 100644 index 00000000..2f0f3b9c --- /dev/null +++ b/docs/merge-report-service-setup.md @@ -0,0 +1,110 @@ +# Merge Report Service Setup + +Dokumen ini untuk aktivasi integrasi: + +- FE -> one-api-lab -> `ibl_merge_report_service` +- one-api-lab menjadi merge gateway +- service Go hanya menerima `POST /merge` + +## 1. Config DB + +Isi kolom berikut di `one_lab.conf_systems`: + +- `S_SystemsMergeReportServiceBaseUrl` +- `S_SystemsMergeReportServiceSecret` +- `S_SystemsMergeReportAdminGroupIDs` + +Contoh query: + +```sql +UPDATE conf_systems +SET + S_SystemsMergeReportServiceBaseUrl = 'http://127.0.0.1:8005', + S_SystemsMergeReportServiceSecret = 'ganti-dengan-shared-secret', + S_SystemsMergeReportAdminGroupIDs = '1' +WHERE S_SystemsID = 1; +``` + +Catatan: + +- `S_SystemsMergeReportServiceBaseUrl` jangan diakhiri slash. +- `S_SystemsMergeReportAdminGroupIDs` diisi daftar `M_UserGroupID` dipisah koma. +- `S_SystemsMergeReportGateway` tetap dipakai untuk membentuk absolute URL source PDF dan preview URL. + +## 2. Kontrak Service Go + +Endpoint minimal yang harus tersedia: + +- `POST /merge` + +Request: + +Headers: + +- `Content-Type: application/json` +- `X-Internal-Secret: ` + +Body: + +```json +{ + "name": "IBL12345-JOHN-DOE-merge-report.pdf", + "urls": [ + "https://devone.aplikasi.web.id/birt/frameset?__report=report/onelab/lab/rpt_test_email.rptdesign&__format=pdf&username=admin&PID=123&ts=1716963662" + ], + "mergeRequestID": 1036, + "T_OrderHeaderID": 123 +} +``` + +Expected response: + +- sukses: `200` dengan body binary PDF dan `Content-Type: application/pdf` +- gagal validasi/source: `400` atau `422` +- secret salah: `401` atau `403` +- source tidak ditemukan: `404` +- timeout/upstream: `408` atau `504` + +Perilaku yang wajib: + +- jika satu source gagal, seluruh merge gagal +- tidak ada cache file/artifact dulu +- fresh generate only + +## 3. Endpoint one-api-lab + +Operasional: + +- `POST /mockup/fo/mergeemailv1/done/mergereport` + +Input minimal: + +```json +{ + "token": "", + "T_OrderHeaderLabNumber": "IBL12345" +} +``` + +Admin create snapshot: + +- `POST /tools/ibl_merge_report_admin/create_request` + +Admin preview: + +- `POST /tools/ibl_merge_report_admin/preview` + +Public preview/stream: + +- `GET /report/{mergeRequestID}` + +## 4. Verifikasi + +Checklist setelah service Go siap: + +1. `mergereport` insert row baru di `merge_request`. +2. Row `merge_request` menyimpan `mergeRequestT_OrderHeaderID`. +3. Row `merge_request` menyimpan `mergeRequestCreatedUserID`. +4. `/report/{id}` mengembalikan PDF, bukan JSON payload lama. +5. Error dari Go dipetakan ke pesan aman dari one-api-lab. +6. Admin tool gagal jika `M_UserGroupID` tidak masuk whitelist config. diff --git a/sql/manual_changes/2026-05-29-merge-report-gateway.sql b/sql/manual_changes/2026-05-29-merge-report-gateway.sql new file mode 100644 index 00000000..4f1f24b7 --- /dev/null +++ b/sql/manual_changes/2026-05-29-merge-report-gateway.sql @@ -0,0 +1,12 @@ +ALTER TABLE merge_request + ADD COLUMN mergeRequestT_OrderHeaderID INT NULL AFTER mergeRequestID, + ADD COLUMN mergeRequestCreatedUserID INT NULL AFTER mergeRequestPayload; + +ALTER TABLE merge_request + ADD INDEX idx_merge_request_order (mergeRequestT_OrderHeaderID), + ADD INDEX idx_merge_request_created_user (mergeRequestCreatedUserID); + +ALTER TABLE conf_systems + ADD COLUMN S_SystemsMergeReportServiceBaseUrl VARCHAR(255) NULL AFTER S_SystemsMergeReportGateway, + ADD COLUMN S_SystemsMergeReportServiceSecret VARCHAR(255) NULL AFTER S_SystemsMergeReportServiceBaseUrl, + ADD COLUMN S_SystemsMergeReportAdminGroupIDs VARCHAR(255) NULL AFTER S_SystemsMergeReportServiceSecret; diff --git a/sql/manual_changes/2026-05-29-merge-report-service-config-template.sql b/sql/manual_changes/2026-05-29-merge-report-service-config-template.sql new file mode 100644 index 00000000..8c572759 --- /dev/null +++ b/sql/manual_changes/2026-05-29-merge-report-service-config-template.sql @@ -0,0 +1,6 @@ +UPDATE conf_systems +SET + S_SystemsMergeReportServiceBaseUrl = 'http://127.0.0.1:8005', + S_SystemsMergeReportServiceSecret = 'ganti-dengan-shared-secret', + S_SystemsMergeReportAdminGroupIDs = '1' +WHERE S_SystemsID = 1;