db_onedev = $this->load->database('onedev', true); $this->load->library('ibl_patient_decrypt'); } public function index() { $this->sys_ok(['message' => 'Use /tools/rpt_lab_result/pdf?PT_OrderHeaderID=']); } public function pdf() { try { $order_id = $this->_get_order_id(); if ($order_id <= 0) { $this->sys_error('PT_OrderHeaderID wajib diisi'); return; } $username = trim($this->input->get('username', true) ?? $this->input->post('username', true) ?? ''); if ($username === '') { $username = $this->sys_user['M_StaffName'] ?? $this->sys_user['M_UserUsername'] ?? 'ADMIN'; } // Populate cache PDP $cache_id = $this->ibl_patient_decrypt->populate_cache_by_order($order_id); // Header data via SP $header = $this->_fetch_header($order_id, $username); if (!$header) { $this->ibl_patient_decrypt->delete_cache($cache_id); $this->sys_error('Data order tidak ditemukan'); return; } // Detail hasil pemeriksaan $details = $this->_fetch_details($order_id, $username); // Sampling data $sampling = $this->_fetch_sampling($order_id); // Company info (address, phone IBL) $company_info = $this->_fetch_company_info(); // Delete cache setelah data diambil $this->ibl_patient_decrypt->delete_cache($cache_id); // Generate PDF $pdf_bytes = $this->_build_pdf($header, $details, $sampling, $company_info, $username); $filename = 'lab_result_' . $order_id . '_' . date('Ymd') . '.pdf'; header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="' . $filename . '"'); header('Content-Length: ' . strlen($pdf_bytes)); echo $pdf_bytes; exit; } catch (Exception $e) { $this->sys_error($e->getMessage()); } } // ------------------------------------------------------------------------- // Data fetching // ------------------------------------------------------------------------- public function data() { $order_id = $this->_get_order_id(); if ($order_id <= 0) { $this->sys_error('PT_OrderHeaderID wajib'); return; } $username = $this->input->get('username', true) ?: 'ADMIN'; $details = $this->_fetch_details($order_id, $username); $this->sys_ok(['count' => count($details), 'rows' => array_slice($details, 0, 5), 'last_query' => $this->db_onedev->last_query()]); } private function _fetch_header($order_id, $username) { $qry = $this->db_onedev->query('CALL sp_rpt_hasil_header(?, ?)', [$order_id, $username]); if (!$qry) return null; $row = $qry->row_array(); if (method_exists($this, 'clean_mysqli_connection')) { $this->clean_mysqli_connection($this->db_onedev->conn_id); } else { $this->_clean_multi_result(); } return $row ?: null; } private function _fetch_sampling($order_id) { $qry = $this->db_onedev->query('CALL sp_rpt_hasil_lab_sampling(?, ?)', [$order_id, '']); if (!$qry) return []; $rows = $qry->result_array(); if (method_exists($this, 'clean_mysqli_connection')) { $this->clean_mysqli_connection($this->db_onedev->conn_id); } else { $this->_clean_multi_result(); } return $rows; } private function _fetch_details($order_id, $username = '') { $qry = $this->db_onedev->query('CALL sp_rpt_hasil_lab(?, ?)', [$order_id, $username]); if (!$qry) return []; $rows = $qry->result_array(); if (method_exists($this, 'clean_mysqli_connection')) { $this->clean_mysqli_connection($this->db_onedev->conn_id); } else { $this->_clean_multi_result(); } return $rows; } private function _fetch_company_info() { $row = $this->db_onedev->query( "SELECT S_SystemsCompanyAddress, S_SystemsCompanyCity, S_SystemsCompanyPhone, S_SystemsCompanyName FROM conf_systems LIMIT 1" )->row_array(); return $row ?: []; } // ------------------------------------------------------------------------- // PDF builder // ------------------------------------------------------------------------- private function _build_pdf(array $h, array $details, array $sampling, array $co, $username) { require_once APPPATH . 'third_party/fpdf/fpdf.php'; // Footer data $validator_name = ''; foreach ($details as $d) { if (!empty($d['M_StaffName'])) { $validator_name = $d['M_StaffName']; } } $lab_number = $this->_s($h['T_OrderHeaderLabNumber'] ?? ''); $print_username = $this->_s($username); $print_datetime = date('d-m-Y H:i:s'); $pv = $validator_name; $pu = $print_username; $pd = $print_datetime; $pln = $lab_number; $pdf = new class($pu, $pd, $pln, $pv) extends FPDF { private $pu, $pd, $pln, $pv; public function __construct($pu, $pd, $pln, $pv) { parent::__construct('P', 'mm', 'A4'); $this->pu = $pu; $this->pd = $pd; $this->pln = $pln; $this->pv = $pv; } public function Footer() { $ml = 10; $cw = $this->GetPageWidth() - 20; $this->SetFont('Helvetica', '', 10); // Validasi Oleh (kanan) $this->SetY(-38); $this->SetX($ml); $this->Cell($cw, 5, 'Validasi Oleh', 0, 1, 'R'); $this->Ln(8); // Garis tanda tangan (kanan 40%) $this->SetX($ml + $cw * 0.62); $this->Cell($cw * 0.38, 0.3, '', 'T', 1); // Nama validator (kanan) $this->SetX($ml); $this->Cell($cw, 5, iconv('UTF-8', 'windows-1252//TRANSLIT', (string) $this->pv), 0, 1, 'R'); $this->Ln(2); // Printed by (kiri) $printed = 'Printed by : ' . $this->pu . ' / ' . $this->pd . ' / ' . $this->pln; $this->SetX($ml); $this->Cell($cw, 5, $printed, 0, 0, 'L'); $this->Ln(); // Page number (tengah) $this->SetX($ml); $this->Cell($cw, 5, $this->PageNo() . ' / {nb}', 0, 0, 'C'); } }; $pdf->AliasNbPages('{nb}'); $pdf->SetMargins(9, 8, 9); $pdf->SetAutoPageBreak(true, 42); $pdf->AddPage(); $pw = $pdf->GetPageWidth(); // 210 $ml = 9; $mr = 9; $cw = $pw - $ml - $mr; // 190 // Form revision number $form_row = $this->db_onedev->query( "SELECT IFNULL(M_No_FormRev,'') AS M_No_FormRev FROM m_no_form WHERE M_No_FormName='LAB' AND M_No_FormIsActive='Y' LIMIT 1" ); $form_rev = $form_row ? $this->_s($form_row->row_array()['M_No_FormRev'] ?? '') : ''; // ── Pojok kanan atas: lab number + form rev ─────────────────────────── $pdf->SetFont('Arial', '', 7); $pdf->SetXY($ml, 4); $top_right = trim($lab_number . ($form_rev ? ' ' . $form_rev : '')); $pdf->Cell($cw, 5, $top_right, 0, 1, 'R'); // ── Kop atas ────────────────────────────────────────────────────────── $company_addr = $this->_s($co['S_SystemsCompanyAddress'] ?? 'Jl. LL. RE. Martadinata No. 135'); $company_city = $this->_s($co['S_SystemsCompanyCity'] ?? 'Bandung'); $company_phone = $this->_s($co['S_SystemsCompanyPhone'] ?? '(022)7271946'); $addr_line = $company_addr . ' ' . $company_city . ' Telp. ' . $company_phone; $pj_name = $this->_s($h['M_DoctorName'] ?? ''); $pdf->SetFont('Arial', '', 9); $pdf->SetXY($ml, 8); $pdf->Cell($cw / 2, 5, $addr_line, 0, 0, 'L'); $pdf->SetX($ml + $cw / 2); $pdf->Cell($cw / 2, 5, 'Penanggung Jawab : ' . $pj_name, 0, 1, 'R'); $pdf->SetLineWidth(0.8); $pdf->Line($ml, 13.5, $ml + $cw, 13.5); $pdf->SetLineWidth(0.2); $pdf->SetY(15); // ── Patient info (2 columns) ────────────────────────────────────────── $lw = 94; $rw = $cw - $lw; $lbl_w = 30; $col_w = 4; $val_w = $lw - $lbl_w - $col_w; $lbl_rw = 30; $val_rw = $rw - $lbl_rw - $col_w; $pid = $this->_s($h['T_OrderHeaderLabNumber'] ?? '-'); $left_rows = [ ['NO. REG', $this->_s($h['M_PatientNoReg'] ?? '-')], ['NAMA', $this->_s($h['M_PatientName'] ?? '-')], ['PENGIRIM', $this->_s($h['M_DoctorName2'] ?? '-')], ['KEL. PELANGGAN*', $this->_s($h['CorporateName'] ?? '-')], ['ALAMAT', $this->_s($h['M_PatientAddress'] ?? '-')], ]; $right_rows = [ ['TANGGAL REG', $this->_s($h['T_OrderHeaderDate'] ?? '-')], ['PID', $pid], ['JENIS KELAMIN', $this->_s($h['Gender'] ?? '-')], ['TGL. LAHIR / USIA', $this->_s($h['Umur'] ?? '-')], ['NO. TLP. / HP', $this->_s($h['M_PatientHp'] ?? '-')], ]; $fnt = 'Arial'; $fs = 8.5; $y_info_start = $pdf->GetY(); $barcode_x = $ml + $lw + 4; $barcode_y = $y_info_start; $barcode_w = 58; $barcode_h = 8; $this->_draw_fake_barcode($pdf, $barcode_x, $barcode_y, $barcode_w, $barcode_h, $pid); $y_left_cur = $y_info_start + 1; $y_right_cur = $y_info_start + 10; foreach ($left_rows as $r) { $y_left_cur = $this->_draw_header_row($pdf, $ml, $y_left_cur, $lbl_w, $col_w, $val_w, $r[0], $r[1], $fnt, $fs); } foreach ($right_rows as $r) { $y_right_cur = $this->_draw_header_row($pdf, $ml + $lw + 4, $y_right_cur, $lbl_rw, $col_w, $val_rw - 4, $r[0], $r[1], $fnt, $fs); } $pdf->SetY(max($y_left_cur, $y_right_cur) + 10); // ── Table header ───────────────────────────────────────────────────── $col = ['JENIS PEMERIKSAAN' => 64, 'HASIL' => 24, 'NILAI RUJUKAN' => 43, 'SATUAN' => 24, 'METODE' => 37]; $col_w_arr = array_values($col); $row_h = 5.2; $y_table_start = $pdf->GetY(); $pdf->SetFont($fnt, 'B', 8.8); $pdf->SetX($ml); foreach ($col as $label => $w) { $pdf->Cell($w, $row_h + 1.8, $label, '1', 0, 'C'); } $pdf->Ln(); // ── Table rows ──────────────────────────────────────────────────────── // Strategy: // - Group/SubSubGroup rows : full-width text only, tanpa border bawah // - Test rows : text per kolom + separator full-width manual // - Outer L/R/T/B : drawn via Rect() after all rows $prev_subgroup = null; $prev_subsubgroup = null; $qr_url = ''; foreach ($details as $d) { $subgroup = strtoupper(trim($d['Nat_SubGroupName'] ?? '')); $subsubgroup = trim($d['Nat_SubSubGroupName'] ?? ''); $sascode_len = strlen(trim($d['T_TestSasCode'] ?? '')); $test_name = $this->_s(trim($d['T_TestName'] ?? '')); $result = $this->_s(trim(strip_tags($d['T_OrderDetailResult'] ?? ''))); $normal = $this->_s(trim($d['T_OrderDetailNormalValueNote'] ?? $d['T_OrderDetailNormalValueDescription'] ?? '')); $unit = $this->_s(trim($d['T_OrderDetailNat_UnitName'] ?? '')); $method = $this->_s(trim($d['T_OrderdetailNat_MethodeName'] ?? $d['methodeName'] ?? '')); $flag = trim($d['T_OrderDetailResultFlag'] ?? ''); $is_abnormal = ($flag !== '' && $flag !== 'N'); if (empty($qr_url) && !empty($d['qrreport'])) { $qr_url = $d['qrreport']; } // Level 1: SubGroup — bold, full-width, tanpa separator if ($subgroup !== '' && $subgroup !== $prev_subgroup) { $pdf->SetX($ml); $pdf->SetFont($fnt, 'B', 8.8); $pdf->Cell($cw, $row_h, $subgroup, 0, 1, 'L'); $prev_subgroup = $subgroup; $prev_subsubgroup = null; } // Level 2: SubSubGroup — italic, full-width, tanpa separator if ($subsubgroup !== '' && $subsubgroup !== $prev_subsubgroup) { $pdf->SetX($ml); $pdf->SetFont($fnt, 'I', 8.6); $pdf->Cell($cw, $row_h, ' ' . $this->_s($subsubgroup), 0, 1, 'L'); $prev_subsubgroup = $subsubgroup; } // Level 3: Test row — indent via spaces, separator full-width $sp = 0; $prefix = ''; if ($sascode_len >= 10 && $sascode_len < 12) { $sp = 2; } elseif ($sascode_len >= 12 && $sascode_len < 14) { $sp = 4; $prefix = chr(183) . ' '; } elseif ($sascode_len >= 14) { $sp = 6; $prefix = chr(183) . ' '; } $display_name = str_repeat(' ', $sp) . $prefix . $test_name; $pdf->SetFont($fnt, $is_abnormal ? 'B' : '', 8.2); $y_start = $pdf->GetY(); // Hitung row height $row_height = $row_h; $texts = [$display_name, $result, $normal, $unit, $method]; foreach ($texts as $i => $txt) { $lines = $this->_estimate_text_lines($pdf, $col_w_arr[$i] - 2, $txt); $row_height = max($row_height, $lines * $row_h); } // Render cells tanpa border, lalu gambar separator full-width manual $x_cur = $ml; foreach ($texts as $i => $txt) { $w = $col_w_arr[$i]; $align = ($i === 1) ? 'C' : 'L'; $pdf->SetXY($x_cur, $y_start); $pdf->Cell($w, $row_height, $txt, 0, 0, $align); $x_cur += $w; } $pdf->Line($ml, $y_start + $row_height, $ml + $cw, $y_start + $row_height); $pdf->SetXY($ml, $y_start + $row_height); } // Outer rectangle — clean single-weight border for entire table $y_table_end = $pdf->GetY(); $pdf->Rect($ml, $y_table_start, $cw, $y_table_end - $y_table_start); // ── Catatan ─────────────────────────────────────────────────────────── $pdf->Ln(3); $pdf->SetFont($fnt, '', 8.2); $pdf->SetX($ml); $pdf->Cell($cw, $row_h, 'Catatan :', 0, 1, 'L'); $pdf->Ln(2); // ── Waktu Pengambilan Spesimen ──────────────────────────────────────── if (!empty($sampling)) { $pdf->SetFont($fnt, '', 8.2); $pdf->SetX($ml); $pdf->Cell($cw, $row_h, 'Waktu Pengambilan Spesimen', 0, 1, 'L'); foreach ($sampling as $s) { $bahan = $this->_s($s['T_BahanName'] ?? ''); $tgl = $this->_s($s['sampling_date'] ?? ''); $jam = $this->_s($s['sample_time'] ?? ''); $pdf->SetX($ml); $pdf->Cell(35, $row_h, $bahan, 0, 0, 'L'); $pdf->Cell(4, $row_h, ':', 0, 0, 'C'); $pdf->Cell(35, $row_h, $tgl, 0, 0, 'L'); $pdf->Cell(30, $row_h, $jam, 0, 1, 'L'); } } // ── QR code (absolute, 20mm di atas footer = -42-3 = -45 dari bawah) ── if (!empty($qr_url)) { $tmp = $this->_fetch_image_to_temp($qr_url); if ($tmp) { $page_h = $pdf->GetPageHeight(); $qr_y = $page_h - 42 - 3 - 20; // footer_margin=42, gap=3, qr_h=20 $pdf->Image($tmp, $ml, $qr_y, 20, 20); @unlink($tmp); } } return $pdf->Output('S'); } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- private function _get_order_id() { foreach (['PT_OrderHeaderID', 'PID', 'order_id'] as $k) { $v = $this->input->get($k, true) ?? $this->input->post($k, true) ?? ($this->sys_input[$k] ?? null); if ($v !== null && $v !== '') return intval($v); } return 0; } private function _draw_header_row($pdf, $x, $y, $label_w, $colon_w, $value_w, $label, $value, $font, $font_size) { $label = $this->_s($label); $value = $this->_s($value); $line_h = 5.2; $pdf->SetFont($font, 'B', $font_size); $pdf->SetXY($x, $y); $pdf->Cell($label_w, $line_h, $label, 0, 0, 'L'); $pdf->SetFont($font, '', $font_size); $pdf->Cell($colon_w, $line_h, ':', 0, 0, 'C'); $value_lines = $this->_estimate_text_lines($pdf, $value_w, $value); $row_h = max(1, $value_lines) * $line_h; $pdf->SetXY($x + $label_w + $colon_w, $y); $pdf->MultiCell($value_w, $line_h, $value, 0, 'L'); return $y + $row_h; } private function _estimate_text_lines($pdf, $width, $text) { $text = str_replace("\r", '', (string) $text); if ($text === '') { return 1; } $lines = explode("\n", $text); $count = 0; foreach ($lines as $line) { $line = trim($line); if ($line === '') { $count++; continue; } $words = preg_split('/\s+/', $line); $current = ''; foreach ($words as $word) { $candidate = $current === '' ? $word : $current . ' ' . $word; if ($pdf->GetStringWidth($candidate) <= $width) { $current = $candidate; continue; } if ($current !== '') { $count++; $current = $word; } else { $count += max(1, (int) ceil($pdf->GetStringWidth($word) / max($width, 1))); $current = ''; } } if ($current !== '') { $count++; } } return max(1, $count); } private function _draw_fake_barcode($pdf, $x, $y, $w, $h, $text) { $text = preg_replace('/[^A-Za-z0-9]/', '', (string) $text); if ($text === '') { return; } $sequence = '1010'; $chars = str_split(strtoupper($text)); foreach ($chars as $char) { $bits = str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT); $sequence .= $bits . '0'; } $sequence .= '10101'; $module_w = $w / strlen($sequence); $pdf->SetFillColor(0, 0, 0); for ($i = 0, $len = strlen($sequence); $i < $len; $i++) { if ($sequence[$i] !== '1') { continue; } $bar_x = $x + ($i * $module_w); $bar_h = ($i % 7 === 0) ? $h : ($h - 1.2); $pdf->Rect($bar_x, $y, max(0.35, $module_w * 0.92), $bar_h, 'F'); } } private function _s($text) { return iconv('UTF-8', 'windows-1252//TRANSLIT', (string) $text); } private function _fetch_image_to_temp($url) { $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_FOLLOWLOCATION => true]); $data = curl_exec($ch); curl_close($ch); if (!$data) return null; $tmp = tempnam(sys_get_temp_dir(), 'qr_') . '.png'; file_put_contents($tmp, $data); return $tmp; } private function _clean_multi_result() { if (isset($this->db_onedev->conn_id) && $this->db_onedev->conn_id instanceof mysqli) { while ($this->db_onedev->conn_id->more_results()) { $this->db_onedev->conn_id->next_result(); } } } }