From b03ae67f75f41ce974cdbe01165d917957d569e0 Mon Sep 17 00:00:00 2001 From: bintanghr Date: Fri, 17 Jul 2026 16:54:03 +0700 Subject: [PATCH] feat: report inventory --- application/controllers/report/Rpt_faktur.php | 532 ++++++++++++++++++ .../report/Rpt_pemakaian_item_divisi.php | 531 +++++++++++++++++ .../report/Rpt_penerimaan_mutasi.php | 427 ++++++++++++++ .../report/Rpt_pengeluaran_barang.php | 403 +++++++++++++ .../report/Rpt_penyerahan_mutasi.php | 429 ++++++++++++++ .../controllers/report/Rpt_request_mutasi.php | 391 +++++++++++++ .../report/Rpt_request_pengeluaran_barang.php | 410 ++++++++++++++ 7 files changed, 3123 insertions(+) create mode 100644 application/controllers/report/Rpt_faktur.php create mode 100644 application/controllers/report/Rpt_pemakaian_item_divisi.php create mode 100644 application/controllers/report/Rpt_penerimaan_mutasi.php create mode 100644 application/controllers/report/Rpt_pengeluaran_barang.php create mode 100644 application/controllers/report/Rpt_penyerahan_mutasi.php create mode 100644 application/controllers/report/Rpt_request_mutasi.php create mode 100644 application/controllers/report/Rpt_request_pengeluaran_barang.php diff --git a/application/controllers/report/Rpt_faktur.php b/application/controllers/report/Rpt_faktur.php new file mode 100644 index 0000000..5facc00 --- /dev/null +++ b/application/controllers/report/Rpt_faktur.php @@ -0,0 +1,532 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_faktur extends MY_Controller +{ + // -- Properti bersama antar fungsi PDF ------------------------------------- + /** @var FakturFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Faktur (Supplier Invoice) Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (SupplierInvoiceID), 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 FakturFpdf('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_summary(); + $this->_pdf_terms(); + + // -- Output -------------------------------------------------------- + $filename = 'INV_' . str_replace('/', '-', $header['SupplierInvoiceNumber']) . '.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 + si.*, + sup.SupplierName, + sup.SupplierAddress, + sup.SupplierPhone, + IFNULL(uCr.M_UserUsername, '') AS CreatedByName, + IFNULL(uVe.M_UserUsername, '') AS VerifiedByName, + IFNULL(uAp.M_UserUsername, '') AS ApprovedByName, + IFNULL(uPa.M_UserUsername, '') AS PaidByName, + IFNULL(uRe.M_UserUsername, '') AS ReceivedByName + FROM supplier_invoice si + LEFT JOIN supplier sup ON sup.SupplierID = si.SupplierInvoiceSupplierID + AND sup.SupplierIsActive = 'Y' + LEFT JOIN m_user uCr ON uCr.M_UserID = si.SupplierInvoiceCreatedUserID + LEFT JOIN m_user uVe ON uVe.M_UserID = si.SupplierInvoiceVerifiedUserID + LEFT JOIN m_user uAp ON uAp.M_UserID = si.SupplierInvoiceApprovedUserID + LEFT JOIN m_user uPa ON uPa.M_UserID = si.SupplierInvoicePaidUserID + LEFT JOIN m_user uRe ON uRe.M_UserID = si.SupplierInvoiceReceivedBy + WHERE si.SupplierInvoiceID = ? + AND si.SupplierInvoiceIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Faktur tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + sid.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode + FROM supplier_invoice_detail sid + LEFT JOIN m_item i ON i.M_ItemID = sid.SupplierInvoiceDetailItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = sid.SupplierInvoiceDetailItemUnitID + WHERE sid.SupplierInvoiceDetailSupplierInvoiceID = ? + AND sid.SupplierInvoiceDetailIsActive = 'Y' + ORDER BY sid.SupplierInvoiceDetailID 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, 'FAKTUR (SUPPLIER INVOICE)', 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 = 32; + $valW = $halfW - $lblW - 4; + + // -- Kolom Kiri ----------------------------------------------------------- + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor Faktur', $header['SupplierInvoiceNumber'], true), + array('Tgl Faktur', $this->_fmt_date($header['SupplierInvoiceDate']), false), + array('Tgl Jatuh Tempo', $this->_fmt_date($header['SupplierInvoiceDueDate']), false), + array('Status', $header['SupplierInvoiceStatus'], false), + array('Supplier', $header['SupplierName'] ?: '-', false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Alamat Supplier (MultiCell) + if (!empty($header['SupplierAddress'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Alamat Supplier', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->MultiCell($valW, 4, $header['SupplierAddress'], 0, 'L'); + $pdf->SetFont('Arial_Narrow', '', 9); + } + + // Keterangan + if (!empty($header['SupplierInvoiceNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->MultiCell($valW, 4, $header['SupplierInvoiceNote'], 0, 'L'); + $pdf->SetFont('Arial_Narrow', '', 9); + } + $leftY = $pdf->GetY(); + + // -- Kolom Kanan ---------------------------------------------------------- + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + // No. Supplier Invoice & tgl (jika ada) + $rightItems = array(); + if (!empty($header['SupplierInvoiceSupplierInvoiceNumber'])) { + $rightItems[] = array('No. Inv. Supplier', $header['SupplierInvoiceSupplierInvoiceNumber']); + $rightItems[] = array('Tgl Inv. Supplier', $this->_fmt_date($header['SupplierInvoiceSupplierInvoiceDate'])); + } + $rightItems[] = array('No. Delivery Order', $header['SupplierInvoiceDeliveryOrderNumber'] ?: '-'); + $rightItems[] = array('Dibuat Oleh', $header['CreatedByName'] ?: '-'); + $rightItems[] = array('Dibuat Tgl', $this->_fmt_datetime($header['SupplierInvoiceCreated'])); + + if ($header['SupplierInvoiceStatus'] === 'Verified' || in_array($header['SupplierInvoiceStatus'], ['Approved', 'Scheduled', 'Paid', 'Partially Paid'])) { + if (!empty($header['SupplierInvoiceVerifiedDate'])) { + $rightItems[] = array('Diverifikasi Oleh', $header['VerifiedByName'] ?: '-'); + $rightItems[] = array('Tgl Verifikasi', $this->_fmt_datetime($header['SupplierInvoiceVerifiedDate'])); + } + } + + if (in_array($header['SupplierInvoiceStatus'], ['Approved', 'Scheduled', 'Paid', 'Partially Paid'])) { + if (!empty($header['SupplierInvoiceApprovedDate'])) { + $rightItems[] = array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'); + $rightItems[] = array('Tgl Disetujui', $this->_fmt_datetime($header['SupplierInvoiceApprovedDate'])); + } + } + + if ($header['SupplierInvoiceStatus'] === 'Paid' || $header['SupplierInvoiceStatus'] === 'Partially Paid') { + if (!empty($header['SupplierInvoicePaidDate'])) { + $rightItems[] = array('Dibayar Oleh', $header['PaidByName'] ?: '-'); + $rightItems[] = array('Tgl Dibayar', $this->_fmt_datetime($header['SupplierInvoicePaidDate'])); + } + } + + // Tgl Terima & Penerima (jika ada) + if (!empty($header['SupplierInvoiceReceiveDate'])) { + $rightItems[] = array('Tgl Terima', $this->_fmt_date($header['SupplierInvoiceReceiveDate'])); + $rightItems[] = array('Diterima Oleh', $header['ReceivedByName'] ?: '-'); + } + + 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; + + // -- Definisi kolom: [label, lebar, align] ------------------------------- + // Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan) + // 8 + 62 + 15 + 20 + 25 + 25 + 25 = 180 + $cols = array( + array('No', 8, 'C'), + array('Deskripsi', 62, 'L'), + array('Unit', 15, 'C'), + array('Qty', 20, 'R'), + array('Harga Satuan', 25, 'R'), + array('Diskon', 25, 'R'), + array('Total', 25, 'R'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 7); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 7.5); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + + foreach ($details as $d) { + $qty = floatval($d['SupplierInvoiceDetailQty'] ?? 0); + $price = floatval($d['SupplierInvoiceDetailPrice'] ?? 0); + $discAmount = floatval($d['SupplierInvoiceDetailDiscountAmount'] ?? 0); + $total = floatval($d['SupplierInvoiceDetailTotal'] ?? 0); + $desc = $d['SupplierInvoiceDetailDescription'] ?: ($d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-')); + + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $desc, 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'),1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[4][1], 6, $this->_fmt_rp($price), 1, 0, 'R'); + $pdf->Cell($cols[5][1], 6, $discAmount > 0 ? $this->_fmt_rp($discAmount) : '-', 1, 0, 'R'); + $pdf->Cell($cols[6][1], 6, $this->_fmt_rp($total), 1, 0, 'R'); + $pdf->Ln(); + + $no++; + } + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Ringkasan Nilai (SubTotal, Pajak, Grand Total) + // ========================================================================= + private function _pdf_summary() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + $header = $this->_header_data; + + // -- Ringkasan nilai (rata kanan) ---------------------------------------- + $cW1 = 50; + $cW2 = 40; + $offsetX = 15 + $this->_pageW - $cW1 - $cW2; + + // Garis pemisah + $pdf->SetDrawColor(0, 0, 0); + $pdf->SetLineWidth(0.3); + $pdf->Line(15 + $pageW - $cW1 - $cW2 - 4, $pdf->GetY(), 15 + $pageW, $pdf->GetY()); + $pdf->Ln(2); + + $items = array(); + + // Sub Total + $items[] = array('Sub Total', $this->_fmt_rp($header['SupplierInvoiceSubTotal'])); + + // Diskon (jika ada) + $discPct = floatval($header['SupplierInvoiceDiscountPercent'] ?? 0); + $discAmt = floatval($header['SupplierInvoiceDiscountAmount'] ?? 0); + if ($discPct > 0 || $discAmt > 0) { + $label = 'Diskon'; + if ($discPct > 0) $label .= ' (' . $this->_fmt_num($discPct) . '%)'; + $items[] = array($label, $this->_fmt_rp($discAmt)); + } + + // PPh (jika ada) + $pphPct = floatval($header['SupplierInvoiceTaxPercentPph'] ?? 0); + $pphAmt = floatval($header['SupplierInvoiceTaxAmountPph'] ?? 0); + if ($pphPct > 0 || $pphAmt > 0) { + $label = 'PPh'; + if ($pphPct > 0) $label .= ' (' . $this->_fmt_num($pphPct) . '%)'; + $items[] = array($label, $this->_fmt_rp($pphAmt)); + } + + // PPN (jika ada) + $ppnPct = floatval($header['SupplierInvoiceTaxPercentPpn'] ?? 0); + $ppnAmt = floatval($header['SupplierInvoiceTaxAmountPpn'] ?? 0); + if ($ppnPct > 0 || $ppnAmt > 0) { + $label = 'PPN'; + if ($ppnPct > 0) $label .= ' (' . $this->_fmt_num($ppnPct) . '%)'; + $items[] = array($label, $this->_fmt_rp($ppnAmt)); + } + + // Biaya Kirim (jika ada) + $shipCost = floatval($header['SupplierInvoiceShippingCost'] ?? 0); + if ($shipCost > 0) { + $items[] = array('Biaya Kirim', $this->_fmt_rp($shipCost)); + } + + // Penyesuaian (jika ada) + $adjAmt = floatval($header['SupplierInvoiceAdjustmentAmount'] ?? 0); + if ($adjAmt != 0) { + $label = 'Penyesuaian'; + if (!empty($header['SupplierInvoiceAdjustmentNote'])) { + $label .= ' (' . $header['SupplierInvoiceAdjustmentNote'] . ')'; + } + $items[] = array($label, $this->_fmt_rp($adjAmt)); + } + + foreach ($items as $item) { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW1, 5, $item[0], 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW2, 5, $item[1], 0, 1, 'R'); + } + + // Grand Total (tebal) + $pdf->SetX($offsetX); + $pdf->SetDrawColor(0, 0, 0); + $pdf->SetLineWidth(0.3); + $pdf->Line($offsetX, $pdf->GetY(), 15 + $pageW, $pdf->GetY()); + $pdf->Ln(1); + + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', 'B', 10); + $pdf->Cell($cW1, 6, 'Grand Total', 0, 0, 'L'); + $pdf->Cell($cW2, 6, $this->_fmt_rp($header['SupplierInvoiceGrandTotal']), 0, 1, 'R'); + + // Sudah dibayar / sisa + $paidAmt = floatval($header['SupplierInvoicePaidAmount'] ?? 0); + $unpaidAmt = floatval($header['SupplierInvoiceUnpaid'] ?? 0); + $isLunas = $header['SupplierInvoiceIsLunas'] ?? 'N'; + + if ($paidAmt > 0) { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW1, 5, 'Dibayar', 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW2, 5, $this->_fmt_rp($paidAmt), 0, 1, 'R'); + } + + if ($isLunas === 'Y') { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', 'B', 9); + $pdf->SetTextColor(0, 128, 0); + $pdf->Cell($cW1 + $cW2, 5, 'LUNAS', 0, 1, 'R'); + $pdf->SetTextColor(0, 0, 0); + } elseif ($unpaidAmt > 0) { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->Cell($cW1, 5, 'Sisa Tagihan', 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->Cell($cW2, 5, $this->_fmt_rp($unpaidAmt), 0, 1, 'R'); + } + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Faktur ini diterbitkan berdasarkan barang/jasa yang telah diterima dan sesuai dengan purchase order.', + 'Pembayaran harus dilakukan sesuai dengan tanggal jatuh tempo yang tercantum.', + 'Keterlambatan pembayaran akan dikenakan sanksi sesuai ketentuan yang berlaku.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_pemakaian_item_divisi.php b/application/controllers/report/Rpt_pemakaian_item_divisi.php new file mode 100644 index 0000000..406d857 --- /dev/null +++ b/application/controllers/report/Rpt_pemakaian_item_divisi.php @@ -0,0 +1,531 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_pemakaian_item_divisi extends MY_Controller +{ + // -- Properti bersama antar fungsi PDF ------------------------------------- + /** @var PemakaianItemDivisiFpdf */ + private $_pdf; + private $_pageW; + private $_start_date; + private $_end_date; + private $_division_id; + private $_division_name; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Pemakaian Item per Divisi Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: start_date, end_date, division_id (opsional), username (opsional) + // ========================================================================= + public function pdf() + { + try { + $start_date = trim($this->input->get_post('start_date') ?? ''); + $end_date = trim($this->input->get_post('end_date') ?? ''); + $division_id = intval($this->input->get_post('division_id')); + $username = trim($this->input->get_post('username') ?? ''); + + if ($start_date === '' || $end_date === '') { + $this->sys_error("Periode tanggal harus diisi"); + exit; + } + + // -- Ambil data dari DB -------------------------------------------- + $data = $this->_get_data($start_date, $end_date, $division_id); + if (empty($data)) { + $this->sys_error("Tidak ada data pemakaian item pada periode tersebut"); + exit; + } + + $this->_start_date = $start_date; + $this->_end_date = $end_date; + $this->_division_id = $division_id; + + // Ambil nama divisi jika dipilih + if ($division_id > 0) { + $div = $this->_get_division($division_id); + $this->_division_name = $div ? $div['DivisionName'] : ''; + } else { + $this->_division_name = 'Semua Divisi'; + } + + // -- Inisialisasi FPDF custom -------------------------------------- + $this->_pdf = new PemakaianItemDivisiFpdf('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->_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($data); + $this->_pdf_terms(); + + // -- Output -------------------------------------------------------- + $filename = 'PID_' . date('Ymd') . '.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 divisi + // ========================================================================= + private function _get_division($id) + { + $sql = " + SELECT DivisionID, DivisionCode, DivisionName + FROM division + WHERE DivisionID = ? + AND DivisionIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + return $qry && $qry->num_rows() > 0 ? $qry->row_array() : null; + } + + // ========================================================================= + // DATABASE: Ambil data pemakaian item per divisi + // ========================================================================= + private function _get_data($start_date, $end_date, $division_id = 0) + { + $sql_where = " + WHERE iu.ItemUsedIsActive = 'Y' + AND iu.ItemUsedIsConfirm = 'Y' + AND iu.ItemUsedDate BETWEEN ? AND ? + "; + $params = array($start_date, $end_date); + + if ($division_id > 0) { + // Filter by division melalui item_usage + $sql_where .= " AND iu.ItemUsedItemUsageID IN ( + SELECT ItemUsageID FROM item_usage + WHERE ItemUsageDivisionID = ? + AND ItemUsageIsActive = 'Y' + )"; + $params[] = $division_id; + } + + $sql = " + SELECT + d.DivisionID, + d.DivisionCode, + d.DivisionName, + iu.ItemUsedID, + iu.ItemUsedDate, + iu.ItemUsedNumber, + iu.ItemUsedBatchNo, + iu.ItemUsedQty, + iu.ItemUsedPrice, + iu.ItemUsedTotal, + item.M_ItemID, + item.M_ItemCode, + item.M_ItemDesc, + iunit.ItemUnitName, + iunit.ItemUnitCode, + us.ItemUsageID, + us.ItemUsageDivisionID + FROM item_used iu + JOIN item_usage us ON us.ItemUsageID = iu.ItemUsedItemUsageID + AND us.ItemUsageIsActive = 'Y' + JOIN division d ON d.DivisionID = us.ItemUsageDivisionID + AND d.DivisionIsActive = 'Y' + JOIN m_item item ON item.M_ItemID = iu.ItemUsedM_ItemID + AND item.M_ItemIsActive = 'Y' + JOIN itemunit iunit ON iunit.ItemUnitID = iu.ItemUsedItemUnitID + AND iunit.ItemUnitIsActive = 'Y' + $sql_where + ORDER BY d.DivisionCode ASC, d.DivisionName ASC, iu.ItemUsedDate ASC, iu.ItemUsedNumber ASC + "; + $qry = $this->db->query($sql, $params); + return $qry ? $qry->result_array() : array(); + } + + // ========================================================================= + // PDF SECTION: Header — Judul + Informasi Laporan + // ========================================================================= + private function _pdf_header() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + // -- Judul utama ------------------------------------------------------- + $pdf->SetFont('Arial_Narrow', 'B', 14); + $pdf->Cell($pageW, 8, 'LAPORAN PEMAKAIAN ITEM PER DIVISI', 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 = 30; + $valW = $halfW - $lblW - 4; + + // -- Kolom Kiri -------------------------------------------------------- + $pdf->SetY($startY); + + $leftItems = array( + array('Periode Awal', $this->_fmt_date($this->_start_date), false), + array('Periode Akhir', $this->_fmt_date($this->_end_date), false), + array('Divisi', $this->_division_name, true), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + $leftY = $pdf->GetY(); + + // -- Kolom Kanan ------------------------------------------------------- + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Print Oleh', $this->_username), + array('Tgl Print', $this->_pdf->printDate), + ); + + 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 pemakaian item per divisi + // ========================================================================= + private function _pdf_data($data) + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + // -- Definisi kolom: [label, lebar, align] ----------------------------- + // Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan) + // 8 + 8 + 68 + 15 + 15 + 22 + 22 + 22 = 180 + $cols = array( + array('No', 6, 'C'), + array('Tgl', 16, 'C'), + array('Nama Item', 62, 'L'), + array('Batch No', 24, 'C'), + array('Unit', 14, 'C'), + array('Qty', 10, 'R'), + array('Harga', 18, 'R'), + array('Total', 30, 'R'), + ); + + $no = 1; + $prevDivID = null; + $divTotalQty = 0; + $divTotalVal = 0; + $grandTotalQty = 0; + $grandTotalVal = 0; + + // Simpan posisi awal Y untuk setiap divisi — dipakai untuk bounding box + $divStartY = 0; + + foreach ($data as $d) { + $divID = $d['DivisionID']; + + // -- Jika berganti divisi, cetak subtotal divisi sebelumnya ---------- + if ($prevDivID !== null && $divID !== $prevDivID) { + $this->_pdf_divisi_subtotal($divTotalQty, $divTotalVal); + $grandTotalQty += $divTotalQty; + $grandTotalVal += $divTotalVal; + $divTotalQty = 0; + $divTotalVal = 0; + } + + // -- Jika divisi baru, cetak header nama divisi --------------------- + if ($prevDivID === null || $divID !== $prevDivID) { + // Cek sisa ruang: butuh minimal 8+7+6*n+8 ˜ 40mm untuk header+1 baris+subtotal + if ($pdf->GetY() > 230) { + $pdf->AddPage(); + } + + $divStartY = $pdf->GetY(); + + // Nama Divisi + $pdf->SetFillColor(50, 80, 130); + $pdf->SetTextColor(255, 255, 255); + $pdf->SetFont('Arial_Narrow', 'B', 10); + $pdf->Cell($pageW, 7, ' ' . $d['DivisionName'] . ' (' . $d['DivisionCode'] . ')', 1, 1, 'L', true); + $pdf->SetTextColor(0, 0, 0); + + // Header kolom + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 7); + $pdf->SetFillColor(220, 220, 220); + $pdf->SetDrawColor(0, 0, 0); + foreach ($cols as $c) { + $pdf->Cell($c[1], 6, $c[0], 1, 0, 'C', true); + } + $pdf->Ln(); + + $no = 1; + } + + // -- Cek apakah perlu halaman baru (min 12mm untuk 1 baris + subtotal) - + if ($pdf->GetY() > 258) { + $pdf->AddPage(); + // Ulang header kolom di halaman baru + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 7); + $pdf->SetFillColor(220, 220, 220); + $pdf->SetDrawColor(0, 0, 0); + foreach ($cols as $c) { + $pdf->Cell($c[1], 6, $c[0], 1, 0, 'C', true); + } + $pdf->Ln(); + } + + // -- Baris data item ----------------------------------------------- + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 7.5); + $pdf->SetFillColor(255, 255, 255); + + $itemDesc = $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'); + $batchNo = $d['ItemUsedBatchNo'] ?: '-'; + $qty = floatval($d['ItemUsedQty']); + $price = floatval($d['ItemUsedPrice']); + $total = floatval($d['ItemUsedTotal']); + + // Simpan posisi Y sebelum cell untuk wrap detection + $beforeY = $pdf->GetY(); + + $pdf->Cell($cols[0][1], 5, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 5, $this->_fmt_date($d['ItemUsedDate']), 1, 0, 'C'); + $pdf->Cell($cols[2][1], 5, $itemDesc, 1, 0, 'L'); + $pdf->Cell($cols[3][1], 5, $batchNo, 1, 0, 'C'); + $pdf->Cell($cols[4][1], 5, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[5][1], 5, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[6][1], 5, $this->_fmt_rp($price), 1, 0, 'R'); + $pdf->Cell($cols[7][1], 5, $this->_fmt_rp($total), 1, 0, 'R'); + $pdf->Ln(); + + $divTotalQty += $qty; + $divTotalVal += $total; + $prevDivID = $divID; + $no++; + } + + // -- Subtotal divisi terakhir ----------------------------------------- + if ($prevDivID !== null) { + $this->_pdf_divisi_subtotal($divTotalQty, $divTotalVal); + $grandTotalQty += $divTotalQty; + $grandTotalVal += $divTotalVal; + } + + // -- Grand Total ------------------------------------------------------ + $pdf->Ln(3); + + // Cek ruang untuk grand total + if ($pdf->GetY() > 270) { + $pdf->AddPage(); + } + + $pdf->SetLineWidth(0.4); + $pdf->SetFont('Arial_Narrow', 'B', 10); + $pdf->SetFillColor(180, 200, 220); + $pdf->SetDrawColor(0, 0, 0); + + $spanSum = array_sum(array_column(array_slice($cols, 0, 5), 1)); + $pdf->Cell($spanSum, 7, 'GRAND TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[5][1], 7, $this->_fmt_qty($grandTotalQty), 1, 0, 'R', true); + $pdf->Cell($cols[6][1], 7, '', 1, 0, 'R', true); + $pdf->Cell($cols[7][1], 7, $this->_fmt_rp($grandTotalVal), 1, 0, 'R', true); + $pdf->Ln(); + + $pdf->Ln(4); + + // -- Ringkasan Akhir -------------------------------------------------- + $summaryX = 15 + $pageW - 90; + $cW1 = 50; + $cW2 = 40; + + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->SetX($summaryX); + $pdf->Cell($cW1, 5, 'Total Item Digunakan', 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', 'B', 9); + $pdf->Cell($cW2, 5, $this->_fmt_qty($grandTotalQty) . ' ' . 'pcs', 0, 1, 'R'); + + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->SetX($summaryX); + $pdf->Cell($cW1, 5, 'Total Nilai Pemakaian', 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', 'B', 9); + $pdf->Cell($cW2, 5, $this->_fmt_rp($grandTotalVal), 0, 1, 'R'); + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SUB-SECTION: Subtotal per Divisi + // ========================================================================= + private function _pdf_divisi_subtotal($qty, $val) + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + // Gunakan lebar kolom yang sama persis dengan _pdf_data() + $cols = array( + array('No', 6, 'C'), + array('Tgl', 16, 'C'), + array('Nama Item', 62, 'L'), + array('Batch No', 24, 'C'), + array('Unit', 14, 'C'), + array('Qty', 10, 'R'), + array('Harga', 18, 'R'), + array('Total', 30, 'R'), + ); + + if ($pdf->GetY() > 270) { + $pdf->AddPage(); + } + + $spanSum = array_sum(array_column(array_slice($cols, 0, 5), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->SetFillColor(235, 240, 248); + $pdf->SetDrawColor(0, 0, 0); + $pdf->Cell($spanSum, 6, 'Sub Total', 1, 0, 'R', true); + $pdf->Cell($cols[5][1], 6, $this->_fmt_qty($qty), 1, 0, 'R', true); + $pdf->Cell($cols[6][1], 6, '', 1, 0, 'R', true); + $pdf->Cell($cols[7][1], 6, $this->_fmt_rp($val), 1, 0, 'R', true); + $pdf->Ln(); + $pdf->Ln(2); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Laporan ini menampilkan seluruh pemakaian item yang telah dikonfirmasi pada periode yang dipilih.', + 'Data pemakaian dikelompokkan berdasarkan divisi yang melakukan pemakaian.', + 'Harga yang ditampilkan adalah harga rata-rata pada saat pemakaian item.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan monitoring pemakaian item.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_penerimaan_mutasi.php b/application/controllers/report/Rpt_penerimaan_mutasi.php new file mode 100644 index 0000000..fbbaefc --- /dev/null +++ b/application/controllers/report/Rpt_penerimaan_mutasi.php @@ -0,0 +1,427 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_penerimaan_mutasi extends MY_Controller +{ + // ── Properti bersama antar fungsi PDF ───────────────────────────────────── + /** @var MutasiPenerimaanFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Mutasi Penerimaan (Receive Mutasi) Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (MutasiHandoverID), 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); + + // Validasi: hanya dokumen berstatus Received yang bisa dicetak + if ($header['MutasiHandoverStatus'] !== 'Received') { + $this->sys_error("Dokumen belum diterima (Status: " . $header['MutasiHandoverStatus'] . ")"); + exit; + } + + $details = $this->_get_detail($id); + + // ── Inisialisasi FPDF custom ────────────────────────────────────── + $this->_pdf = new MutasiPenerimaanFpdf('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 = 'PM_' . str_replace('/', '-', $header['MutasiHandoverNumber']) . '.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 + mh.*, + mr.MutasiRequestNumber, + ic.itemCategoryID, + ic.itemCategoryName, + a.M_BranchID AS branch_asal_id, + a.M_BranchCode AS branch_asal_code, + a.M_BranchName AS branch_asal_name, + b.M_BranchID AS branch_tujuan_id, + b.M_BranchCode AS branch_tujuan_code, + b.M_BranchName AS branch_tujuan_name, + IFNULL(u.M_UserUsername, '') AS CreatedByName, + IFNULL(ur.M_UserUsername, '') AS ReceiveByName, + CASE + WHEN wh.WarehouseType = 'B' THEN CONCAT(wh.WarehouseCode, ' ', wh.WarehouseName, ' - ', b.M_BranchName) + ELSE wh.WarehouseName + END AS WarehouseName, + r.M_RuanganName + FROM mutasi_handover mh + LEFT JOIN mutasi_request mr ON mr.MutasiRequestID = mh.MutasiHandoverMutasiRequestID + JOIN item_category ic ON ic.itemCategoryID = mh.MutasiHandoverItemCategoryID + AND ic.itemCategoryIsActive = 'Y' + JOIN m_branch a ON a.M_BranchID = mh.MutasiHandoverFromBranchID + AND a.M_BranchIsActive = 'Y' + JOIN m_branch b ON b.M_BranchID = mh.MutasiHandoverToBranchID + AND b.M_BranchIsActive = 'Y' + LEFT JOIN m_user u ON u.M_UserID = mh.MutasiHandoverUserID + LEFT JOIN m_user ur ON ur.M_UserID = mh.MutasiHandoverReceiveUserID + LEFT JOIN warehouse wh ON wh.WarehouseID = mh.MutasiHandoverReceiveWarehouseID + AND wh.WarehouseIsActive = 'Y' + LEFT JOIN m_ruangan r ON r.M_RuanganID = mh.MutasiHandoverReceiveM_RuanganID + AND r.M_RuanganIsActive = 'Y' + WHERE mh.MutasiHandoverID = ? + AND mh.MutasiHandoverIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Penerimaan Mutasi tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + mhd.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode, + st.StockStockNumber AS StockToNumber, + sf.StockStockNumber AS StockFromNumber + FROM mutasi_handover_detail mhd + LEFT JOIN m_item i ON i.M_ItemID = mhd.MutasiHandoverDetailM_ItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = mhd.MutasiHandoverDetailItemUnitID + LEFT JOIN stock sf ON sf.StockID = mhd.MutasiHandoverDetailStockID_From + LEFT JOIN stock st ON st.StockID = mhd.MutasiHandoverDetailStockID_To + WHERE mhd.MutasiHandoverDetailMutasiHandoverID = ? + AND mhd.MutasiHandoverDetailIsActive = 'Y' + ORDER BY mhd.MutasiHandoverDetailID 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, 'PENERIMAAN MUTASI', 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 = 30; + $valW = $halfW - $lblW - 4; + + // ── Kolom Kiri ──────────────────────────────────────────────────────── + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor Handover', $header['MutasiHandoverNumber'], true), + array('No. Request', $header['MutasiRequestNumber'] ?: '-', false), + array('Tgl Kirim', $this->_fmt_date($header['MutasiHandoverDate']), false), + array('Tgl Terima', $this->_fmt_date($header['MutasiHandoverReceiveDate']), false), + array('Status', $header['MutasiHandoverStatus'], false), + array('Kategori', $header['itemCategoryName'], false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Keterangan (Note) langsung di bawah Kategori + if (!empty($header['MutasiHandoverNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->MultiCell($valW, 5, $header['MutasiHandoverNote'], 0, 'L'); + } + $leftY = $pdf->GetY(); + + // ── Kolom Kanan ─────────────────────────────────────────────────────── + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Cabang Asal', $header['branch_asal_name']), + array('Cabang Tujuan', $header['branch_tujuan_name']), + array('Warehouse Tujuan', $header['WarehouseName'] ?: '-'), + array('Ruangan Tujuan', $header['M_RuanganName'] ?: '-'), + array('Diterima Oleh', $header['MutasiHandoverStatus'] === 'Received' ? $header['ReceiveByName'] : '-'), + 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 + ringkasan nilai + // ========================================================================= + 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 + 72 + 15 + 15 + 22 + 22 + 26 = 180 + $cols = array( + array('No', 8, 'C'), + array('Nama Item', 72, 'L'), + array('Unit', 15, 'C'), + array('Qty', 15, 'R'), + array('Nilai Buku', 22, 'R'), + array('Total', 22, 'R'), + array('Stock Tujuan', 26, 'C'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 7); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 7.5); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + $total_val = 0; + + foreach ($details as $d) { + $bookValue = floatval($d['MutasiHandoverDetailBookValue'] ?? 0); + $qty = floatval($d['MutasiHandoverDetailQty'] ?? 0); + $lineTotal = $qty * $bookValue; + $stockTo = $d['StockToNumber'] ?: '-'; + + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R'); + $pdf->Cell($cols[5][1], 6, $this->_fmt_rp($lineTotal), 1, 0, 'R'); + $pdf->Cell($cols[6][1], 6, $stockTo, 1, 0, 'C'); + $pdf->Ln(); + + $total_val += $lineTotal; + $no++; + } + + // Baris TOTAL + $span = array_sum(array_column(array_slice($cols, 0, 6), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->SetFillColor(220, 220, 220); + $pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[6][1], 7, '', 1, 0, 'C', true); + $pdf->Ln(); + + // ── Ringkasan nilai (rata kanan) ────────────────────────────────────── + $pdf->Ln(3); + $summary = array( + array('Total Nilai Penerimaan', $this->_fmt_rp($header['MutasiHandoverTotalValue'])), + ); + + $cW1 = 50; + $cW2 = 40; + $offsetX = 15 + $this->_pageW - $cW1 - $cW2; + + foreach ($summary as $s) { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW1, 5, $s[0], 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->Cell($cW2, 5, $s[1], 0, 1, 'R'); + } + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Penerimaan mutasi ini dilakukan berdasarkan dokumen handover (penyerahan) yang telah dikirim oleh cabang asal.', + 'Penerima wajib memeriksa kesesuaian jumlah, jenis, dan kondisi barang dengan dokumen handover.', + 'Setelah diterima, barang menjadi tanggung jawab cabang tujuan dan akan dicatat dalam stok tujuan.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_pengeluaran_barang.php b/application/controllers/report/Rpt_pengeluaran_barang.php new file mode 100644 index 0000000..07d2a76 --- /dev/null +++ b/application/controllers/report/Rpt_pengeluaran_barang.php @@ -0,0 +1,403 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_pengeluaran_barang extends MY_Controller +{ + // -- Properti bersama antar fungsi PDF ------------------------------------- + /** @var PengeluaranBarangFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Pengeluaran Barang Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (T_ItemOutID), 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 PengeluaranBarangFpdf('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 = 'IO_' . str_replace('/', '-', $header['T_ItemOutNumber']) . '.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 + io.*, + IFNULL(u.M_UserUsername, '') AS CreatedByName, + IFNULL(ur.M_UserUsername, '') AS ReceiveByName, + IFNULL(ua.M_UserUsername, '') AS ApprovedByName, + wh.WarehouseCode, + wh.WarehouseName, + wh.WarehouseType, + d.DivisionCode, + d.DivisionName + FROM t_item_out io + LEFT JOIN m_user u ON u.M_UserID = io.T_ItemOutUserID + LEFT JOIN m_user ur ON ur.M_UserID = io.T_ItemOutReceiveUserID + LEFT JOIN m_user ua ON ua.M_UserID = io.T_ItemOutApproveID + LEFT JOIN warehouse wh ON wh.WarehouseID = io.T_ItemOutWarehouseID + AND wh.WarehouseIsActive = 'Y' + LEFT JOIN division d ON d.DivisionID = io.T_ItemOutDivisionID + AND d.DivisionIsActive = 'Y' + WHERE io.T_ItemOutID = ? + AND io.T_ItemOutIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Pengeluaran Barang tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + iod.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode, + rod.RequestItemOutDetailQty AS RequestQty, + rod.RequestItemOutDetailStatus AS RequestStatus + FROM t_item_out_detail iod + LEFT JOIN m_item i ON i.M_ItemID = iod.T_ItemOutDetailM_ItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = iod.T_ItemOutDetailItemUnitID + LEFT JOIN request_item_out_detail rod + ON rod.RequestItemOutDetailID = iod.T_ItemOutDetailRequestItemOutDetailID + AND rod.RequestItemOutDetailIsActive = 'Y' + WHERE iod.T_ItemOutDetailT_ItemOutID = ? + AND iod.T_ItemOutDetailIsActive = 'Y' + ORDER BY iod.T_ItemOutDetailID 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, 'PENGELUARAN BARANG (ITEM OUT)', 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 = 30; + $valW = $halfW - $lblW - 4; + + // -- Kolom Kiri -------------------------------------------------------- + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor IO', $header['T_ItemOutNumber'], true), + array('Tanggal', $this->_fmt_date($header['T_ItemOutDate']), false), + array('Status', $header['T_ItemOutStatus'], false), + array('Confirm', $header['T_ItemOutIsConfirm'] === 'Y' ? 'Ya' : 'Tidak', false), + array('Divisi', $header['DivisionName'] ?: '-', false), + array('Gudang', $header['WarehouseName'] ?: '-', false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Keterangan (Note) langsung di bawah field terakhir + if (!empty($header['T_ItemOutNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->MultiCell($valW, 5, $header['T_ItemOutNote'], 0, 'L'); + } + $leftY = $pdf->GetY(); + + // -- Kolom Kanan ------------------------------------------------------- + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Dibuat Oleh', $header['CreatedByName'] ?: '-'), + array('Dibuat Tgl', $this->_fmt_datetime($header['T_ItemOutCreated'])), + array('Penerima', $header['ReceiveByName'] ?: '-'), + array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'), + array('Tgl Approve', $header['T_ItemOutApproveDate'] ? $this->_fmt_datetime($header['T_ItemOutApproveDate']) : '-'), + array('Tgl Dikonfirmasi', $header['T_ItemOutIsConfirmDate'] ? $this->_fmt_datetime($header['T_ItemOutIsConfirmDate']) : '-'), + ); + + 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; + + // -- Definisi kolom: [label, lebar, align] ----------------------------- + // Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan) + // 8 + 72 + 20 + 20 + 30 + 30 = 180 + $cols = array( + array('No', 8, 'C'), + array('Nama Item', 72, 'L'), + array('Unit', 20, 'C'), + array('Qty', 20, 'R'), + array('Batch No.', 30, 'C'), + array('Qty Request', 30, 'R'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + $total_qty = 0; + $total_req = 0; + + foreach ($details as $d) { + $qty = floatval($d['T_ItemOutDetailQty'] ?? 0); + $qtyReq = floatval($d['T_ItemOutDetailRequestItemOutDetailID'] > 0 ? $qty : 0); + $batchNo = $d['T_ItemOutDetailItemBatchNo'] ?? ''; + // Jika batch no berupa JSON array, decode untuk ditampilkan + $batchDisplay = $batchNo; + if (strpos($batchNo, '[') === 0) { + $decoded = json_decode($batchNo, true); + if (is_array($decoded) && count($decoded) > 0) { + $parts = array(); + foreach ($decoded as $b) { + $parts[] = isset($b['batchNo']) ? $b['batchNo'] : (isset($b['BatchNo']) ? $b['BatchNo'] : (is_string($b) ? $b : '-')); + } + $batchDisplay = implode(', ', $parts); + } + } + + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[4][1], 6, $batchDisplay, 1, 0, 'C'); + $reqQty = isset($d['RequestQty']) ? floatval($d['RequestQty']) : 0; + $pdf->Cell($cols[5][1], 6, $reqQty > 0 ? $this->_fmt_qty($reqQty) : '-', 1, 0, 'R'); + $pdf->Ln(); + + $total_qty += $qty; + if ($reqQty > 0) { + $total_req += $reqQty; + } + $no++; + } + + // Baris TOTAL + $span = array_sum(array_column(array_slice($cols, 0, 4), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->SetFillColor(220, 220, 220); + $pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[4][1], 7, '', 1, 0, 'C', true); + $pdf->Cell($cols[5][1], 7, $this->_fmt_qty($total_qty), 1, 0, 'R', true); + $pdf->Ln(); + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Barang yang dikeluarkan harus sesuai dengan jumlah dan spesifikasi yang tercantum dalam dokumen ini.', + 'Penerima barang wajib memeriksa dan memverifikasi kesesuaian barang sebelum menerima.', + 'Dokumen ini merupakan bukti sah pengeluaran barang dari gudang.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_penyerahan_mutasi.php b/application/controllers/report/Rpt_penyerahan_mutasi.php new file mode 100644 index 0000000..8882c32 --- /dev/null +++ b/application/controllers/report/Rpt_penyerahan_mutasi.php @@ -0,0 +1,429 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_penyerahan_mutasi extends MY_Controller +{ + // ── Properti bersama antar fungsi PDF ───────────────────────────────────── + /** @var MutasiHandoverFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Mutasi Handover (Penyerahan Mutasi) Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (MutasiHandoverID), 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 MutasiHandoverFpdf('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 = 'PH_' . str_replace('/', '-', $header['MutasiHandoverNumber']) . '.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 + mh.*, + mr.MutasiRequestNumber, + ic.itemCategoryID, + ic.itemCategoryName, + a.M_BranchID AS branch_asal_id, + a.M_BranchCode AS branch_asal_code, + a.M_BranchName AS branch_asal_name, + b.M_BranchID AS branch_tujuan_id, + b.M_BranchCode AS branch_tujuan_code, + b.M_BranchName AS branch_tujuan_name, + IFNULL(u.M_UserUsername, '') AS CreatedByName, + IFNULL(ur.M_UserUsername, '') AS ReceiveByName, + CASE + WHEN wh.WarehouseType = 'B' THEN CONCAT(wh.WarehouseCode, ' ', wh.WarehouseName, ' - ', b.M_BranchName) + ELSE wh.WarehouseName + END AS WarehouseName, + r.M_RuanganName + FROM mutasi_handover mh + LEFT JOIN mutasi_request mr ON mr.MutasiRequestID = mh.MutasiHandoverMutasiRequestID + JOIN item_category ic ON ic.itemCategoryID = mh.MutasiHandoverItemCategoryID + AND ic.itemCategoryIsActive = 'Y' + JOIN m_branch a ON a.M_BranchID = mh.MutasiHandoverFromBranchID + AND a.M_BranchIsActive = 'Y' + JOIN m_branch b ON b.M_BranchID = mh.MutasiHandoverToBranchID + AND b.M_BranchIsActive = 'Y' + LEFT JOIN m_user u ON u.M_UserID = mh.MutasiHandoverUserID + LEFT JOIN m_user ur ON ur.M_UserID = mh.MutasiHandoverReceiveUserID + LEFT JOIN warehouse wh ON wh.WarehouseID = mh.MutasiHandoverReceiveWarehouseID + AND wh.WarehouseIsActive = 'Y' + LEFT JOIN m_ruangan r ON r.M_RuanganID = mh.MutasiHandoverReceiveM_RuanganID + AND r.M_RuanganIsActive = 'Y' + WHERE mh.MutasiHandoverID = ? + AND mh.MutasiHandoverIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Penyerahan Mutasi tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + mhd.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode + FROM mutasi_handover_detail mhd + LEFT JOIN m_item i ON i.M_ItemID = mhd.MutasiHandoverDetailM_ItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = mhd.MutasiHandoverDetailItemUnitID + WHERE mhd.MutasiHandoverDetailMutasiHandoverID = ? + AND mhd.MutasiHandoverDetailIsActive = 'Y' + ORDER BY mhd.MutasiHandoverDetailID 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, 'PENYERAHAN MUTASI (HANDOVER)', 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 = 30; + $valW = $halfW - $lblW - 4; + + // ── Kolom Kiri ──────────────────────────────────────────────────────── + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor Handover', $header['MutasiHandoverNumber'], true), + array('No. Request', $header['MutasiRequestNumber'] ?: '-', false), + array('Tanggal', $this->_fmt_date($header['MutasiHandoverDate']), false), + array('Status', $header['MutasiHandoverStatus'], false), + array('Kategori', $header['itemCategoryName'], false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Keterangan (Note) langsung di bawah Status + if (!empty($header['MutasiHandoverNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->MultiCell($valW, 5, $header['MutasiHandoverNote'], 0, 'L'); + } + $leftY = $pdf->GetY(); + + // ── Kolom Kanan ─────────────────────────────────────────────────────── + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Cabang Asal', $header['branch_asal_name']), + array('Cabang Tujuan', $header['branch_tujuan_name']), + array('Warehouse Tujuan', $header['WarehouseName'] ?: '-'), + array('Ruangan Tujuan', $header['M_RuanganName'] ?: '-'), + 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'); + } + + // Tanggal terima di kolom kanan + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Tgl Terima', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $this->_fmt_date($header['MutasiHandoverReceiveDate']), 0, 1, 'L'); + + // Penerima di kolom kanan + if ($header['MutasiHandoverStatus'] === 'Received') { + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Diterima Oleh', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $header['ReceiveByName'] ?: '-', 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 + ringkasan nilai + // ========================================================================= + 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 + 80 + 20 + 20 + 25 + 27 = 180 + $cols = array( + array('No', 8, 'C'), + array('Nama Item', 80, 'L'), + array('Unit', 20, 'C'), + array('Qty', 20, 'R'), + array('Nilai Buku', 25, 'R'), + array('Total', 27, 'R'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + $total_val = 0; + + foreach ($details as $d) { + $bookValue = floatval($d['MutasiHandoverDetailBookValue'] ?? 0); + $qty = floatval($d['MutasiHandoverDetailQty'] ?? 0); + $lineTotal = $qty * $bookValue; + + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R'); + $pdf->Cell($cols[5][1], 6, $this->_fmt_rp($lineTotal), 1, 0, 'R'); + $pdf->Ln(); + + $total_val += $lineTotal; + $no++; + } + + // Baris TOTAL + $span = array_sum(array_column(array_slice($cols, 0, 5), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 9); + $pdf->SetFillColor(220, 220, 220); + $pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[5][1], 7, $this->_fmt_rp($total_val), 1, 0, 'R', true); + $pdf->Ln(); + + // ── Ringkasan nilai (rata kanan) ────────────────────────────────────── + $pdf->Ln(3); + $summary = array( + array('Total Nilai Penyerahan', $this->_fmt_rp($header['MutasiHandoverTotalValue'])), + ); + + $cW1 = 50; + $cW2 = 40; + $offsetX = 15 + $this->_pageW - $cW1 - $cW2; + + foreach ($summary as $s) { + $pdf->SetX($offsetX); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->Cell($cW1, 5, $s[0], 0, 0, 'L'); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->Cell($cW2, 5, $s[1], 0, 1, 'R'); + } + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Penyerahan mutasi ini dilakukan berdasarkan dokumen mutasi yang telah disetujui sebelumnya.', + 'Barang yang diserahkan harus sesuai dengan jumlah dan spesifikasi yang tercantum dalam dokumen ini.', + 'Penerima barang wajib memeriksa dan memverifikasi kesesuaian barang sebelum menerima.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_request_mutasi.php b/application/controllers/report/Rpt_request_mutasi.php new file mode 100644 index 0000000..56f12ce --- /dev/null +++ b/application/controllers/report/Rpt_request_mutasi.php @@ -0,0 +1,391 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_request_mutasi extends MY_Controller +{ + // ── Properti bersama antar fungsi PDF ───────────────────────────────────── + /** @var RequestMutasiFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Request Mutasi Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (MutasiRequestID), 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 RequestMutasiFpdf('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 = 'RM_' . str_replace('/', '-', $header['MutasiRequestNumber']) . '.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 + mr.*, + ic.itemCategoryID, + ic.itemCategoryName, + a.M_BranchID AS branch_asal_id, + a.M_BranchCode AS branch_asal_code, + a.M_BranchName AS branch_asal_name, + b.M_BranchID AS branch_tujuan_id, + b.M_BranchCode AS branch_tujuan_code, + b.M_BranchName AS branch_tujuan_name, + IFNULL(u.M_UserUsername, '') AS CreatedByName, + IFNULL(uv.M_UserUsername, '') AS VerifByName, + IFNULL(ua.M_UserUsername, '') AS ApprovedByName + FROM mutasi_request mr + JOIN item_category ic ON ic.itemCategoryID = mr.MutasiRequestItemCategoryID + JOIN m_branch a ON a.M_BranchID = mr.MutasiRequestFromBranchID + JOIN m_branch b ON b.M_BranchID = mr.MutasiRequestToBranchID + LEFT JOIN m_user u ON u.M_UserID = mr.MutasiRequestUserID + LEFT JOIN m_user uv ON uv.M_UserID = mr.MutasiRequestVerifUserID + LEFT JOIN m_user ua ON ua.M_UserID = mr.MutasiRequestApprovedUserID + WHERE mr.MutasiRequestID = ? + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Request Mutasi tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + mrd.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode + FROM mutasi_request_detail mrd + LEFT JOIN m_item i ON i.M_ItemID = mrd.MutasiRequestDetailM_ItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = mrd.MutasiRequestDetailItemUnitID + WHERE mrd.MutasiRequestDetailMutasiRequestID = ? + AND mrd.MutasiRequestDetailIsActive = 'Y' + ORDER BY mrd.MutasiRequestDetailID 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, 'REQUEST MUTASI REPORT (RM)', 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 = 30; + $valW = $halfW - $lblW - 4; + + // ── Kolom Kiri ──────────────────────────────────────────────────────── + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor', $header['MutasiRequestNumber'], true), + array('Tanggal', $this->_fmt_date($header['MutasiRequestDate']), false), + array('Status', $header['MutasiRequestStatus'], false), + array('Kategori', $header['itemCategoryName'], false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Keterangan (Note) langsung di bawah Status + if (!empty($header['MutasiRequestNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->MultiCell($valW, 5, $header['MutasiRequestNote'], 0, 'L'); + } + $leftY = $pdf->GetY(); + + // ── Kolom Kanan ─────────────────────────────────────────────────────── + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Cabang Asal', $header['branch_asal_name']), + array('Cabang Tujuan', $header['branch_tujuan_name']), + array('Dibuat Oleh', $header['CreatedByName']), + array('Diverifikasi Oleh', $header['VerifByName'] ?: '-'), + array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'), + ); + + 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'); + } + + // Tanggal approvals di kolom kanan + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Tgl Verifikasi', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $this->_fmt_datetime($header['MutasiRequestVerifDate']), 0, 1, 'L'); + + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Tgl Approved', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $this->_fmt_datetime($header['MutasiRequestApprovedDate']), 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 + ringkasan nilai + // ========================================================================= + 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 + 80 + 20 + 20 + 25 + 27 = 180 + $cols = array( + array('No', 8, 'C'), + array('Nama Item', 80, 'L'), + array('Unit', 20, 'C'), + array('Qty', 20, 'R'), + array('Nilai Buku', 25, 'R'), + array('Total', 27, 'R'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + $total_val = 0; + + foreach ($details as $d) { + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($d['MutasiRequestDetailQty']), 1, 0, 'R'); + $bookValue = floatval($d['MutasiRequestDetailBookValue'] ?? 0); + $pdf->Cell($cols[4][1], 6, $this->_fmt_rp($bookValue), 1, 0, 'R'); + $pdf->Cell($cols[5][1], 6, $this->_fmt_rp($d['MutasiRequestDetailQty'] * $bookValue), 1, 0, 'R'); + $pdf->Ln(); + + $total_val += floatval($d['MutasiRequestDetailQty'] * $bookValue); + $no++; + } + + // Baris TOTAL + $span = array_sum(array_column(array_slice($cols, 0, 5), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 9); + $pdf->SetFillColor(220, 220, 220); + $pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[5][1], 7, $this->_fmt_rp($total_val), 1, 0, 'R', true); + $pdf->Ln(); + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Mutasi barang ini harus mendapatkan persetujuan dari pejabat berwenang sebelum proses mutasi dilakukan.', + 'Barang yang dimutasi harus sesuai dengan spesifikasi dan jumlah yang tercantum dalam dokumen ini.', + 'Proses mutasi dilakukan setelah barang diverifikasi dan disetujui oleh pihak terkait.', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan proses mutasi barang.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +} diff --git a/application/controllers/report/Rpt_request_pengeluaran_barang.php b/application/controllers/report/Rpt_request_pengeluaran_barang.php new file mode 100644 index 0000000..b5ff290 --- /dev/null +++ b/application/controllers/report/Rpt_request_pengeluaran_barang.php @@ -0,0 +1,410 @@ +AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular + $this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold + } + + public function Footer() + { + $pageW = $this->GetPageWidth() - 30; // 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'); + } +} + +// ============================================================================= +// Controller +// ============================================================================= +class Rpt_request_pengeluaran_barang extends MY_Controller +{ + // -- Properti bersama antar fungsi PDF ------------------------------------- + /** @var RequestPengeluaranBarangFpdf */ + private $_pdf; + private $_pageW; + private $_header_data; + private $_username; + + public function __construct() + { + parent::__construct(); + } + + public function index() + { + echo "Request Pengeluaran Barang Report API"; + } + + // ========================================================================= + // ENDPOINT: pdf + // GET/POST: id (RequestItemOutID), 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 RequestPengeluaranBarangFpdf('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 = 'RIO_' . str_replace('/', '-', $header['RequestItemOutNumber']) . '.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 + rio.*, + wh.WarehouseCode, + wh.WarehouseName, + wh.WarehouseType, + r.S_RegionalName, + d.DivisionCode, + d.DivisionName, + b.M_BranchName AS BranchName, + IFNULL(u.M_UserUsername, '') AS CreatedByName, + IFNULL(um.M_UserUsername, '') AS ManagerByName, + IFNULL(ua.M_UserUsername, '') AS ApprovedByName, + IFNULL(uv.M_UserUsername, '') AS VerifiedByName + FROM request_item_out rio + LEFT JOIN warehouse wh ON wh.WarehouseID = rio.RequestItemOutWarehouseID + AND wh.WarehouseIsActive = 'Y' + LEFT JOIN s_regional r ON r.S_RegionalID = rio.RequestItemOutS_RegionalID + AND r.S_RegionalIsActive = 'Y' + LEFT JOIN m_branch b ON b.M_BranchCode = rio.RequestItemOutM_BranchCode + AND b.M_BranchIsActive = 'Y' + LEFT JOIN division d ON d.DivisionID = rio.RequestItemOutDivisionID + AND d.DivisionIsActive = 'Y' + LEFT JOIN m_user u ON u.M_UserID = rio.RequestItemOutUserID + LEFT JOIN m_user um ON um.M_UserID = rio.RequestItemOutApprovedManagerUserID + LEFT JOIN m_user ua ON ua.M_UserID = rio.RequestItemOutApprovedUserID + LEFT JOIN m_user uv ON uv.M_UserID = rio.RequestItemOutVerifiedUserID + WHERE rio.RequestItemOutID = ? + AND rio.RequestItemOutIsActive = 'Y' + LIMIT 1 + "; + $qry = $this->db->query($sql, array($id)); + if (!$qry || $qry->num_rows() === 0) { + $this->sys_error("Data Request Pengeluaran Barang tidak ditemukan"); + exit; + } + return $qry->row_array(); + } + + // ========================================================================= + // DATABASE: Ambil data detail + // ========================================================================= + private function _get_detail($id) + { + $sql = " + SELECT + rod.*, + i.M_ItemCode, + i.M_ItemDesc, + iu.ItemUnitName, + iu.ItemUnitCode + FROM request_item_out_detail rod + LEFT JOIN m_item i ON i.M_ItemID = rod.RequestItemOutDetailM_ItemID + LEFT JOIN itemunit iu ON iu.ItemUnitID = rod.RequestItemOutDetailItemUnitID + WHERE rod.RequestItemOutDetailRequestItemOutID = ? + AND rod.RequestItemOutDetailIsActive = 'Y' + ORDER BY rod.RequestItemOutDetailID 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, 'REQUEST PENGELUARAN BARANG', 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 = 32; + $valW = $halfW - $lblW - 4; + + // -- Kolom Kiri ----------------------------------------------------------- + $pdf->SetY($startY); + + $leftItems = array( + array('Nomor Request', $header['RequestItemOutNumber'], true), + array('Tanggal', $this->_fmt_date($header['RequestItemOutDate']), false), + array('Status', $header['RequestItemOutStatus'], false), + array('Divisi', $header['DivisionName'] ?: '-', false), + array('Gudang', $header['WarehouseName'] ?: '-', false), + array('Regional', $header['S_RegionalName'] ?: '-', false), + ); + + foreach ($leftItems as $item) { + $pdf->SetX(15); + $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); + } + $pdf->Cell($valW, 5, $item[1], 0, 1, 'L'); + } + + // Keterangan (Note) + if (!empty($header['RequestItemOutNote'])) { + $pdf->SetX(15); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Keterangan', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->MultiCell($valW, 5, $header['RequestItemOutNote'], 0, 'L'); + } + $leftY = $pdf->GetY(); + + // -- Kolom Kanan ---------------------------------------------------------- + $pdf->SetY($startY); + $rightX = 15 + $halfW; + + $rightItems = array( + array('Cabang', $header['BranchName'] ?: '-'), + array('Dibuat Oleh', $header['CreatedByName'] ?: '-'), + array('Dibuat Tgl', $this->_fmt_datetime($header['RequestItemOutCreated'])), + array('Approve Manager', $header['ManagerByName'] ?: '-'), + array('Tgl Appr. Manager',$header['RequestItemOutApprovedManagerDate'] ? $this->_fmt_datetime($header['RequestItemOutApprovedManagerDate']) : '-'), + array('Disetujui Oleh', $header['ApprovedByName'] ?: '-'), + array('Tgl Disetujui', $header['RequestItemOutApprovedDate'] ? $this->_fmt_datetime($header['RequestItemOutApprovedDate']) : '-'), + ); + + 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'); + } + + // Verifikasi (hanya tampil jika ada) + if (!empty($header['RequestItemOutVerifiedDate'])) { + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Diverifikasi Oleh', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $header['VerifiedByName'] ?: '-', 0, 1, 'L'); + + $pdf->SetX($rightX); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($lblW, 5, 'Tgl Verifikasi', 0, 0, 'L'); + $pdf->Cell(4, 5, ':', 0, 0, 'C'); + $pdf->SetFont('Arial_Narrow', '', 9); + $pdf->Cell($valW, 5, $this->_fmt_datetime($header['RequestItemOutVerifiedDate']), 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; + + // -- Definisi kolom: [label, lebar, align] ------------------------------- + // Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan) + // 8 + 72 + 20 + 25 + 25 + 30 = 180 + $cols = array( + array('No', 8, 'C'), + array('Nama Item', 72, 'L'), + array('Unit', 20, 'C'), + array('Qty Request', 25, 'R'), + array('Qty Diterima', 25, 'R'), + array('Status', 30, 'C'), + ); + + // Header kolom + $pdf->SetLineWidth(0.3); + $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(); + + // Baris data + $pdf->SetLineWidth(0.2); + $pdf->SetFont('Arial_Narrow', '', 8); + $pdf->SetFillColor(255, 255, 255); + $no = 1; + $total_qty = 0; + $total_recv = 0; + + foreach ($details as $d) { + $qty = floatval($d['RequestItemOutDetailQty'] ?? 0); + $recvQty = floatval($d['RequestItemOutDetailReceiveQty'] ?? 0); + $status = $d['RequestItemOutDetailStatus'] ?? '-'; + + $pdf->Cell($cols[0][1], 6, $no, 1, 0, 'C'); + $pdf->Cell($cols[1][1], 6, $d['M_ItemDesc'] ?: ($d['M_ItemCode'] ?: '-'), 1, 0, 'L'); + $pdf->Cell($cols[2][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C'); + $pdf->Cell($cols[3][1], 6, $this->_fmt_qty($qty), 1, 0, 'R'); + $pdf->Cell($cols[4][1], 6, $this->_fmt_qty($recvQty), 1, 0, 'R'); + $pdf->Cell($cols[5][1], 6, $status, 1, 0, 'C'); + $pdf->Ln(); + + $total_qty += $qty; + $total_recv += $recvQty; + $no++; + } + + // Baris TOTAL + $span = array_sum(array_column(array_slice($cols, 0, 3), 1)); + $pdf->SetLineWidth(0.3); + $pdf->SetFont('Arial_Narrow', 'B', 8); + $pdf->SetFillColor(220, 220, 220); + $pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true); + $pdf->Cell($cols[3][1], 7, $this->_fmt_qty($total_qty), 1, 0, 'R', true); + $pdf->Cell($cols[4][1], 7, $this->_fmt_qty($total_recv), 1, 0, 'R', true); + $pdf->Cell($cols[5][1], 7, '', 1, 0, 'C', true); + $pdf->Ln(); + + $pdf->Ln(4); + } + + // ========================================================================= + // PDF SECTION: Syarat & Ketentuan + // ========================================================================= + private function _pdf_terms() + { + $pdf = $this->_pdf; + $pageW = $this->_pageW; + + $terms = array( + 'Request ini harus disetujui oleh atasan (manager) sebelum diproses lebih lanjut.', + 'Barang yang diminta akan dikeluarkan sesuai dengan jumlah yang telah disetujui.', + 'Apabila jumlah barang tidak tersedia, maka akan dilakukan pengeluaran sebagian (partial).', + 'Dokumen ini bersifat internal dan hanya digunakan untuk keperluan administrasi perusahaan.', + ); + + $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, ',', '.'); + } + + private function _fmt_qty($n) + { + return number_format(floatval($n), 0, ',', '.'); + } + + private function _fmt_rp($n) + { + return 'Rp ' . number_format(floatval($n), 2, ',', '.'); + } +}