Files
be-accone/application/controllers/report/Rpt_faktur.php
2026-07-17 16:54:03 +07:00

533 lines
21 KiB
PHP

<?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, ',', '.');
}
}