add report receive item po persediaan

This commit is contained in:
Hanan Askarim
2026-07-16 14:18:35 +07:00
parent 67441e9666
commit eac89f6ddd

View File

@@ -0,0 +1,434 @@
<?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
// Footer tidak bisa bergeser karena FPDF memanggil Footer() sebelum ganti hal.
// =============================================================================
class ReceiveItemPoFpdf extends FPDF
{
public $printUsername = '-';
public $printDate = '';
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
{
parent::__construct($orientation, $unit, $size);
// Daftarkan Arial Narrow — file font ada di fpdf/font/Arial_Narrow.php
$this->AddFont('Arial_Narrow', '', 'Arial_Narrow.php'); // regular
$this->AddFont('Arial_Narrow', 'B', 'Arial_Narrow_B.php'); // bold (Liberation Sans Narrow Bold)
}
public function Footer()
{
$pageW = $this->GetPageWidth() - 30; // sama dengan margin 15+15
// ── Posisi: 20mm dari bawah halaman ──────────────────────────────────
$this->SetY(-20);
// ── Baris 1: Print Oleh (kiri) | Nomor Halaman (tengah) ───────────────
$colSide = ($pageW - 40) / 2;
$colCenter = 40;
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Print Oleh : ' . $this->printUsername, 0, 0, 'L');
$this->Cell($colCenter, 4, $this->PageNo() . ' / {nb}', 0, 0, 'C');
$this->Cell($colSide, 4, '', 0, 1, 'R');
// ── Baris 2: Tgl Print (kiri) ──────────────────────────────────────────
$this->SetFont('Arial_Narrow', '', 7);
$this->Cell($colSide, 4, 'Tgl Print : ' . $this->printDate, 0, 0, 'L');
$this->Cell($colCenter + $colSide, 4, '', 0, 1, 'L');
}
}
// =============================================================================
// Controller
// =============================================================================
class Rpt_receive_item_po extends MY_Controller
{
// ── Properti bersama antar fungsi PDF ─────────────────────────────────────
/** @var ReceiveItemPoFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Receive Item PO (Persediaan) Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (ReceiveOrderPoID), 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 ReceiveItemPoFpdf('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 = 'RI_PO_' . str_replace('/', '-', $header['ReceiveOrderPoNumber']) . '.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
rop.*,
sup.SupplierName,
sup.SupplierAddress,
sup.SupplierPhone,
b.M_BranchName,
b.M_BranchAddress,
r.S_RegionalName,
wh.WarehouseName AS ReceivedWarehouseName,
IFNULL(uCr.M_UserUsername, '') AS CreatedByName,
IFNULL(uCf.M_UserUsername, '') AS ConfirmedByName,
IFNULL((
SELECT po.PurchaseOrderShippingCost
FROM receive_order_po_detail d
JOIN purchase_order po ON po.PurchaseOrderID = d.ReceiveOrderPoDetailPurchaseOrderID
WHERE d.ReceiveOrderPoDetailReceiveOrderPoID = rop.ReceiveOrderPoID
AND d.ReceiveOrderPoDetailIsActive = 'Y'
LIMIT 1
), 0) AS PoShippingCostAmount,
IFNULL((
SELECT po.PurchaseOrderShippingCostStatus
FROM receive_order_po_detail d
JOIN purchase_order po ON po.PurchaseOrderID = d.ReceiveOrderPoDetailPurchaseOrderID
WHERE d.ReceiveOrderPoDetailReceiveOrderPoID = rop.ReceiveOrderPoID
AND d.ReceiveOrderPoDetailIsActive = 'Y'
LIMIT 1
), 'N') AS PoShippingCostIsPaid
FROM receive_order_po rop
LEFT JOIN supplier sup ON sup.SupplierID = rop.ReceiveOrderPoSupplierID
LEFT JOIN m_branch b ON b.M_BranchCode = rop.ReceiveOrderPoM_BranchCode
LEFT JOIN s_regional r ON r.S_RegionalID = rop.ReceiveOrderPoS_RegionalID
LEFT JOIN warehouse wh ON wh.WarehouseID = rop.ReceiveOrderPoWarehouseID
LEFT JOIN m_user uCr ON uCr.M_UserID = rop.ReceiveOrderPoCreatedUserID
LEFT JOIN m_user uCf ON uCf.M_UserID = rop.ReceiveOrderPoConfirmedUserID
WHERE rop.ReceiveOrderPoIsActive = 'Y'
AND rop.ReceiveOrderPoID = ?
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Penerimaan PO tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
ropd.*,
item.M_ItemCode,
item.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode,
po.PurchaseOrderNumber
FROM receive_order_po_detail ropd
JOIN m_item item ON item.M_ItemID = ropd.ReceiveOrderPoItemID
AND item.M_ItemIsActive = 'Y'
LEFT JOIN itemunit iu ON iu.ItemUnitID = ropd.ReceiveOrderPoItemUnitID
AND iu.ItemUnitIsActive = 'Y'
LEFT JOIN purchase_order po ON po.PurchaseOrderID = ropd.ReceiveOrderPoDetailPurchaseOrderID
WHERE ropd.ReceiveOrderPoDetailIsActive = 'Y'
AND ropd.ReceiveOrderPoDetailReceiveOrderPoID = ?
ORDER BY ropd.ReceiveOrderPoDetailID 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 BARANG PO (RECEIVE PO)', 0, 1, 'L');
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Line(15, $pdf->GetY(), 15 + $pageW, $pdf->GetY());
$pdf->Ln(2);
$startY = $pdf->GetY();
$halfW = $pageW / 2;
$lblW = 28;
$valW = $halfW - $lblW - 4;
// ── Kolom Kiri ────────────────────────────────────────────────────────
$pdf->SetY($startY);
$leftItems = array(
array('Nomor', $header['ReceiveOrderPoNumber'], true),
array('Tanggal', $this->_fmt_date($header['ReceiveOrderPoIDate']), false),
array('No. Referensi', $header['ReceiveOrderPoRefNumber'] ?: '-', false),
array('No. Surat Jalan', $header['ReceiveOrderPoDONumber'] ?: '-', false),
array('Gudang Penerima', $header['ReceivedWarehouseName'] ?: '-', 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 (tanpa spasi vertikal)
if (!empty($header['ReceiveOrderPoNote'])) {
$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['ReceiveOrderPoNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// ── Kolom Kanan ───────────────────────────────────────────────────────
$pdf->SetY($startY);
$rightX = 15 + $halfW;
$rightItems = array(
array('Supplier', $header['SupplierName'] ?: '-'),
array('Alamat Supplier', $header['SupplierAddress'] ?: '-'),
array('Telp Supplier', $header['SupplierPhone'] ?: '-'),
array('Alamat Penerima', $header['M_BranchAddress'] ?: '-'),
);
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);
if ($item[0] === 'Alamat Supplier' || $item[0] === 'Alamat Penerima') {
$pdf->MultiCell($valW, 5, $item[1], 0, 'L');
} else {
$pdf->Cell($valW, 5, $item[1], 0, 1, 'L');
}
}
$rightY = $pdf->GetY();
// Posisikan Y ke yang paling bawah + margin
$pdf->SetY(max($leftY, $rightY) + 4);
}
// =========================================================================
// PDF SECTION: Data — Tabel detail item
// =========================================================================
private function _pdf_data($details)
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$header = $this->_header_data;
// ── Definisi kolom: [label, lebar, align] ─────────────────────────────
// Total lebar = 180mm (A4 portrait: 210 - margin 15 kiri - 15 kanan)
// 8 + 45 + 38 + 20 + 15 + 24 + 30 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama', 45, 'L'),
array('No. PO', 38, 'L'),
array('Unit', 20, 'C'),
array('Qty', 15, 'C'),
array('Harga', 24, 'R'),
array('Total', 30, 'R'),
);
// Header kolom
$pdf->SetLineWidth(0.3); // border medium
$pdf->SetFont('Arial_Narrow', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$pdf->SetDrawColor(0, 0, 0);
foreach ($cols as $c) {
$pdf->Cell($c[1], 7, $c[0], 1, 0, 'C', true);
}
$pdf->Ln();
// Baris data
$pdf->SetLineWidth(0.2); // border lebih tipis di data
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->SetFillColor(255, 255, 255);
$no = 1;
$grand_tot = 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['PurchaseOrderNumber'] ?: '-', 1, 0, 'L');
$pdf->Cell($cols[3][1], 6, $d['ItemUnitName'] ?: ($d['ItemUnitCode'] ?: '-'), 1, 0, 'C');
$pdf->Cell($cols[4][1], 6, $this->_fmt_qty($d['ReceiveOrderPoDetailQty']), 1, 0, 'C');
$pdf->Cell($cols[5][1], 6, $this->_fmt_rp($d['ReceiveOrderPoDetailPrice']), 1, 0, 'R');
$pdf->Cell($cols[6][1], 6, $this->_fmt_rp($d['ReceiveOrderPoDetailTotal']), 1, 0, 'R');
$pdf->Ln();
$grand_tot += floatval($d['ReceiveOrderPoDetailTotal']);
$no++;
}
// Baris TOTAL
$span = array_sum(array_column(array_slice($cols, 0, 6), 1));
$pdf->SetLineWidth(0.3);
$pdf->SetFont('Arial_Narrow', 'B', 9); // bold + size 9 agar menonjol
$pdf->SetFillColor(220, 220, 220);
$pdf->Cell($span, 7, 'TOTAL', 1, 0, 'R', true);
$pdf->Cell($cols[6][1], 7, $this->_fmt_rp($grand_tot), 1, 0, 'R', true);
$pdf->Ln(6);
// Ringkasan Biaya jika ada Biaya Ekspedisi (mendukung fallback dari PO)
$shippingCost = floatval($header['ReceiveOrderShippingCostAmount'] ?? 0);
$isPaid = $header['ReceiveOrderShippingCostIsPaid'] ?? 'N';
if ($shippingCost <= 0) {
$shippingCost = floatval($header['PoShippingCostAmount'] ?? 0);
$isPaid = $header['PoShippingCostIsPaid'] ?? 'N';
}
if ($shippingCost > 0) {
$pdf->Ln(2);
$grandTotal = $grand_tot + $shippingCost;
$statusPaid = ($isPaid === 'Y') ? ' (Lunas)' : ' (Belum Lunas)';
$summary = array(
array('Total Item', $this->_fmt_rp($grand_tot)),
array('Biaya Ekspedisi' . $statusPaid, $this->_fmt_rp($shippingCost)),
array('Grand Total', $this->_fmt_rp($grandTotal)),
);
$cW1 = 39;
$cW2 = 30;
$offsetX = 15 + array_sum(array_column(array_slice($cols, 0, 4), 1));
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(8);
}
// =========================================================================
// PDF SECTION: Syarat & Ketentuan
// =========================================================================
private function _pdf_terms()
{
$pdf = $this->_pdf;
$pageW = $this->_pageW;
$terms = array(
'Barang yang diterima telah dicocokkan dengan Surat Jalan supplier dan Purchase Order asli.',
'Segala klaim ketidaksesuaian jumlah atau jenis barang harus diselesaikan langsung dengan supplier bersangkutan.',
'Penerimaan barang ini akan otomatis memperbarui stok/inventori gudang persediaan.',
'Dokumen ini dicetak secara otomatis dan sah sebagai bukti penerimaan barang masuk.'
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
// format jumlah/qty: tanpa desimal
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}