Files
BE_IBL/application/controllers/tools/Rpt_lab_result.php

472 lines
19 KiB
PHP

<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* Rpt_lab_result — FPDF-based lab result report
* Endpoint: GET /tools/rpt_lab_result/pdf?token=...&PT_OrderHeaderID=...
*
* Menggunakan sp_rpt_hasil_header untuk data pasien,
* query langsung untuk detail hasil pemeriksaan.
* Cache PDP di-populate sebelum call SP lalu dihapus setelah selesai.
*/
class Rpt_lab_result extends MY_Controller
{
public $db_onedev;
public function __construct()
{
parent::__construct();
$this->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=<id>']);
}
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(10, 8, 10);
$pdf->SetAutoPageBreak(true, 42);
$pdf->AddPage();
$pw = $pdf->GetPageWidth(); // 210
$ml = 10;
$mr = 10;
$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('Helvetica', '', 8);
$pdf->SetXY($ml, 5);
$top_right = trim($lab_number . ($form_rev ? ' ' . $form_rev : ''));
$pdf->Cell($cw, 5, $top_right, 0, 1, 'R');
// ── 3cm gap untuk kop (y=30) ─────────────────────────────────────────
$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('Helvetica', '', 10);
$pdf->SetXY($ml, 30);
$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->SetX($ml);
$pdf->Cell($cw, 0.3, '', 'T', 1);
$pdf->Ln(1);
// ── Patient info (2 columns) ──────────────────────────────────────────
$lw = $cw * 0.50;
$rw = $cw * 0.50;
$lbl_w = 38;
$col_w = 4;
$val_w = $lw - $lbl_w - $col_w;
$lbl_rw = 38;
$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 = 'Helvetica'; // match Calibri (BIRT)
$fs = 10; // 10pt = font size BIRT
$pdf->SetFont($fnt, '', $fs);
$y_info_start = $pdf->GetY();
// Barcode (teks) di atas kolom kanan
$pdf->SetXY($ml + $lw, $y_info_start);
$pdf->SetFont($fnt, 'B', 11);
$pdf->Cell($rw, 7, '||| ' . $pid . ' |||', 0, 1, 'C');
$pdf->SetFont($fnt, '', $fs);
$y_right_start = $pdf->GetY();
$y_left_cur = $y_info_start;
$y_right_cur = $y_right_start;
// Left column
foreach ($left_rows as $r) {
$y = $pdf->GetY();
$pdf->SetXY($ml, $y);
$pdf->SetFont($fnt, '', $fs);
$pdf->Cell($lbl_w, 6, $r[0], 0, 0);
$pdf->Cell($col_w, 6, ':', 0, 0, 'C');
$pdf->SetXY($ml + $lbl_w + $col_w, $y);
$pdf->MultiCell($val_w, 6, $r[1], 0, 'L');
}
$y_left_cur = $pdf->GetY();
// Right column
$pdf->SetY($y_right_cur);
foreach ($right_rows as $r) {
$y = $pdf->GetY();
$pdf->SetXY($ml + $lw, $y);
$pdf->SetFont($fnt, '', $fs);
$pdf->Cell($lbl_rw, 6, $r[0], 0, 0);
$pdf->Cell($col_w, 6, ':', 0, 0, 'C');
$pdf->SetXY($ml + $lw + $lbl_rw + $col_w, $y);
$pdf->MultiCell($val_rw, 6, $r[1], 0, 'L');
}
$y_right_cur = $pdf->GetY();
$pdf->SetY(max($y_left_cur, $y_right_cur) + 2);
$pdf->SetX($ml);
$pdf->Cell($cw, 0.3, '', 'T', 1);
$pdf->Ln(2);
// ── Table header ─────────────────────────────────────────────────────
$col = ['JENIS PEMERIKSAAN' => 67, 'HASIL' => 25, 'NILAI RUJUKAN' => 51, 'SATUAN' => 20, 'METODE' => 27];
$col_w_arr = array_values($col);
$row_h = 6;
$pdf->SetFont($fnt, 'B', $fs);
$pdf->SetFillColor(240, 240, 240);
$pdf->SetX($ml);
foreach ($col as $label => $w) {
$pdf->Cell($w, $row_h + 1, $label, '1', 0, 'C', true);
}
$pdf->Ln();
// ── Table 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)
if ($subgroup !== '' && $subgroup !== $prev_subgroup) {
$pdf->SetX($ml);
$pdf->SetFont($fnt, 'B', $fs);
$pdf->Cell($cw, $row_h, $subgroup, 'LR', 1, 'L');
$pdf->SetX($ml); $pdf->Cell($cw, 0, '', 'T', 1);
$prev_subgroup = $subgroup; $prev_subsubgroup = null;
}
// Level 2: SubSubGroup (italic, full width)
if ($subsubgroup !== '' && $subsubgroup !== $prev_subsubgroup) {
$pdf->SetX($ml);
$pdf->SetFont($fnt, 'I', $fs);
$pdf->Cell($cw, $row_h, ' ' . $this->_s($subsubgroup), 'LR', 1, 'L');
$pdf->SetX($ml); $pdf->Cell($cw, 0, '', 'T', 1);
$prev_subsubgroup = $subsubgroup;
}
// Level 3: Test row — indent via spaces (always draw from $ml 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' : '', $fs);
$y_start = $pdf->GetY();
// Hitung row height
$row_height = $row_h;
$texts = [$display_name, $result, $normal, $unit, $method];
foreach ($texts as $i => $txt) {
$avail = $col_w_arr[$i] - 2;
if ($avail > 0 && $pdf->GetStringWidth($txt) > $avail) {
$lines = (int) ceil($pdf->GetStringWidth($txt) / $avail);
$row_height = max($row_height, $lines * $row_h);
}
}
// Render cells — selalu dari $ml, full width per kolom
$x_cur = $ml;
foreach ($texts as $i => $txt) {
$w = $col_w_arr[$i];
$align = ($i === 0) ? 'L' : 'C';
$pdf->SetXY($x_cur, $y_start);
$pdf->Cell($w, $row_height, $txt, 'LR', 0, $align);
$x_cur += $w;
}
$pdf->SetXY($ml, $y_start + $row_height);
$pdf->SetX($ml); $pdf->Cell($cw, 0, '', 'T', 1);
}
// Tutup tabel (outer bottom border)
$pdf->SetX($ml);
$pdf->Cell($cw, 0, '', 'B', 1);
// ── Catatan ───────────────────────────────────────────────────────────
$pdf->Ln(3);
$pdf->SetFont($fnt, '', $fs);
$pdf->SetX($ml);
$pdf->Cell($cw, $row_h, 'Catatan :', 0, 1, 'L');
$pdf->Ln(2);
// ── Waktu Pengambilan Spesimen ────────────────────────────────────────
if (!empty($sampling)) {
$pdf->SetFont($fnt, '', $fs);
$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 _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();
}
}
}
}