feat: report inventory

This commit is contained in:
2026-07-17 16:54:03 +07:00
parent 76258c7c61
commit 513888a74d
7 changed files with 3123 additions and 0 deletions

View File

@@ -0,0 +1,532 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class FakturFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,531 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class PemakaianItemDivisiFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,427 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class MutasiPenerimaanFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,403 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class PengeluaranBarangFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,429 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class MutasiHandoverFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,391 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class RequestMutasiFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}

View File

@@ -0,0 +1,410 @@
<?php
defined('BASEPATH') or exit('No direct script access allowed');
require_once(APPPATH . 'libraries/fpdf/fpdf.php');
// =============================================================================
// Custom FPDF: override Footer() agar tampil otomatis di SETIAP halaman
// =============================================================================
class RequestPengeluaranBarangFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow
$this->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, ',', '.');
}
}