add report po asset

This commit is contained in:
Hanan Askarim
2026-07-16 16:26:25 +07:00
parent e186b5f491
commit 578371dc01

View File

@@ -0,0 +1,464 @@
<?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 PurchaseOrderAssetFpdf 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_purchase_order_asset extends MY_Controller
{
// ── Properti bersama antar fungsi PDF ─────────────────────────────────────
/** @var PurchaseOrderAssetFpdf */
private $_pdf;
private $_pageW;
private $_header_data;
private $_username;
public function __construct()
{
parent::__construct();
}
public function index()
{
echo "Purchase Order Asset Report API";
}
// =========================================================================
// ENDPOINT: pdf
// GET/POST: id (PurchaseOrderID), 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 PurchaseOrderAssetFpdf('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 = 'PO_ASSET_' . str_replace('/', '-', $header['PurchaseOrderNumber']) . '.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
po.*,
sup.SupplierName,
sup.SupplierAddress,
sup.SupplierPhone,
b.M_BranchName,
b.M_BranchAddress,
r.S_RegionalName,
ac.PurchaseOrderAssetContractName AS ContractName,
ac.PurchaseOrderAssetContractDate AS ContractDate,
ac.PurchaseOrderAssetContractStartDate AS ContractStart,
ac.PurchaseOrderAssetContractEndDate AS ContractEnd,
ac.PurchaseOrderAssetContractDuration AS ContractDuration,
ac.PurchaseOrderAssetContractInstallmentNumber AS InstallmentNumber,
ac.PurchaseOrderAssetContractInstallmentPayAmount AS InstallmentPayAmount,
ac.PurchaseOrderAssetContractInstallmentDownPayment AS InstallmentDownPayment,
ac.PurchaseOrderAssetContractInstallmentDownPaymentType AS InstallmentDownPaymentType,
ac.PurchaseOrderAssetContractStatus AS ContractStatus
FROM purchase_order po
LEFT JOIN supplier sup ON sup.SupplierID = po.PurchaseOrderSupplierID
LEFT JOIN s_regional r ON r.S_RegionalID = po.PurchaseOrderS_RegionalID
LEFT JOIN purchase_order_asset_contract ac ON ac.PurchaseOrderAssetContractPurchaseOrderID = po.PurchaseOrderID
AND ac.PurchaseOrderAssetContractIsActive = 'Y'
LEFT JOIN m_branch b ON b.M_BranchID = (
-- Cabang pengirim/tujuan diambil dari detail pertama
SELECT pod.PurchaseOrderDetailWarehouseID
FROM purchase_order_detail pod
WHERE pod.PurchaseOrderDetailPurchaseOrderID = po.PurchaseOrderID
AND pod.PurchaseOrderDetailIsActive = 'Y'
LIMIT 1
)
WHERE po.PurchaseOrderIsActive = 'Y'
AND po.PurchaseOrderID = ?
LIMIT 1
";
$qry = $this->db->query($sql, array($id));
if (!$qry || $qry->num_rows() === 0) {
$this->sys_error("Data Purchase Order tidak ditemukan");
exit;
}
return $qry->row_array();
}
// =========================================================================
// DATABASE: Ambil data detail (menggunakan summary agar teragregasi)
// =========================================================================
private function _get_detail($id)
{
$sql = "
SELECT
pos.PurchaseOrderSummaryQty AS RequestQty,
pos.PurchaseOrderSummaryPrice AS SupplierPrice,
pos.PurchaseOrderSummaryDiscountAmount AS DiskonAmount,
pos.PurchaseOrderSummaryTotal AS TempTotal,
item.M_ItemCode,
item.M_ItemDesc,
iu.ItemUnitName,
iu.ItemUnitCode
FROM purchase_order_summary pos
JOIN m_item item ON item.M_ItemID = pos.PurchaseOrderSummaryItemID
AND item.M_ItemIsActive = 'Y'
LEFT JOIN itemunit iu ON iu.ItemUnitID = pos.PurchaseOrderSummaryItemUnitID
AND iu.ItemUnitIsActive = 'Y'
WHERE pos.PurchaseOrderSummaryIsActive = 'Y'
AND pos.PurchaseOrderSummaryPurchaseOrderID = ?
ORDER BY pos.PurchaseOrderSummaryID 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, 'PURCHASE ORDER ASET (KONTRAK / CICILAN)', 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);
// Periode kontrak
$periodeKontrak = '-';
if (!empty($header['ContractStart']) && $header['ContractStart'] !== '0000-00-00') {
$periodeKontrak = $this->_fmt_date($header['ContractStart']) . ' s/d ' . $this->_fmt_date($header['ContractEnd']);
if (intval($header['ContractDuration']) > 0) {
$periodeKontrak .= ' (' . $header['ContractDuration'] . ' Bln)';
}
}
$leftItems = array(
array('Nomor PO', $header['PurchaseOrderNumber'], true),
array('Tanggal PO', $this->_fmt_date($header['PurchaseOrderDate']), false),
array('No. Referensi', $header['PurchaseOrderRefNumber'] ?: '-', false),
array('Nama Kontrak', $header['ContractName'] ?: '-', false),
array('Tanggal Kontrak', $this->_fmt_date($header['ContractDate']), false),
array('Periode Kontrak', $periodeKontrak, 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['PurchaseOrderNote'])) {
$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['PurchaseOrderNote'], 0, 'L');
}
$leftY = $pdf->GetY();
// ── Kolom Kanan ───────────────────────────────────────────────────────
$pdf->SetY($startY);
$rightX = 15 + $halfW;
// Down Payment format
$dpVal = '-';
$dpNominal = floatval($header['InstallmentDownPayment'] ?? 0);
if ($dpNominal > 0) {
if (($header['InstallmentDownPaymentType'] ?? 'nominal') === 'percent') {
$dpVal = number_format($dpNominal, 0, ',', '.') . '%';
} else {
$dpVal = $this->_fmt_rp($dpNominal);
}
}
// Cicilan format
$installmentVal = '-';
$instNum = intval($header['InstallmentNumber'] ?? 0);
$instAmount = floatval($header['InstallmentPayAmount'] ?? 0);
if ($instNum > 0) {
$installmentVal = $instNum . ' Kali x ' . $this->_fmt_rp($instAmount);
}
$rightItems = array(
array('Supplier', $header['SupplierName'] ?: '-'),
array('Alamat Supplier', $header['SupplierAddress'] ?: '-'),
array('Telp Supplier', $header['SupplierPhone'] ?: '-'),
array('Down Payment (DP)', $dpVal),
array('Cicilan / Tenor', $installmentVal),
);
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') {
$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 + 60 + 20 + 15 + 25 + 22 + 30 = 180
$cols = array(
array('No', 8, 'C'),
array('Nama', 60, 'L'),
array('Unit', 20, 'C'),
array('Qty', 15, 'R'),
array('Harga Satuan', 25, 'R'),
array('Diskon', 22, '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;
$subTotal = 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['RequestQty']), 1, 0, 'C');
$pdf->Cell($cols[4][1], 6, $this->_fmt_rp($d['SupplierPrice']), 1, 0, 'R');
$pdf->Cell($cols[5][1], 6, $this->_fmt_rp($d['DiskonAmount']), 1, 0, 'R');
$pdf->Cell($cols[6][1], 6, $this->_fmt_rp($d['TempTotal']), 1, 0, 'R');
$pdf->Ln();
$subTotal += floatval($d['TempTotal']);
$no++;
}
$pdf->Ln(2);
// ── Ringkasan Biaya di Bagian Kanan Bawah ──────────────────────────────
$discountPO = floatval($header['PurchaseOrderDiscountAmount'] ?? 0);
$ppnAmount = floatval($header['PurchaseOrderTaxAmountPpn'] ?? 0);
$pphAmount = floatval($header['PurchaseOrderTaxAmountPph'] ?? 0);
$shipCost = floatval($header['PurchaseOrderShippingCost'] ?? 0);
$grandTotal = floatval($header['PurchaseOrderGrandTotal'] ?? ($subTotal - $discountPO + $ppnAmount - $pphAmount + $shipCost));
$summary = array(
array('Subtotal', $this->_fmt_rp($subTotal)),
);
if ($discountPO > 0) {
$summary[] = array('Diskon PO', '-' . $this->_fmt_rp($discountPO));
}
if ($ppnAmount > 0) {
$summary[] = array('PPN', $this->_fmt_rp($ppnAmount));
}
if ($pphAmount > 0) {
$summary[] = array('PPH', '-' . $this->_fmt_rp($pphAmount));
}
if ($shipCost > 0) {
$summary[] = array('Ongkos Kirim', $this->_fmt_rp($shipCost));
}
$summary[] = array('Grand Total', $this->_fmt_rp($grandTotal));
$cW1 = 62;
$cW2 = 30;
$offsetX = 15 + array_sum(array_column(array_slice($cols, 0, 3), 1)); // Sejajar dengan Qty ke kanan
foreach ($summary as $s) {
$pdf->SetX($offsetX);
$pdf->SetFont('Arial_Narrow', '', 8);
$pdf->Cell($cW1, 5, $s[0], 0, 0, 'L');
if ($s[0] === 'Grand Total') {
$pdf->SetFont('Arial_Narrow', 'B', 9);
} else {
$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(
'Purchase Order (PO) ini tunduk pada syarat dan ketentuan kontrak sewa/beli aset yang disepakati.',
'Seluruh pembayaran DP dan angsuran harus disetorkan tepat waktu sesuai jadwal kontrak.',
'Serah terima aset akan didokumentasikan terpisah menggunakan Berita Acara Serah Terima (BAST).',
'PO ini sah secara hukum setelah disetujui dan divalidasi secara digital melalui sistem ERP.'
);
$pdf->SetFont('Arial_Narrow', 'B', 9);
$pdf->Cell($pageW, 5, 'Syarat & Ketentuan:', 0, 1, 'L');
$pdf->SetFont('Arial_Narrow', '', 8);
foreach ($terms as $i => $t) {
$pdf->Cell(6, 5, ($i + 1) . '.', 0, 0, 'R');
$pdf->Cell($pageW - 6, 5, $t, 0, 1, 'L');
}
}
// =========================================================================
// HELPER: Format tampilan
// =========================================================================
private function _fmt_date($d)
{
if (!$d || $d === '0000-00-00') return '-';
return date('d-m-Y', strtotime($d));
}
private function _fmt_datetime($d)
{
if (!$d || $d === '0000-00-00 00:00:00') return '-';
return date('d-m-Y H:i', strtotime($d));
}
private function _fmt_num($n)
{
return number_format(floatval($n), 2, ',', '.');
}
// format jumlah/qty: tanpa desimal
private function _fmt_qty($n)
{
return number_format(floatval($n), 0, ',', '.');
}
private function _fmt_rp($n)
{
return 'Rp ' . number_format(floatval($n), 2, ',', '.');
}
}