AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold (Liberation Sans Narrow Bold) } public function Footer() { $pageW = $this->GetPageWidth() - 30; // sama dengan margin 15+15 // ── Posisi: 20mm dari bawah halaman ────────────────────────────────── $this->SetY(-20); // ── Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ─────────────── $colSide = ($pageW - 40) / 2; $colCenter = 40; $this->SetFont('Arial_Narrow', '', 7); $this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L'); $this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C'); $this->Cell($colSide, 4, '', 0, 1, 'R'); // ── Baris 2: Tgl Print (kiri) ────────────────────────────────────────── $this->SetFont('Arial_Narrow', '', 7); $this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L'); $this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L'); } // Properties untuk tabel multiline public $widths; public $aligns; public function SetWidths($w) { $this->widths = $w; } public function SetAligns($a) { $this->aligns = $a; } public function Row($data, $fill = false, $h = 6) { // Hitung tinggi maksimum baris berdasarkan multiline $nb = 0; for ($i = 0; $i < count($data); $i++) { $nb = max($nb, $this->NbLines($this->widths[$i], $data[$i])); } $rowH = $h * $nb; // Cek apakah perlu ganti halaman secara otomatis $this->CheckPageBreak($rowH); // Gambar cell pada baris for ($i = 0; $i < count($data); $i++) { $w = $this->widths[$i]; $a = isset($this->aligns[$i]) ? $this->aligns[$i] : 'L'; $x = $this->GetX(); $y = $this->GetY(); // Gambar border dan background $this->Rect($x, $y, $w, $rowH, $fill ? 'DF' : 'D'); // Tulis teks menggunakan MultiCell $this->MultiCell($w, $h, $data[$i], 0, $a); // Geser posisi X ke kanan untuk cell berikutnya $this->SetXY($x + $w, $y); } // Pindah baris $this->Ln($rowH); } public function CheckPageBreak($h) { // Jika tinggi baris melewati batas, buat halaman baru if ($this->GetY() + $h > $this->PageBreakTrigger) { $this->AddPage($this->CurOrientation); } } public function NbLines($w, $txt) { // Menghitung jumlah baris yang akan dihasilkan oleh MultiCell $cw =& $this->CurrentFont['cw']; if ($w == 0) { $w = $this->w - $this->rMargin - $this->x; } $wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize; $s = str_replace("\r", '', $txt); $nb = strlen($s); if ($nb > 0 && $s[$nb - 1] == "\n") { $nb--; } $sep = -1; $i = 0; $j = 0; $l = 0; $nl = 1; while ($i < $nb) { $c = $s[$i]; if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; } if ($c == ' ') { $sep = $i; } $l += $cw[$c]; if ($l > $wmax) { if ($sep == -1) { if ($i == $j) { $i++; } } else { $i = $sep + 1; } $sep = -1; $j = $i; $l = 0; $nl++; } else { $i++; } } return $nl; } } // ============================================================================= // Controller // ============================================================================= class Rpt_payment_voucher extends MY_Controller { // ── Properti bersama antar fungsi PDF ───────────────────────────────────── /** @var PaymentVoucherFpdf */ private $_pdf; private $_pageW; private $_header_data; private $_username; public function __construct() { parent::__construct(); } public function index() { echo "Payment Voucher Report API"; } // ========================================================================= // ENDPOINT: pdf // GET/POST: id (PaymentVoucherID), username (opsional) // ========================================================================= public function pdf() { try { $id = intval($this->input->get_post('id')); $username = trim($this->input->get_post('username') ?? ''); if ($id <= 0) { $this->sys_error("ID tidak valid"); exit; } // ── Ambil data header & detail dari DB ──────────────────────────── $header = $this->_get_header($id); $details = $this->_get_detail($id); // ── Inisialisasi FPDF custom ────────────────────────────────────── $this->_pdf = new PaymentVoucherFpdf('P', 'mm', 'A4'); $this->_pdf->printUsername = $username !== '' ? $username : '-'; $this->_pdf->printDate = date('d-m-Y H:i:s'); $this->_pageW = $this->_pdf->GetPageWidth() - 30; $this->_header_data = $header; $this->_username = $this->_pdf->printUsername; $this->_pdf->AliasNbPages(); // aktifkan alias total halaman $this->_pdf->SetMargins(15, 15, 15); $this->_pdf->SetAutoPageBreak(true, 22); // 22mm ruang footer di bawah $this->_pdf->AddPage(); // ── Susun isi halaman ───────────────────────────────────────────── $this->_pdf_header(); $this->_pdf_data($details); $this->_pdf_terms(); // ── Output ──────────────────────────────────────────────────────── $filename = 'PV_' . str_replace('/', '-', $header['PaymentVoucherNumber']) . '.pdf'; header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="' . $filename . '"'); header('Cache-Control: private, max-age=0, must-revalidate'); header('Pragma: public'); echo $this->_pdf->Output('S'); } catch (Exception $exc) { $this->sys_error($exc->getMessage()); } } // ========================================================================= // DATABASE: Ambil data header // ========================================================================= private function _get_header($id) { $sql = " SELECT pv.*, cs.coaAccountNo, cs.coaDescription, ct.coaAccountNo AS coaTempAccountNo, ct.coaDescription AS coaTempDescription, b.M_BranchName, IFNULL(uCr.M_UserUsername, '') AS CreatedByName, IFNULL(uPd.M_UserUsername, '') AS PaidByName, IFNULL(uRc.M_UserUsername, '') AS PaidReceiveByName FROM payment_voucher pv LEFT JOIN coa cs ON cs.coaID = pv.PaymentVoucherCoaSourceID LEFT JOIN coa ct ON ct.coaID = pv.PaymentVoucherCoaTemporaryID LEFT JOIN m_branch b ON b.M_BranchCode = pv.PaymentVoucherM_BranchCode LEFT JOIN m_user uCr ON uCr.M_UserID = pv.PaymentVoucherUserID LEFT JOIN m_user uPd ON uPd.M_UserID = pv.PaymentVoucherPaidUserID LEFT JOIN m_user uRc ON uRc.M_UserID = pv.PaymentVoucherPaidReceiveUserID WHERE pv.PaymentVoucherIsActive = 'Y' AND pv.PaymentVoucherID = ? LIMIT 1 "; $qry = $this->db->query($sql, array($id)); if (!$qry || $qry->num_rows() === 0) { $this->sys_error("Data Payment Voucher tidak ditemukan"); exit; } return $qry->row_array(); } private function _get_detail($id) { $sql = " SELECT pvd.*, prd.PurchaseRequestDirectNumber, prdd.PurchaseRequestDirectDescription AS ItemDescription, prdd.PurchaseRequestDirectDetailAccount AS ExpenseAccount, prdd.PurchaseRequestDirectDetailTotalRealitationPrice, prdd.PurchaseRequestDirectDetailTotalEstimationPrice, pdc.PurchaseDirectCategoryName, c.coaDescription AS ExpenseAccountName FROM payment_voucher_detail pvd JOIN purchase_request_direct prd ON prd.PurchaseRequestDirectID = pvd.PaymentVoucherDetailPurchaseRequestDirectID JOIN purchase_request_direct_detail prdd ON prdd.PurchaseRequestDirectDetailPurchaseRequestDirectID = prd.PurchaseRequestDirectID AND prdd.PurchaseRequestDirectDetailIsActive = 'Y' LEFT JOIN purchase_direct_category pdc ON pdc.PurchaseDirectCategoryID = prdd.PurchaseRequestDirectDetailPurchaseRequestDirectCategoryID LEFT JOIN coa c ON c.coaAccountNo = prdd.PurchaseRequestDirectDetailAccount AND c.coaIsActive = 'Y' WHERE pvd.PaymentVoucherDetailIsActive = 'Y' AND pvd.PaymentVoucherDetailPaymentVoucherID = ? ORDER BY pvd.PaymentVoucherDetailID ASC, prdd.PurchaseRequestDirectDetailID ASC "; $qry = $this->db->query($sql, array($id)); return $qry ? $qry->result_array() : array(); } // ========================================================================= // PDF SECTION: Header — Judul + Informasi Dokumen (2 kolom) // ========================================================================= private function _pdf_header() { $pdf = $this->_pdf; $pageW = $this->_pageW; $header = $this->_header_data; // ── Judul utama ─────────────────────────────────────────────────────── $pdf->SetFont('Arial_Narrow', 'B', 14); $pdf->Cell($pageW, 8, 'PAYMENT VOUCHER (BUKTI PENGELUARAN KAS / BANK)', 0, 1, 'L'); $pdf->SetDrawColor(0, 0, 0); $pdf->SetLineWidth(0.5); $pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY()); $pdf->Ln(2); $startY = $pdf->GetY(); $halfW = $pageW / 2; $lblW = 28; $valW = $halfW - $lblW - 4; // ── Kolom Kiri ──────────────────────────────────────────────────────── $pdf->SetY($startY); $coaSource = '-'; if (!empty($header['coaAccountNo'])) { $coaSource = $header['coaAccountNo'] . ' - ' . $header['coaDescription']; } $coaTemp = ''; if (!empty($header['coaTempAccountNo'])) { $coaTemp = $header['coaTempAccountNo'] . ' - ' . $header['coaTempDescription']; } $leftItems = array( array('Nomor', $header['PaymentVoucherNumber'], true), array('Tanggal', $this->_fmt_date($header['PaymentVoucherDate']), false), array('Sumber Dana', $coaSource, false), ); if ($coaTemp !== '') { $leftItems[] = array('Akun Transit', $coaTemp, false); } foreach ($leftItems as $item) { $pdf->SetX(15); if ($item[2]) { $pdf->SetFont('Arial_Narrow', 'B', 9); } else { $pdf->SetFont('Arial_Narrow', '', 9); } $pdf->Cell($lblW, 5, $item[0], 0, 0, 'L'); $pdf->Cell(4, 5, ':', 0, 0, 'C'); if ($item[2]) { $pdf->SetFont('Arial_Narrow', 'B', 9); } else { $pdf->SetFont('Arial_Narrow', '', 9); } if ($item[0] === 'Sumber Dana' || $item[0] === 'Akun Transit') { $pdf->MultiCell($valW, 5, $item[1], 0, 'L'); } else { $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); } } // Keterangan / Paid Note langsung di bawah Status (tanpa spasi vertikal) if (!empty($header['PaymentVoucherPaidNote'])) { $pdf->SetX(15); $pdf->SetFont('Arial_Narrow', '', 9); $pdf->Cell($lblW, 5, 'Catatan Bayar', 0, 0, 'L'); $pdf->Cell(4, 5, ':', 0, 0, 'C'); $pdf->MultiCell($valW, 5, $header['PaymentVoucherPaidNote'], 0, 'L'); } $leftY = $pdf->GetY(); // ── Kolom Kanan ─────────────────────────────────────────────────────── $pdf->SetY($startY); $rightX = 15 + $halfW; $rightItems = array( array('Cabang', $header['M_BranchName'] ?: '-'), array('Tgl Pembayaran', $this->_fmt_date($header['PaymentVoucherPaidDate'])), array('Dibuat Oleh', $header['CreatedByName'] ?: '-'), ); foreach ($rightItems as $item) { $pdf->SetX($rightX); $pdf->SetFont('Arial_Narrow', '', 9); $pdf->Cell($lblW, 5, $item[0], 0, 0, 'L'); $pdf->Cell(4, 5, ':', 0, 0, 'C'); $pdf->SetFont('Arial_Narrow', '', 9); $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); } $rightY = $pdf->GetY(); // Posisikan Y ke yang paling bawah + margin $pdf->SetY(max($leftY, $rightY) + 4); } // ========================================================================= // PDF SECTION: Data — Tabel detail item // ========================================================================= private function _pdf_data($details) { $pdf = $this->_pdf; $pageW = $this->_pageW; $header = $this->_header_data; // ── Definisi kolom: [label, lebar, align] ───────────────────────────── // Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan) // 8 + 45 + 30 + 72 + 25 = 180 $cols = array( array('No', 8, 'C'), array('No. Request Direct', 45, 'L'), array('Kategori', 30, 'L'), array('Deskripsi', 72, 'L'), array('Nominal Bayar', 25, 'R'), ); // Header kolom $pdf->SetLineWidth(0.3); // border medium $pdf->SetFont('Arial_Narrow', 'B', 8); $pdf->SetFillColor(220, 220, 220); $pdf->SetDrawColor(0, 0, 0); foreach ($cols as $c) { $pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true); } $pdf->Ln(); // Set lebar dan alignment kolom untuk metode Row() $pdf->SetWidths(array_column($cols, 1)); $pdf->SetAligns(array_column($cols, 2)); // Baris data $pdf->SetLineWidth(0.2); // border lebih tipis di data $pdf->SetFont('Arial_Narrow', '', 8); $pdf->SetFillColor(255, 255, 255); $no = 1; $totalPay = 0; foreach ($details as $d) { $amount = floatval($d['PurchaseRequestDirectDetailTotalRealitationPrice']); if ($amount <= 0) { $amount = floatval($d['PurchaseRequestDirectDetailTotalEstimationPrice']); } $row_data = array( $no, $d['PurchaseRequestDirectNumber'] ?: '-', $d['PurchaseDirectCategoryName'] ?: '-', $d['ItemDescription'] ?: '-', $this->_fmt_rp($amount), ); $pdf->Row($row_data); $totalPay += $amount; $no++; } // Baris TOTAL $span = array_sum(array_column(array_slice($cols, 0, 4), 1)); $pdf->SetLineWidth(0.3); $pdf->SetFont('Arial_Narrow', 'B', 9); // bold + size 9 agar menonjol $pdf->SetFillColor(220, 220, 220); $pdf->Cell($span, 7, 'TOTAL PEMBAYARAN', 1, 0, 'R', true); $pdf->Cell($cols[4][1], 7, $this->_fmt_rp($totalPay), 1, 0, 'R', true); $pdf->Ln(6); // Ringkasan Biaya di Kanan Bawah (sejajar dengan Nominal Bayar) $pdf->Ln(2); } // ========================================================================= // PDF SECTION: Syarat & Ketentuan // ========================================================================= private function _pdf_terms() { $pdf = $this->_pdf; $pageW = $this->_pageW; $terms = array( 'Voucher ini adalah tanda bukti pengeluaran kas/bank yang sah untuk pembayaran dokumen PR Direct di atas.', 'Penerima dana bertanggung jawab penuh atas keabsahan penggunaan dana yang diserahterimakan.', 'Realisasi pembayaran harus dilaporkan kembali ke bagian keuangan selambat-lambatnya 3 hari kerja.', 'Dokumen ini dicetak secara digital dan divalidasi oleh otorisasi sistem akuntansi ERP.' ); $pdf->SetFont('Arial_Narrow', 'B', 9); $pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L'); $pdf->SetFont('Arial_Narrow', '', 8); foreach ($terms as $i => $t) { $pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R'); $pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L'); } } // ========================================================================= // HELPER: Format tampilan // ========================================================================= private function _fmt_date($d) { if (!$d || $d === '0000-00-00') return '-'; return date('d-m-Y', strtotime($d)); } private function _fmt_datetime($d) { if (!$d || $d === '0000-00-00 00:00:00') return '-'; return date('d-m-Y H:i', strtotime($d)); } private function _fmt_num($n) { return number_format(floatval($n), 2, ',', '.'); } // format jumlah/qty: tanpa desimal private function _fmt_qty($n) { return number_format(floatval($n), 0, ',', '.'); } private function _fmt_rp($n) { return 'Rp ' . number_format(floatval($n), 2, ',', '.'); } }