Files
2026-09-11 16:03:00 +07:00

223 lines
111 KiB
PHP

<?php
$step=$this->input->get('step')?:'order';if(!in_array($step,array('order','payment','receipt','invoice'),true))$step='order';
$paymentAccounts=$this->db->where(array('is_active'=>1,'allow_posting'=>1))->group_start()->like('nama_akun','kas')->or_like('nama_akun','bank')->group_end()->order_by('kode_akun')->get('accounts')->result();
$bankChargeMapping=$this->db->get_where('system_account_mappings',array('mapping_key'=>'bank_charge_expense','is_active'=>1))->row();$bankChargeAccountId=$bankChargeMapping?(int)$bankChargeMapping->account_id:0;
$bankChargeAccounts=$this->db->where(array('is_active'=>1,'allow_posting'=>1,'tipe'=>'expense'))->order_by('kode_akun')->get('accounts')->result();
$payments=$this->db->select('sp.*,po.po_no,s.name supplier_name')->from('supplier_payments sp')->join('purchase_orders po','po.id=sp.purchase_order_id','left')->join('suppliers s','s.id=sp.supplier_id')->order_by('sp.id','DESC')->limit(200)->get()->result();
$invoiceReceipts=$this->db->select('gr.*,po.po_no,s.name')->from('goods_receipts gr')->join('purchase_orders po','po.id=gr.purchase_order_id')->join('suppliers s','s.id=gr.supplier_id')->where('gr.status','posted')->where_in('po.status',array('partially_received','received'))->where("EXISTS(SELECT 1 FROM goods_receipt_lines grl WHERE grl.goods_receipt_id=gr.id AND grl.qty-COALESCE((SELECT SUM(prl.shipped_qty) FROM purchase_return_lines prl JOIN purchase_returns pr ON pr.id=prl.purchase_return_id WHERE prl.receipt_line_id=grl.id AND pr.status IN('shipped','waiting_supplier','partially_resolved','resolved')),0)>COALESCE((SELECT SUM(sil.qty) FROM supplier_invoice_lines sil WHERE sil.receipt_line_id=grl.id),0))",null,false)->order_by('gr.id','DESC')->get()->result();
$paymentItems=array();$paymentItemRows=$this->db->select('pol.purchase_order_id,pol.description,pol.qty,pol.unit_price,pol.tax_amount,pol.line_total,pol.tracking_type,k.kode_barang,k.nama nama_barang')->from('purchase_order_lines pol')->join('kode_barang k','k.id=pol.kode_barang_id','left')->order_by('pol.purchase_order_id')->order_by('pol.id')->get()->result();foreach($paymentItemRows as$paymentItem)$paymentItems[$paymentItem->purchase_order_id][]=$paymentItem;
$returnBarcodes=$this->db->select('ib.id,ib.barcode,ib.serial_number,ib.qty_sisa,ib.source_receipt_line_id,pol.tracking_type')->from('item_barcodes ib')->join('goods_receipt_lines grl','grl.id=ib.source_receipt_line_id')->join('purchase_order_lines pol','pol.id=grl.po_line_id')->where('ib.qty_sisa >',0)->order_by('ib.barcode')->get()->result_array();
$budgetCompanyId=(int)$this->session->userdata('company_id');$poBudgetAccounts=$this->db->select('id,kode_akun,nama_akun')->where(array('company_id'=>$budgetCompanyId,'tipe'=>'expense','is_active'=>1,'allow_posting'=>1))->order_by('kode_akun')->get('accounts')->result_array();
function p_action($route,$id,$label,$ci,$step,$class='outline-primary'){return'<form class="d-inline" method="post" action="'.base_url('purchases/'.$route.'/'.$id).'"><input type="hidden" name="'.$ci->security->get_csrf_token_name().'" value="'.$ci->security->get_csrf_hash().'"><input type="hidden" name="return_step" value="'.$step.'"><button class="btn btn-sm btn-'.$class.'">'.html_escape($label).'</button></form>';}
function p_docs($type,$id,$status=null){$ci=&get_instance();$master=$ci->session->userdata('role')==='Admin';$canReturnView=$master||check_permission('purchase_returns','can_view');$canReturnCreate=$master||check_permission('purchase_returns','can_create');$canRefundView=$master||check_permission('purchase_refunds','can_view');$canRefundCreate=$master||check_permission('purchase_refunds','can_create');$canDebitView=$master||check_permission('supplier_debit_notes','can_view');$canDebitCreate=$master||check_permission('supplier_debit_notes','can_create');$detailClass=$type==='purchase_order'?'purchase-order-detail':($type==='supplier_payment'?'purchase-payment-detail':($type==='goods_receipt'?'purchase-receipt-detail':($type==='supplier_invoice'?'purchase-invoice-detail':'purchase-detail')));$pdfUrl=$type==='supplier_payment'?'purchasedocuments/payment_bank_pdf/'.$id:($type==='goods_receipt'?'purchasedocuments/receipt_pdf/'.$id:($type==='supplier_invoice'?'purchasedocuments/invoice_pdf/'.$id:'purchasedocuments/pdf/'.$type.'/'.$id));$links=array();if($type==='purchase_order'&&in_array($status,array('approved','partially_received','received'),true)){if($canReturnCreate)$links[]=array('Ajukan Retur','return&po_id='.$id);if($canRefundCreate)$links[]=array('Ajukan Refund','refund&po_id='.$id);if($canReturnView||$canRefundView)$links[]=array('Lihat Retur/Refund Terkait','history&po_id='.$id);}elseif($type==='goods_receipt'&&$status==='posted'){if($canReturnCreate){$links[]=array('Retur Barang','return&receipt_id='.$id);$links[]=array('Laporkan Kekurangan','return&category=specification&receipt_id='.$id);$links[]=array('Laporkan Barang Rusak','return&category=damaged&receipt_id='.$id);}if($canReturnView){$links[]=array('Catat Barang Pengganti','replacement&receipt_id='.$id);$links[]=array('Lihat Retur Terkait','history&receipt_id='.$id);}}elseif($type==='supplier_invoice'&&in_array($status,array('partial','paid'),true)){if($canDebitCreate)$links[]=array('Buat Debit Note','debit_note&invoice_id='.$id);if($canRefundCreate)$links[]=array('Ajukan Refund','refund&invoice_id='.$id);if($canDebitView||$canRefundView)$links[]=array('Lihat Penyesuaian Tagihan','history&invoice_id='.$id);}elseif($type==='supplier_payment'&&$status==='posted'){if($canRefundCreate)$links[]=array('Ajukan Refund','refund&payment_id='.$id);if($canRefundView)$links[]=array('Lihat Refund Terkait','history&payment_id='.$id);}$context='';if($links){$context=' <span class="dropdown"><button class="btn btn-sm btn-outline-warning dropdown-toggle" type="button" data-bs-toggle="dropdown"><i class="bi bi-arrow-left-right me-1"></i>Tindak Lanjut</button><span class="dropdown-menu dropdown-menu-end">';foreach($links as$link)$context.='<a class="dropdown-item" href="'.base_url('purchases/returns?action='.$link[1]).'">'.$link[0].'</a>';$context.='</span></span>';}$pdf=$master||check_permission('purchases','can_export')?' <a target="_blank" class="btn btn-sm btn-outline-danger" href="'.base_url($pdfUrl).'"><i class="bi bi-file-earmark-pdf me-1"></i>PDF</a>':'';return'<button type="button" class="btn btn-sm btn-outline-info '.$detailClass.'" data-type="'.$type.'" data-id="'.(int)$id.'"><i class="bi bi-eye me-1"></i>Detail</button>'.$pdf.$context;}
?>
<div class="container-fluid py-3">
<div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-3"><div><h4 class="mb-1">Workflow Pembelian</h4><p class="text-muted mb-0">Alur normal dari permintaan sampai finalisasi Invoice Supplier.</p></div><a class="btn btn-outline-primary" href="<?=base_url('purchases/returns')?>"><i class="bi bi-arrow-return-left me-1"></i>Retur & Refund</a></div>
<?php foreach(array('success'=>'success','error'=>'error')as$key=>$icon)if($this->session->flashdata($key)):?><div class="d-none" data-app-toast data-icon="<?=$icon?>" data-message="<?=html_escape($this->session->flashdata($key))?>"></div><?php endif?>
<?php $workflowSteps=array('request'=>array('bi-file-earmark-plus','Permintaan','Kebutuhan barang'),'order'=>array('bi-cart-check','Purchase Order','Pesanan disetujui'),'payment'=>array('bi-wallet2','Pembayaran Awal/DP','Jika disyaratkan'),'receipt'=>array('bi-box-arrow-in-down','Penerimaan','Pemeriksaan gudang'),'invoice'=>array('bi-receipt','Invoice Supplier','Final matching'));?>
<nav class="purchase-workflow-stepper mb-4" aria-label="Tahapan Purchase Workflow"><?php $stepNo=0;foreach($workflowSteps as$key=>$meta):$stepNo++;?><a class="purchase-workflow-step <?=$step===$key?'active':''?>" href="<?=base_url('purchases?step='.$key)?>"><span class="workflow-number"><?=$stepNo?></span><i class="bi <?=$meta[0]?>"></i><span><b><?=$meta[1]?></b><small><?=$meta[2]?></small></span></a><?php endforeach?></nav>
<?php if($step==='order'):?>
<div class="card mb-3"><div class="card-header"><b>Buat PO dari PR Disetujui</b><small class="d-block text-muted">Isi data order di kiri dan periksa rincian serta total PO di kanan.</small></div><div class="card-body">
<form method="post" action="<?=base_url('purchases/create_order')?>" id="professionalPoForm" enctype="multipart/form-data">
<input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="order">
<div class="row g-4">
<div class="col-xl-5"><div class="border rounded p-3 h-100"><h6 class="mb-3"><i class="bi bi-cart-check me-1"></i>Data Purchase Order</h6><div class="row g-3">
<div class="col-12"><label class="form-label">Purchase Request</label><select class="form-select" name="request_id" id="professionalPoRequest" required><option value="">Pilih PR disetujui</option><?php foreach($requests as$r)if($r->status==='approved'):?><option value="<?=$r->id?>"><?=html_escape($r->request_no.' · '.$r->department)?></option><?php endif?></select></div>
<div class="col-12"><label class="form-label">Supplier/Toko/Marketplace Seller</label><select class="form-select select-search" name="supplier_id" required><?php foreach($suppliers as$s):?><option value="<?=$s->id?>"><?=html_escape($s->supplier_code.' - '.$s->name)?></option><?php endforeach?></select></div>
<div class="col-12"><label class="form-label">Gudang Tujuan</label><select class="form-select" name="warehouse_id" required><?php foreach($warehouses as$w):?><option value="<?=$w->id?>"><?=html_escape($w->nama)?></option><?php endforeach?></select></div>
<div class="col-md-6"><label class="form-label">Tanggal PO</label><input class="form-control" type="date" name="date" value="<?=date('Y-m-d')?>" required></div><div class="col-md-6"><label class="form-label">Estimasi Datang</label><input class="form-control" type="date" name="expected_date"></div>
<div class="col-md-6"><label class="form-label">Metode Pengadaan</label><select class="form-select" name="procurement_method" id="procurementMethod"><option value="online">Pesanan Online</option><option value="direct">Pembelian Langsung di Toko</option><option value="supplier" selected>Supplier Biasa</option></select></div><div class="col-md-6"><label class="form-label">Waktu Pembayaran</label><select class="form-select" name="payment_timing" id="paymentTiming"><option value="prepaid">Bayar Sebelum Barang Diterima</option><option value="on_receipt">Bayar Saat Barang Diterima</option><option value="installment">Cicilan</option><option value="credit" selected>Kredit/Jatuh Tempo</option></select></div>
<div class="col-12 online-fields d-none"><div class="row g-2"><div class="col-md-6"><input class="form-control" name="marketplace" placeholder="Marketplace"></div><div class="col-md-6"><input class="form-control" name="external_order_no" placeholder="Nomor pesanan"></div><div class="col-md-6"><input class="form-control" name="courier" placeholder="Kurir"></div><div class="col-md-6"><input class="form-control" name="tracking_no" placeholder="Nomor resi"></div></div></div>
<div class="col-12 installment-fields d-none"><div class="border rounded p-3"><div class="row g-3"><div class="col-md-6"><label class="form-label">Jumlah Cicilan Setelah DP</label><input class="form-control installment-required" type="number" name="installment_count" min="2" value="3"><small class="text-muted">Contoh total Rp600.000 tanpa DP dan 3 cicilan = Rp200.000 per bulan.</small></div><div class="col-md-6"><label class="form-label">Jatuh Tempo Cicilan Pertama</label><input class="form-control installment-required" type="date" name="first_due_date"></div><div class="col-12"><div class="form-check form-switch"><input class="form-check-input" type="checkbox" name="down_payment_required" value="1" id="downPaymentRequired"><label class="form-check-label" for="downPaymentRequired">PO ini mensyaratkan DP sebelum barang diterima</label></div></div><div class="col-md-6 down-payment-field d-none"><label class="form-label">Nominal DP</label><input class="form-control format-rupiah" name="down_payment_amount" value="0" inputmode="decimal"><small class="text-muted">Hanya DP ini yang muncul di Menu Pembayaran.</small></div></div></div></div>
<?php foreach(array('shipping_cost'=>'Ongkos Kirim','insurance_cost'=>'Asuransi','service_fee'=>'Biaya Layanan','discount_amount'=>'Diskon/Voucher','cashback_amount'=>'Cashback')as$n=>$label):?><div class="col-md-6"><label class="form-label"><?=$label?></label><input class="form-control format-rupiah po-cost-input" name="<?=$n?>" value="0" inputmode="decimal"></div><?php endforeach?>
<div class="col-12"><div class="border rounded p-3 bg-light"><label class="form-label mb-1" id="poDocumentLabel">Dokumen Pendukung Order Supplier</label><p class="small text-muted mb-2" id="poDocumentHelp">Unggah penawaran, konfirmasi order, atau dokumen kesepakatan supplier.</p><input class="form-control" type="file" name="purchase_documents[]" id="poDocuments" accept="image/jpeg,image/png,image/webp,application/pdf" multiple required><small class="text-muted">Maksimal 10 file, masing-masing 5 MB.</small><div class="row g-2 mt-2" id="poDocumentPreview"></div></div></div>
<div class="col-12"><label class="form-label">Catatan PO</label><textarea class="form-control" name="notes" rows="2"></textarea></div>
</div></div></div>
<div class="col-xl-7"><div class="border rounded overflow-hidden h-100"><div class="px-3 py-2 bg-light border-bottom"><b><i class="bi bi-receipt me-1"></i>Detail Pemesanan</b><small class="d-block text-muted">Harga dan pajak dapat disesuaikan sebelum PO disimpan.</small></div>
<div id="poRequestLines" class="table-responsive"><div class="text-muted text-center p-5"><i class="bi bi-inbox fs-2 d-block mb-2"></i>Pilih PR untuk memuat semua barang.</div></div>
<div class="p-3 border-top bg-light"><div class="row justify-content-end"><div class="col-sm-8">
<div class="d-flex justify-content-between mb-1"><span>Subtotal barang</span><span id="poLiveSubtotal">Rp 0,00</span></div><div class="d-flex justify-content-between mb-1"><span>Pajak</span><span id="poLiveTax">Rp 0,00</span></div><div class="d-flex justify-content-between mb-1"><span>Biaya tambahan</span><span id="poLiveAdditional">Rp 0,00</span></div><div class="d-flex justify-content-between mb-1"><span>Diskon & cashback</span><span id="poLiveDiscount">- Rp 0,00</span></div><div class="d-flex justify-content-between fw-bold fs-5 border-top mt-2 pt-2 text-primary"><span>Total PO</span><span id="poLiveTotal">Rp 0,00</span></div>
</div></div></div>
<div class="p-3 border-top d-grid"><button class="btn btn-primary" id="savePoButton"><i class="bi bi-check2-circle me-1"></i>Simpan Draft PO</button></div>
</div></div>
</div>
</form>
</div></div>
<div class="card"><div class="card-header">Daftar Purchase Order</div><div class="table-responsive"><table class="table"><thead><tr><th>PO</th><th>Supplier</th><th>Metode</th><th>Pembayaran</th><th>Total</th><th>Status</th><th>Aksi</th></tr></thead><tbody><?php foreach($orders as$o):?><tr><td><?=html_escape($o->po_no)?></td><td><?=html_escape($o->name)?></td><td><?=html_escape($o->procurement_method)?></td><td><?=html_escape($o->payment_timing)?> · <?=html_escape($o->payment_status)?></td><td class="text-end"><?=number_format($o->total,2,',','.')?></td><td><?=$o->status?></td><td><?=p_docs('purchase_order',$o->id,$o->status)?> <?php if($o->status==='draft')echo p_action('submit_order',$o->id,'Ajukan',$this,'order');elseif($o->status==='submitted'&&($this->session->userdata('role')==='Admin'||check_permission('purchases','can_approve')))echo p_action('approve_order',$o->id,'Approve',$this,'order','success');?></td></tr><?php endforeach?></tbody></table></div></div>
<?php elseif($step==='payment'):?>
<div class="alert alert-info">Pembayaran sebelum barang diterima dicatat ke <b>Barang Dalam Perjalanan</b>. Stok baru bertambah saat Penerimaan Gudang.</div>
<?php foreach($orders as$o):$isPrepaid=$o->payment_timing==='prepaid';$isRequiredDp=$o->payment_timing==='installment'&&(int)($o->down_payment_required??0)===1;$paymentTarget=$isRequiredDp?(float)$o->down_payment_amount:(float)$o->total;if(!in_array($o->status,array('approved','partially_received'),true)||(!$isPrepaid&&!$isRequiredDp)||(float)$o->paid_amount>=$paymentTarget-.001)continue;$requiredPayment=round($paymentTarget-$o->paid_amount,2);$paymentLabel=$isRequiredDp?'DP wajib dibayar':'Wajib dibayar';$poPaymentItems=$paymentItems[$o->id]??array();?>
<div class="card mb-3 purchase-payment-card">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<div><b><?=html_escape($o->po_no.' · '.$o->name)?></b><small class="d-block text-muted"><?=html_escape($o->procurement_method==='online'?'Pesanan online '.($o->marketplace?:''):'Pembelian '.$o->procurement_method)?></small></div>
<span class="badge bg-primary-subtle text-primary fs-6"><?=html_escape($paymentLabel)?> Rp <?=number_format($requiredPayment,2,',','.')?></span>
</div>
<div class="card-body"><div class="row g-4">
<div class="col-xl-5"><div class="payment-form-panel border rounded p-3 h-100">
<h6 class="mb-3"><i class="bi bi-wallet2 me-1"></i>Form Pembayaran</h6>
<form class="row g-3 payment-source-form" data-required="<?=$requiredPayment?>" data-plan="<?=html_escape($o->payment_timing)?>" method="post" enctype="multipart/form-data" action="<?=base_url('purchases/pay_order/'.$o->id)?>">
<input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="payment">
<div class="col-md-6"><label class="form-label">Tanggal Pembayaran</label><input class="form-control" type="date" name="date" value="<?=date('Y-m-d')?>" required></div>
<div class="col-md-6"><label class="form-label">Referensi</label><input class="form-control" name="reference_no" placeholder="Nomor transfer/struk" required></div>
<div class="col-12 payment-sources"><label class="form-label">Sumber Kas/Bank & Total Uang Keluar</label><div class="row g-2 payment-source"><div class="col-6"><select class="form-select" name="source_account_id[]" required><?php foreach($paymentAccounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><div class="col-5"><input class="form-control format-rupiah payment-source-amount" name="source_amount[]" placeholder="Pokok + admin" inputmode="decimal" required></div><div class="col-1 d-grid"><button type="button" class="btn btn-outline-danger remove-source px-2" title="Hapus sumber"><i class="bi bi-x-lg"></i></button></div></div><small class="text-muted">Isi nominal yang benar-benar keluar dari akun Kas/Bank.</small></div>
<div class="col-12"><button type="button" class="btn btn-sm btn-outline-secondary add-source"><i class="bi bi-plus-lg me-1"></i>Tambah Akun</button></div>
<div class="col-md-5"><label class="form-label">Admin Bank <span class="text-muted">(opsional)</span></label><input class="form-control format-rupiah payment-bank-charge" name="bank_charge_amount" value="0" inputmode="decimal"><small class="text-muted">Tidak mengurangi saldo PO.</small></div><div class="col-md-7"><label class="form-label">Akun Biaya Admin</label><select class="form-select payment-bank-charge-account" name="bank_charge_account_id"><?php foreach($bankChargeAccounts as$a):?><option value="<?=$a->id?>" <?=$a->id===$bankChargeAccountId?'selected':''?>><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div>
<div class="col-12"><div class="payment-calculation rounded p-3 bg-light border"><div class="d-flex justify-content-between mb-1"><span><?=html_escape($paymentLabel)?> (pokok)</span><b>Rp <?=number_format($requiredPayment,2,',','.')?></b></div><div class="d-flex justify-content-between mb-1"><span>Admin bank</span><b class="payment-charge-display">Rp 0,00</b></div><div class="d-flex justify-content-between mb-1 fw-semibold"><span>Total wajib keluar</span><b class="payment-cash-required">Rp <?=number_format($requiredPayment,2,',','.')?></b></div><div class="d-flex justify-content-between mb-2"><span>Total sumber Kas/Bank</span><b class="payment-entered">Rp 0,00</b></div><div class="d-flex justify-content-between pt-2 border-top"><span class="payment-difference-label">Kekurangan sumber dana</span><b class="payment-difference text-danger">Rp <?=number_format($requiredPayment,2,',','.')?></b></div><div class="payment-balance-status small mt-2 text-danger"><i class="bi bi-exclamation-circle me-1"></i>Nominal pembayaran belum sesuai.</div></div></div>
<div class="col-12"><label class="form-label">Bukti Pembayaran</label><input class="form-control purchase-evidence-input" type="file" name="payment_documents[]" accept="image/jpeg,image/png,image/webp,application/pdf" multiple required><small class="text-muted">Wajib. Maksimal 10 file, masing-masing 5 MB.</small><div class="purchase-evidence-preview mt-2"></div></div>
<div class="col-12"><label class="form-label">Catatan</label><textarea class="form-control" name="notes" placeholder="Catatan pembayaran"></textarea></div>
<div class="col-12 d-grid"><button class="btn btn-primary payment-submit" disabled><i class="bi bi-check2-circle me-1"></i>Post Pembayaran</button></div>
</form>
</div></div>
<div class="col-xl-7"><div class="order-detail-panel border rounded overflow-hidden h-100">
<div class="px-3 py-2 bg-light border-bottom"><b><i class="bi bi-receipt me-1"></i>Detail Pemesanan</b><small class="d-block text-muted">Periksa barang dan nilai PO sebelum pembayaran.</small></div>
<div class="table-responsive"><table class="table table-sm align-middle mb-0"><thead><tr><th>Kode - Barang</th><th>Spesifikasi</th><th class="text-end">Qty</th><th class="text-end">Harga</th><th class="text-end">Total</th></tr></thead><tbody><?php foreach($poPaymentItems as$item):?><tr><td><b><?=html_escape(($item->kode_barang?:'-').' - '.($item->nama_barang?:'-'))?></b><small class="d-block text-muted"><?=html_escape($item->tracking_type?:'-')?></small></td><td><?=html_escape($item->description?:'-')?></td><td class="text-end"><?=number_format($item->qty,0,',','.')?></td><td class="text-end">Rp <?=number_format($item->unit_price,2,',','.')?></td><td class="text-end fw-semibold">Rp <?=number_format($item->line_total,2,',','.')?></td></tr><?php endforeach?></tbody></table></div>
<div class="p-3 border-top bg-light"><div class="row justify-content-end"><div class="col-sm-8"><div class="d-flex justify-content-between"><span>Subtotal</span><span>Rp <?=number_format($o->subtotal,2,',','.')?></span></div><div class="d-flex justify-content-between"><span>Pajak</span><span>Rp <?=number_format($o->tax_total,2,',','.')?></span></div><div class="d-flex justify-content-between"><span>Ongkir, asuransi & layanan</span><span>Rp <?=number_format($o->shipping_cost+$o->insurance_cost+$o->service_fee,2,',','.')?></span></div><div class="d-flex justify-content-between"><span>Diskon & cashback</span><span>- Rp <?=number_format($o->discount_amount+$o->cashback_amount,2,',','.')?></span></div><div class="d-flex justify-content-between fw-bold border-top mt-2 pt-2"><span>Total PO</span><span>Rp <?=number_format($o->total,2,',','.')?></span></div><div class="d-flex justify-content-between text-success"><span>Sudah dibayar</span><span>Rp <?=number_format($o->paid_amount,2,',','.')?></span></div><div class="d-flex justify-content-between fw-bold text-primary fs-6"><span>Wajib dibayar</span><span>Rp <?=number_format($requiredPayment,2,',','.')?></span></div></div></div></div>
</div></div>
</div></div>
</div>
<?php endforeach?>
<div class="card"><div class="card-header">Riwayat Pembayaran Pembelian</div><div class="table-responsive"><table class="table"><tr><th>No.</th><th>PO</th><th>Supplier</th><th>Tanggal</th><th>Jenis</th><th class="text-end">Pokok</th><th class="text-end">Admin Bank</th><th class="text-end">Total Keluar</th><th>Dokumen</th></tr><?php foreach($payments as$p):?><tr><td><?=html_escape($p->payment_no)?></td><td><?=html_escape($p->po_no?:'-')?></td><td><?=html_escape($p->supplier_name)?></td><td><?=$p->payment_date?></td><td><?=$p->payment_kind?></td><td class="text-end"><?=number_format($p->amount,2,',','.')?></td><td class="text-end"><?=number_format($p->bank_charge_amount??0,2,',','.')?></td><td class="text-end fw-semibold"><?=number_format(($p->total_cash_out??0)>0?$p->total_cash_out:($p->amount+($p->bank_charge_amount??0)),2,',','.')?></td><td><?=p_docs('supplier_payment',$p->id,$p->status)?></td></tr><?php endforeach?></table></div></div>
<?php elseif($step==='receipt'):?>
<div class="d-flex align-items-center justify-content-between mb-3"><div><h5 class="mb-1">Penerimaan Barang ke Gudang</h5><p class="text-muted mb-0">Periksa barang fisik, catat surat jalan, kuantitas aktual, dan bukti penerimaan.</p></div><span class="badge bg-primary-subtle text-primary">Petugas Gudang</span></div>
<?php $availableReceipt=false;foreach($orders as$o):if(!in_array($o->status,array('approved','partially_received'),true))continue;$availableReceipt=true;$lines=$this->db->where('purchase_order_id',$o->id)->get('purchase_order_lines')->result();?>
<div class="card mb-3 shadow-sm"><div class="card-header bg-body d-flex flex-wrap justify-content-between gap-2"><div><b><?=html_escape($o->po_no)?></b><small class="d-block text-muted"><?=html_escape($o->name)?></small></div><div class="text-end"><small class="text-muted d-block">Kurir / Resi</small><span><?=html_escape(($o->courier?:'-').' / '.($o->tracking_no?:'-'))?></span></div></div><div class="card-body"><form method="post" enctype="multipart/form-data" action="<?=base_url('purchases/receive/'.$o->id)?>" class="row g-3 receipt-evidence-form"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="receipt"><div class="col-md-4"><label class="form-label">Tanggal Diterima</label><input class="form-control" type="date" name="date" value="<?=date('Y-m-d')?>" required></div><div class="col-md-8"><label class="form-label">Nomor Surat Jalan / Resi</label><input class="form-control" name="delivery_no" placeholder="Masukkan nomor surat jalan atau resi pengiriman" required></div><div class="col-12"><label class="form-label">Barang yang Diterima</label><div class="table-responsive border rounded"><table class="table table-sm align-middle mb-0"><thead class="table-light"><tr><th>Barang</th><th class="text-end">Dipesan</th><th class="text-end">Sudah Diterima</th><th style="width:180px">Diterima Sekarang</th></tr></thead><tbody><?php foreach($lines as$l):$remaining=$l->qty-$l->received_qty;?><tr><td><?=html_escape($l->description)?></td><td class="text-end"><?=number_format($l->qty,2,',','.')?></td><td class="text-end"><?=number_format($l->received_qty,2,',','.')?></td><td><input class="form-control form-control-sm" type="number" name="qty[<?=$l->id?>]" min="0" max="<?=$remaining?>" step=".01" placeholder="Maks. <?=number_format($remaining,2,',','.')?>"></td></tr><?php endforeach?></tbody></table></div></div><div class="col-12"><div class="border rounded p-3 bg-light"><label class="form-label mb-1">Bukti Penerimaan Barang</label><p class="small text-muted mb-2">Unggah foto kondisi paket/barang, surat jalan bertanda tangan, atau dokumen penerimaan. Bisa memilih beberapa file sekaligus.</p><input class="form-control receipt-evidence-input" type="file" name="receipt_documents[]" accept="image/jpeg,image/png,image/webp,application/pdf" multiple required><small class="text-muted">Wajib. Maksimal 10 file, masing-masing 5 MB.</small><div class="receipt-evidence-preview mt-2"></div></div></div><div class="col-12 d-flex justify-content-end"><button class="btn btn-primary"><i class="bi bi-box-arrow-in-down me-1"></i>Post Penerimaan & Buat Barcode</button></div></form></div></div>
<?php endforeach;if(!$availableReceipt):?><div class="alert alert-light border text-center py-4"><i class="bi bi-inbox fs-3 d-block text-muted mb-2"></i>Tidak ada Purchase Order yang siap diterima.</div><?php endif?>
<div class="card mt-3"><div class="card-header">Riwayat Penerimaan</div><div class="table-responsive"><table class="table"><tr><th>No.</th><th>PO</th><th>Supplier</th><th>Tanggal</th><th>Surat Jalan/Resi</th><th>Dokumen</th></tr><?php foreach($receipts as$r):?><tr><td><?=$r->receipt_no?></td><td><?=$r->po_no?></td><td><?=html_escape($r->name)?></td><td><?=$r->receipt_date?></td><td><?=html_escape($r->supplier_delivery_no?:'-')?></td><td><?=p_docs('goods_receipt',$r->id,$r->status)?></td></tr><?php endforeach?></table></div></div>
<?php elseif($step==='invoice'):?>
<div class="row g-3 mb-3"><div class="col-lg-5"><div class="card shadow-sm h-100"><div class="card-header bg-body"><b>Finalisasi Dokumen Invoice Supplier</b><small class="d-block text-muted">Pilih penerimaan untuk memeriksa final hubungan PO, pembayaran, penerimaan, dan invoice supplier.</small></div><div class="card-body"><form class="row g-3" method="post" enctype="multipart/form-data" action="<?=base_url('purchases/create_bill')?>" id="supplierInvoiceForm"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="invoice"><input type="hidden" name="po_id" id="invoicePoId"><div class="col-12"><label class="form-label">Penerimaan Barang</label><select class="form-select" name="receipt_id" id="invoiceReceiptId" required><option value="">Pilih penerimaan yang akan ditagihkan</option><?php foreach($invoiceReceipts as$r):?><option value="<?=$r->id?>" data-po="<?=$r->purchase_order_id?>" data-po-no="<?=html_escape($r->po_no)?>" data-supplier="<?=html_escape($r->name)?>" data-date="<?=$r->receipt_date?>" data-delivery="<?=html_escape($r->supplier_delivery_no?:'-')?>"><?=html_escape($r->receipt_no.' · '.$r->po_no.' · '.$r->name)?></option><?php endforeach?></select></div><div class="col-md-7"><label class="form-label">Nomor Invoice/Nota Supplier</label><input class="form-control" name="supplier_invoice_no" placeholder="Sesuai nomor pada dokumen supplier" required></div><div class="col-md-5"><label class="form-label">Tanggal Invoice</label><input class="form-control" type="date" name="date" value="<?=date('Y-m-d')?>" required></div><div class="col-12"><div class="border rounded p-3 bg-light"><label class="form-label mb-1">Dokumen Invoice Supplier</label><p class="small text-muted mb-2">Unggah invoice/nota final dan faktur pajak bila ada. Dokumen pendukung PO tidak perlu diunggah ulang.</p><input class="form-control invoice-evidence-input" type="file" name="invoice_documents[]" accept="image/jpeg,image/png,image/webp,application/pdf" multiple required><small class="text-muted">Wajib. Maksimal 10 file, masing-masing 5 MB.</small><div class="invoice-evidence-preview mt-2"></div></div></div><div class="col-12 text-end"><button class="btn btn-primary"><i class="bi bi-file-earmark-check me-1"></i>Simpan & Periksa Final Matching</button></div></form></div></div></div><div class="col-lg-7"><div class="card h-100"><div class="card-header bg-body"><b>Ringkasan Dokumen Sumber</b></div><div class="card-body" id="invoiceSourceSummary"><div class="text-center text-muted py-5"><i class="bi bi-receipt fs-2 d-block mb-2"></i>Pilih penerimaan untuk melihat PO, supplier, dan barang.</div></div></div></div></div>
<div class="card"><div class="card-header bg-body"><b>Daftar Invoice Supplier</b><small class="d-block text-muted">Pemeriksaan final matching → Parsial atau Lunas. Tidak memerlukan approval ulang.</small></div><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Invoice</th><th>Supplier</th><th>Jatuh Tempo</th><th class="text-end">Total Invoice</th><th class="text-end">Total Sudah Dibayar</th><th class="text-end">Sisa Tagihan</th><th>Status</th><th>Aksi</th></tr></thead><tbody><?php foreach($bills as$b):?><tr><td><b><?=$b->internal_no?></b><small class="d-block text-muted"><?=html_escape($b->supplier_invoice_no)?></small></td><td><?=html_escape($b->name)?></td><td><?=$b->due_date?></td><?php $displayTotal=(float)$b->total;$displayPaid=max((float)($b->po_paid_amount??0),(float)$b->paid_amount);$displayBalance=max(0,$displayTotal-$displayPaid);?><td class="text-end fw-semibold"><?=number_format($displayTotal,2,',','.')?></td><td class="text-end fw-semibold text-primary"><?=number_format($displayPaid,2,',','.')?></td><td class="text-end fw-semibold"><?=number_format($displayBalance,2,',','.')?></td><td><span class="badge bg-<?=$b->status==='paid'?'success':($b->status==='draft'?'secondary':'warning')?>"><?=html_escape(array('verified'=>'PERLU FINALISASI','partial'=>'PARSIAL','paid'=>'LUNAS')[$b->status]??strtoupper($b->status))?></span></td><td class="text-nowrap"><?=p_docs('supplier_invoice',$b->id,$b->status)?> <?php if($b->status==='verified'){$prepaidReconcile=in_array($b->po_payment_timing,array('prepaid','on_receipt'),true)&&(float)$b->po_paid_amount>=(float)$b->total-.001;echo '<button type="button" class="btn btn-sm btn-warning bill-post-action" data-id="'.(int)$b->id.'" data-plan="'.html_escape($b->payment_plan).'" data-timing="'.html_escape($b->po_payment_timing).'" data-prepaid="'.($prepaidReconcile?'1':'0').'" data-total="'.html_escape($b->total).'" data-balance="'.html_escape($b->balance).'">'.($prepaidReconcile?'Rekonsiliasi Pembayaran':($b->payment_plan==='credit'?'Posting Hutang':'Proses Pembayaran')).'</button>';}?></td></tr><?php endforeach?></tbody></table></div></div>
<?php elseif($step==='legacy_adjustment_disabled'):?>
<div class="row g-3"><div class="col-lg-4"><div class="card h-100"><div class="card-header">Retur Barang</div><div class="card-body"><form method="post" action="<?=base_url('purchases/purchase_return')?>"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="adjustment"><select class="form-select mb-2" name="receipt_line_id" id="returnReceiptLine" required><option value="">Pilih penerimaan</option><?php foreach($this->db->select('grl.id,gr.receipt_no,i.nama_barang')->from('goods_receipt_lines grl')->join('goods_receipts gr','gr.id=grl.goods_receipt_id')->join('items i','i.id=grl.item_id')->get()->result()as$l):?><option value="<?=$l->id?>"><?=html_escape($l->receipt_no.' · '.$l->nama_barang)?></option><?php endforeach?></select><select class="form-select mb-2" name="barcode_id" id="returnBarcode" required></select><input class="form-control mb-2" type="date" name="date" value="<?=date('Y-m-d')?>"><input class="form-control mb-2" type="number" name="qty" min=".01" step=".01" placeholder="Qty"><textarea class="form-control mb-2" name="reason" placeholder="Alasan retur" required></textarea><button class="btn btn-warning">Post Retur</button></form></div></div></div>
<div class="col-lg-4"><div class="card h-100"><div class="card-header">Debit Note</div><div class="card-body"><form method="post" action="<?=base_url('purchases/debit_note')?>"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="adjustment"><select class="form-select mb-2" name="bill_id" required><?php foreach($bills as$b)if($b->status==='partial'&&$b->balance>0):?><option value="<?=$b->id?>"><?=html_escape($b->internal_no.' · '.$b->name)?></option><?php endif?></select><input class="form-control mb-2" type="date" name="date" value="<?=date('Y-m-d')?>"><input class="form-control format-rupiah mb-2" name="amount" placeholder="Nilai"><textarea class="form-control mb-2" name="reason" placeholder="Alasan" required></textarea><button class="btn btn-danger">Post Debit Note</button></form></div></div></div>
<div class="col-lg-4"><div class="card h-100"><div class="card-header">Refund Pesanan</div><div class="card-body"><form method="post" action="<?=base_url('purchases/refund_order/0')?>" id="refundForm"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="adjustment"><select class="form-select mb-2" id="refundPo" required><?php foreach($orders as$o):if($o->paid_amount<=$o->transit_cleared_amount)continue;?><option value="<?=$o->id?>"><?=html_escape($o->po_no.' · '.$o->name)?></option><?php endforeach?></select><select class="form-select mb-2" name="account_id" required><?php foreach($paymentAccounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select><input class="form-control mb-2" type="date" name="date" value="<?=date('Y-m-d')?>"><input class="form-control format-rupiah mb-2" name="amount" placeholder="Nilai refund"><textarea class="form-control mb-2" name="reason" placeholder="Alasan refund" required></textarea><button class="btn btn-success">Post Refund</button></form></div></div></div></div>
<div class="card mt-3"><div class="card-header">Riwayat Retur, Refund & Debit Note</div><div class="table-responsive"><table class="table"><tr><th>Jenis</th><th>Nomor</th><th>Tanggal</th><th>Nilai</th><th>Dokumen</th></tr><?php foreach($returns as$r):?><tr><td>Retur Barang</td><td><?=$r->return_no?></td><td><?=$r->return_date?></td><td><?=number_format($r->amount,2,',','.')?></td><td><?=p_docs('purchase_return',$r->id)?></td></tr><?php endforeach?><?php foreach($refunds as$r):?><tr><td>Refund</td><td><?=$r->refund_no?></td><td><?=$r->refund_date?></td><td><?=number_format($r->amount,2,',','.')?></td><td><?=p_docs('purchase_refund',$r->id)?></td></tr><?php endforeach?><?php foreach($debitNotes as$d):?><tr><td>Debit Note</td><td><?=$d->debit_note_no?></td><td><?=$d->note_date?></td><td><?=number_format($d->amount,2,',','.')?></td><td><?=p_docs('supplier_debit_note',$d->id)?></td></tr><?php endforeach?></table></div></div>
<?php endif?>
</div>
<style>
.purchase-workflow-stepper{display:flex;gap:.65rem;overflow-x:auto;padding:.2rem .1rem .65rem;scrollbar-width:thin}.purchase-workflow-step{position:relative;display:flex;align-items:center;gap:.6rem;min-width:190px;padding:.85rem .9rem;border:1px solid var(--bs-border-color);border-radius:14px;background:var(--bs-body-bg);color:var(--bs-body-color);text-decoration:none;box-shadow:0 .15rem .55rem rgba(30,42,70,.05)}.purchase-workflow-step:hover{border-color:var(--bs-primary);color:var(--bs-primary)}.purchase-workflow-step.active{background:linear-gradient(135deg,var(--bs-primary),#135ed2);border-color:var(--bs-primary);color:#fff;box-shadow:0 .4rem 1rem rgba(13,110,253,.2)}.purchase-workflow-step .workflow-number{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;background:rgba(13,110,253,.1);font-weight:700;flex:none}.purchase-workflow-step.active .workflow-number{background:rgba(255,255,255,.2)}.purchase-workflow-step>i{font-size:1.05rem}.purchase-workflow-step>span:last-child{display:flex;flex-direction:column;white-space:nowrap}.purchase-workflow-step small{font-size:.72rem;opacity:.72}@media(max-width:575.98px){.purchase-workflow-step{min-width:150px;padding:.7rem}.purchase-workflow-step small{display:none}.purchase-workflow-step>i{display:none}}
</style>
<div class="modal fade" id="purchaseDocumentModal" tabindex="-1"><div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Detail Dokumen Pembelian</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body" id="purchaseDocumentBody"></div><div class="modal-footer"><form id="purchaseAttachmentForm" enctype="multipart/form-data" class="me-auto d-flex gap-2"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="entity_type"><input type="hidden" name="entity_id"><input class="form-control form-control-sm" type="file" name="attachment" accept=".pdf,.jpg,.jpeg,.png,.webp,.csv" required><button class="btn btn-sm btn-outline-primary">Unggah Lampiran</button></form><button class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button></div></div></div></div>
<div class="modal fade attachment-preview-modal" id="purchaseAttachmentPreviewModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-lg modal-dialog-centered"><div class="modal-content"><div class="modal-header py-2"><h5 class="modal-title text-truncate" id="purchaseAttachmentPreviewTitle">Preview Lampiran</h5><button class="btn-close" data-bs-dismiss="modal" aria-label="Tutup"></button></div><div class="modal-body p-2 text-center" id="purchaseAttachmentPreviewBody"></div></div></div></div>
<div class="modal fade" id="supplierInvoicePostModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-dialog-centered modal-lg"><div class="modal-content"><form method="post" enctype="multipart/form-data" id="supplierInvoicePostForm"><div class="modal-header"><h5 class="modal-title">Posting Invoice Supplier</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="invoice"><div class="alert alert-info" id="invoicePostExplanation"></div><div id="invoicePaymentFields"><div class="mb-3"><label class="form-label">Sumber Kas/Bank</label><div id="invoicePaymentSources"><div class="row g-2 invoice-source-row mb-2"><div class="col-7"><select class="form-select" name="source_account_id[]"><?php foreach($paymentAccounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><div class="col-4"><input class="form-control format-rupiah invoice-source-amount" name="source_amount[]" placeholder="Nominal keluar" inputmode="decimal"></div><div class="col-1 d-grid"><button type="button" class="btn btn-outline-danger invoice-remove-source"><i class="bi bi-x-lg"></i></button></div></div></div><button type="button" class="btn btn-sm btn-outline-primary mb-3" id="invoiceAddSource"><i class="bi bi-plus-lg"></i> Tambah Akun</button><div class="row g-3 mb-3"><div class="col-md-5"><label class="form-label">Admin Bank <span class="text-muted">(opsional)</span></label><input class="form-control format-rupiah invoice-bank-charge" name="bank_charge_amount" value="0" inputmode="decimal"><small class="text-muted">Dicatat otomatis sebagai beban, bukan pembayaran invoice.</small></div><div class="col-md-7"><label class="form-label">Akun Biaya Admin</label><select class="form-select invoice-bank-charge-account" name="bank_charge_account_id"><?php foreach($bankChargeAccounts as$a):?><option value="<?=$a->id?>" <?=$a->id===$bankChargeAccountId?'selected':''?>><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div></div><div class="border rounded bg-light p-3 mb-3"><div class="d-flex justify-content-between"><span>Pokok invoice</span><b id="invoicePrincipalRequired">Rp 0,00</b></div><div class="d-flex justify-content-between"><span>Admin bank</span><b id="invoiceBankChargeDisplay">Rp 0,00</b></div><div class="d-flex justify-content-between fw-semibold"><span>Total wajib keluar</span><b id="invoiceCashOutRequired">Rp 0,00</b></div><div class="d-flex justify-content-between border-top mt-2 pt-2"><span>Total sumber Kas/Bank</span><b id="invoiceSourceTotal">Rp 0,00</b></div><div class="small text-danger mt-2" id="invoicePaymentStatus">Nominal sumber dana belum sesuai.</div></div><div class="row g-3"><div class="col-md-6"><label class="form-label">Tanggal Pembayaran</label><input class="form-control" type="date" name="payment_date" value="<?=date('Y-m-d')?>"></div><div class="col-md-6"><label class="form-label">Referensi Pembayaran</label><input class="form-control" name="reference_no" placeholder="Nomor transfer/struk"></div><div class="col-12"><label class="form-label">Bukti Pembayaran <span class="text-danger">*</span></label><input class="form-control purchase-evidence-input" type="file" name="invoice_payment_documents[]" accept="image/*,.pdf,application/pdf" multiple><small class="text-muted">Wajib untuk pembayaran baru. Maksimal 10 file, masing-masing 5 MB.</small><div class="purchase-evidence-preview mt-2"></div></div></div></div></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button><button class="btn btn-primary" id="invoicePostSubmit">Lanjutkan</button></div></form></div></div></div>
<style>
#poDocumentPreview{display:flex;flex-wrap:wrap;gap:.5rem;margin-left:0;margin-right:0}#poDocumentPreview>[class*=col-]{width:92px;padding:0;flex:0 0 92px}#poDocumentPreview .card,#purchaseDocumentBody .attachment-thumb{width:92px;cursor:pointer;border:1px solid var(--bs-border-color);transition:.15s ease}#poDocumentPreview .card:hover,#purchaseDocumentBody a.card:hover{transform:translateY(-2px);box-shadow:0 .25rem .75rem rgba(0,0,0,.12)}#poDocumentPreview .card-body{padding:.35rem!important}#poDocumentPreview img{height:62px!important;border-radius:5px!important}#poDocumentPreview .py-4{padding-top:.65rem!important;padding-bottom:.65rem!important}#purchaseDocumentBody a.card{width:100%;max-width:110px;margin:auto}#purchaseDocumentBody a.card img{height:72px!important}.attachment-preview-modal{z-index:1070}.attachment-preview-modal+.modal-backdrop{z-index:1065}.attachment-preview-modal .modal-dialog{width:auto;max-width:min(94vw,1200px);margin:.75rem auto}.attachment-preview-modal .modal-content{max-height:94vh}.attachment-preview-modal .modal-body{overflow:hidden;display:flex;align-items:center;justify-content:center;min-height:120px}.attachment-preview-modal img{display:block;max-width:100%;max-height:calc(94vh - 70px);width:auto;height:auto;object-fit:contain}.attachment-preview-modal iframe{display:block;width:min(90vw,1120px);height:calc(94vh - 70px);min-height:280px;border:0}@media(max-width:575.98px){.attachment-preview-modal .modal-dialog{max-width:calc(100vw - 1rem);margin:.5rem}.attachment-preview-modal iframe{width:calc(100vw - 1.25rem);height:calc(100vh - 85px)}.attachment-preview-modal img{max-height:calc(100vh - 85px)}}
</style>
<style>.purchase-evidence-preview,.receipt-evidence-preview,.invoice-evidence-preview{display:flex;flex-wrap:wrap;gap:.5rem}.purchase-evidence-preview .card,.receipt-evidence-preview .card,.invoice-evidence-preview .card{width:92px;cursor:pointer}.purchase-evidence-preview .card-body,.receipt-evidence-preview .card-body,.invoice-evidence-preview .card-body{padding:.35rem}.purchase-evidence-preview img,.receipt-evidence-preview img,.invoice-evidence-preview img{width:100%;height:62px;object-fit:cover;border-radius:5px}.purchase-evidence-preview small,.receipt-evidence-preview small,.invoice-evidence-preview small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}</style>
<style>.attachment-picker-hidden{position:absolute!important;width:1px!important;height:1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important}.attachment-gallery{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.5rem}.attachment-add-tile{width:92px;height:92px;border:2px dashed var(--bs-border-color);border-radius:.5rem;background:var(--bs-body-bg);color:var(--bs-secondary-color);display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;transition:.15s ease}.attachment-add-tile:hover{border-color:var(--bs-primary);color:var(--bs-primary);background:rgba(var(--bs-primary-rgb),.04)}.attachment-remove{position:absolute;right:4px;top:4px;width:24px;height:24px;padding:0;border:2px solid #fff;border-radius:50%;background:#dc3545;color:#fff;line-height:18px;font-size:17px;font-weight:700;z-index:20;box-shadow:0 2px 5px rgba(0,0,0,.35)}.attachment-gallery .card{position:relative;margin:0!important;flex:0 0 92px;overflow:visible!important}.attachment-gallery>[class*=col-]{width:92px!important;flex:0 0 92px!important;padding:0!important;overflow:visible!important}</style>
<script>
$(function(){
<?php if($this->session->userdata('role')!=='Admin'&&!check_permission('purchases','can_post')):?>$('.payment-source-form').closest('.card').remove();<?php endif?>
const poBudgetAccounts=<?=json_encode($poBudgetAccounts)?>;function poOptions(rows,placeholder){return'<option value="">'+placeholder+'</option>'+rows.map(x=>'<option value="'+x.id+'">'+String(x.kode_akun+' - '+x.nama_akun).replace(/</g,'&lt;')+'</option>').join('');}
function procurement(){var online=$('#procurementMethod').val()==='online',installment=$('#paymentTiming').val()==='installment',requiresDp=installment&&$('#downPaymentRequired').is(':checked');$('.online-fields').toggleClass('d-none',!online).find('[name=marketplace],[name=external_order_no]').prop('required',online);$('.installment-fields').toggleClass('d-none',!installment);$('.installment-required').prop('required',installment);$('.down-payment-field').toggleClass('d-none',!requiresDp).find('[name=down_payment_amount]').prop('required',requiresDp);}$('#procurementMethod,#paymentTiming,#downPaymentRequired').on('change',procurement);procurement();
function poNumber(value){var cleaned=window.cleanNumber?window.cleanNumber(value):String(value||'').replace(/\./g,'').replace(',','.');var number=Number(cleaned);return Number.isFinite(number)?number:0;}
function poMoney(value){return 'Rp '+Number(value||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});}
function updatePoTotal(){var subtotal=0,tax=0;$('#poRequestLines .po-item-row').each(function(){var row=$(this),qty=Number(row.data('qty')||0),price=poNumber(row.find('.po-unit-price').val()),rate=poNumber(row.find('.po-tax-rate').val()),base=qty*price,lineTax=base*rate/100;subtotal+=base;tax+=lineTax;row.find('.po-line-total').text(poMoney(base+lineTax));});var additional=poNumber($('[name=shipping_cost]').val())+poNumber($('[name=insurance_cost]').val())+poNumber($('[name=service_fee]').val()),discount=poNumber($('[name=discount_amount]').val())+poNumber($('[name=cashback_amount]').val()),total=Math.max(0,subtotal+tax+additional-discount);$('#poLiveSubtotal').text(poMoney(subtotal));$('#poLiveTax').text(poMoney(tax));$('#poLiveAdditional').text(poMoney(additional));$('#poLiveDiscount').text('- '+poMoney(discount));$('#poLiveTotal').text(poMoney(total));}
$('#professionalPoRequest').on('change',async function(){var box=$('#poRequestLines'),id=this.value;if(!id){box.html('<div class="text-muted text-center p-5">Pilih PR untuk memuat semua barang.</div>');updatePoTotal();return;}box.html('<div class="text-center p-5"><span class="spinner-border spinner-border-sm me-2"></span>Memuat barang...</div>');try{var res=await fetch('<?=base_url('purchases/request_detail/')?>'+id),json=await res.json(),lines=json.lines||[];if(!json.status)throw Error(json.message||'Gagal memuat PR.');box.html('<table class="table table-sm align-middle mb-0"><thead><tr><th>Barang & Akun Budget</th><th>Spesifikasi</th><th class="text-end">Qty</th><th style="width:145px">Harga Satuan</th><th style="width:100px">Pajak %</th><th class="text-end">Total</th></tr></thead><tbody>'+lines.map(function(x){return '<tr class="po-item-row" data-qty="'+Number(x.qty||0)+'"><td><b>'+String((x.kode_barang||'')+' - '+(x.master_name||'')).replace(/</g,'&lt;')+'</b><select class="form-select form-select-sm mt-1" name="budget_account_id['+x.id+']">'+poOptions(poBudgetAccounts,'Akun budget pengeluaran (opsional)')+'</select></td><td>'+String(x.description||'').replace(/</g,'&lt;')+'</td><td class="text-end">'+Number(x.qty).toLocaleString('id-ID')+'</td><td><input class="form-control form-control-sm format-rupiah po-unit-price" name="unit_price['+x.id+']" value="'+window.formatNumber(x.estimated_price)+'" inputmode="decimal" required></td><td><input class="form-control form-control-sm po-tax-rate" type="number" name="tax_rate['+x.id+']" value="0" min="0" step=".01"></td><td class="text-end fw-semibold po-line-total">Rp 0,00</td></tr>';}).join('')+'</tbody></table>');updatePoTotal();}catch(e){box.html('<div class="alert alert-danger m-3">'+String(e.message||'Gagal memuat barang PR.').replace(/</g,'&lt;')+'</div>');updatePoTotal();}});
$(document).on('input','.po-unit-price,.po-tax-rate,.po-cost-input',updatePoTotal);updatePoTotal();
function paymentNumber(value){var cleaned=window.cleanNumber?window.cleanNumber(value):String(value||'').replace(/\./g,'').replace(',','.');var number=Number(cleaned);return Number.isFinite(number)?number:0;}
function paymentMoney(value){return 'Rp '+Number(value||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});}
function updatePaymentBalance(form){var $form=$(form),principal=Number($form.data('required')||0),charge=Math.max(0,paymentNumber($form.find('.payment-bank-charge').val())),required=principal+charge,entered=0;$form.find('.payment-source-amount').each(function(){entered+=paymentNumber(this.value);});var difference=required-entered,valid=entered>0&&Math.abs(difference)<.005&&(!charge||$form.find('.payment-bank-charge-account').val()),$difference=$form.find('.payment-difference'),$status=$form.find('.payment-balance-status');$form.find('.payment-charge-display').text(paymentMoney(charge));$form.find('.payment-cash-required').text(paymentMoney(required));$form.find('.payment-entered').text(paymentMoney(entered));$form.find('.payment-difference-label').text(difference<-.005?'Kelebihan sumber dana':'Kekurangan sumber dana');$difference.text(paymentMoney(Math.abs(difference))).toggleClass('text-danger',!valid).toggleClass('text-success',valid);if(valid){$status.attr('class','payment-balance-status small mt-2 text-success').html('<i class="bi bi-check-circle me-1"></i>Balance: pokok '+paymentMoney(principal)+' + admin '+paymentMoney(charge)+' = '+paymentMoney(required)+'.');}else{$status.attr('class','payment-balance-status small mt-2 text-danger').html('<i class="bi bi-exclamation-circle me-1"></i>'+(charge&&!$form.find('.payment-bank-charge-account').val()?'Pilih akun biaya admin bank.':'Total sumber Kas/Bank harus sama dengan pokok pembayaran ditambah admin bank.'));}$form.data('payment-valid',!!valid).find('.payment-submit').prop('disabled',!valid);}
$('.payment-source-form').each(function(){updatePaymentBalance(this);});$(document).on('input change','.payment-source-amount,.payment-bank-charge,.payment-bank-charge-account',function(){updatePaymentBalance(this.form);});$(document).on('click','.add-source',function(){var form=$(this).closest('form'),sources=form.find('.payment-sources'),row=sources.find('.payment-source').first().clone();row.find('input').val('');sources.append(row);updatePaymentBalance(form[0]);});$(document).on('click','.remove-source',function(){var form=$(this).closest('form'),rows=$(this).closest('.payment-sources').find('.payment-source');if(rows.length>1)$(this).closest('.payment-source').remove();else $(this).closest('.payment-source').find('input').val('');updatePaymentBalance(form[0]);});$(document).on('submit','.payment-source-form',function(event){updatePaymentBalance(this);if(!$(this).data('payment-valid')){event.preventDefault();if(window.Swal)Swal.fire({toast:true,position:'top-end',icon:'error',title:'Total sumber Kas/Bank harus balance dengan pokok + admin bank.',showConfirmButton:false,timer:3000,timerProgressBar:true});}});
var barcodes=<?=json_encode($returnBarcodes)?>;function returnOptions(){var id=String($('#returnReceiptLine').val()||''),options=barcodes.filter(x=>String(x.source_receipt_line_id)===id);$('#returnBarcode').html('<option value="">Pilih barcode/serial</option>'+options.map(x=>'<option value="'+x.id+'" data-max="'+x.qty_sisa+'" data-tracking="'+x.tracking_type+'">'+x.barcode+(x.serial_number?' · '+x.serial_number:'')+' · '+x.qty_sisa+'</option>').join(''));}$('#returnReceiptLine').on('change',returnOptions);returnOptions();$('#returnBarcode').on('change',function(){var o=$(this).find(':selected'),input=$('[name=qty]',$(this).closest('form')),unit=o.data('tracking')==='UNIT';input.attr('max',o.data('max')||'').val(unit?1:'').prop('readonly',unit);});
$('#refundPo').on('change',function(){$('#refundForm').attr('action','<?=base_url('purchases/refund_order/')?>'+this.value);}).trigger('change');
$(document).on('click','.purchase-detail',async function(){var type=this.dataset.type,id=this.dataset.id,body=$('#purchaseDocumentBody');$('#purchaseDocumentModal .modal-title').text('Detail Dokumen Pembelian');body.html('<div class="text-center p-4">Memuat...</div>');$('#purchaseAttachmentForm [name=entity_type]').val(type);$('#purchaseAttachmentForm [name=entity_id]').val(id);bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseDocumentModal')).show();try{var res=await fetch('<?=base_url('purchasedocuments/detail/')?>'+type+'/'+id),json=await res.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],attachments=json.attachments||[];body.html('<dl class="row">'+Object.keys(h).filter(k=>h[k]!==null&&h[k]!==''&&k!=='id').map(k=>'<dt class="col-sm-4">'+k.replaceAll('_',' ')+'</dt><dd class="col-sm-8">'+$('<div>').text(h[k]).html()+'</dd>').join('')+'</dl><h6>Rincian Item</h6><pre class="bg-light p-2 rounded">'+$('<div>').text(JSON.stringify(lines,null,2)).html()+'</pre><h6>Lampiran</h6>'+(attachments.length?attachments.map(a=>'<a class="d-block" href="'+a.download_url+'">'+$('<div>').text(a.original_name).html()+'</a>').join(''):'<span class="text-muted">Belum ada lampiran.</span>'));}catch(e){body.html('<div class="alert alert-danger">'+e.message+'</div>');}});
$('#purchaseAttachmentForm').on('submit',async function(e){e.preventDefault();var res=await fetch('<?=base_url('accountingattachments/upload')?>',{method:'POST',body:new FormData(this)}),json=await res.json();if(json.status){AppAlert.toast(json.message,'success');var selector=this.entity_type.value==='supplier_invoice'?'.purchase-invoice-detail':'.purchase-detail';$(selector+'[data-id="'+this.entity_id.value+'"]').first().trigger('click');}else AppAlert.toast(json.message,'error');});
});
</script>
<script>
$(function(){
var esc=v=>$('<div>').text(v??'-').html(),qty=n=>Number(n||0).toLocaleString('id-ID',{maximumFractionDigits:2});
// Invoice Supplier adalah verifikasi matching, bukan approval berlapis.
$('#supplierInvoiceForm').closest('.card').find('.card-header b').first().text('Catat & Verifikasi Invoice Supplier');$('#supplierInvoiceForm button[type="submit"]').html('<i class="bi bi-file-earmark-check me-1"></i>Simpan & Verifikasi Matching');
// PR dan PO wajib dapat disetujui atau ditolak dengan alasan yang dapat diaudit.
$('form[action*="purchases/approve_order/"]').each(function(){var approve=$(this),id=approve.attr('action').split('/').pop(),reject=$('<button type="button" class="btn btn-sm btn-outline-danger ms-1">Reject</button>');approve.after(reject);reject.on('click',function(){Swal.fire({icon:'warning',title:'Tolak Purchase Order?',input:'textarea',inputLabel:'Alasan penolakan',inputPlaceholder:'Contoh: metode harus online, bukan pembelian langsung',inputValidator:v=>!String(v||'').trim()?'Alasan penolakan wajib diisi.':undefined,showCancelButton:true,confirmButtonText:'Tolak PO',cancelButtonText:'Batal'}).then(function(r){if(!r.isConfirmed)return;var f=$('<form method="post" action="<?=base_url('purchases/reject_order/')?>'+id+'"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="order"><input type="hidden" name="reason"></form>');f.find('[name=reason]').val(r.value);f.appendTo(document.body).trigger('submit');});});});
// Matching ditampilkan segera saat dokumen penerimaan dipilih.
$('#invoiceReceiptId').off('change').on('change',async function(){var option=this.options[this.selectedIndex],box=$('#invoiceSourceSummary'),id=this.value;$('#invoicePoId').val(option?option.dataset.po:'');if(!id){box.html('<div class="text-center text-muted py-5">Pilih penerimaan untuk memeriksa matching dan langkah pembayaran.</div>');return}box.html('<div class="text-center py-4"><span class="spinner-border spinner-border-sm me-2"></span>Memeriksa PO dan penerimaan...</div>');try{var response=await fetch('<?=base_url('purchasedocuments/detail/goods_receipt/')?>'+id),json=await response.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],esc=v=>$('<div>').text(v??'-').html(),qty=n=>Number(n||0).toLocaleString('id-ID',{maximumFractionDigits:2}),money=n=>'Rp '+Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2}),fullyPaid=Number(h.po_paid_amount||0)>=Number(h.po_total||0)-.001,next=fullyPaid?'Pembayaran sudah tercatat — setelah disimpan lakukan rekonsiliasi.':(h.payment_timing==='credit'?'Belum dibayar — setelah disimpan posting sebagai Hutang Usaha.':(h.payment_timing==='installment'?'Cicilan — pembayaran yang ada direkonsiliasi dan sisanya menjadi hutang.':'Belum dibayar — setelah disimpan lanjutkan Proses Pembayaran.'));var mismatch=lines.some(l=>Math.abs(Number(l.ordered_qty||0)-Number(l.total_received_qty||0))>.001);box.html('<div class="border rounded p-3 mb-3"><b>'+esc(h.supplier_code+' - '+h.supplier_name)+'</b><div class="small text-muted">PO '+esc(h.po_no)+' · Penerimaan '+esc(h.receipt_no)+'</div><div class="d-flex justify-content-between mt-2"><span>Total PO</span><b>'+money(h.po_total)+'</b></div><div class="d-flex justify-content-between"><span>Sudah dibayar</span><b>'+money(h.po_paid_amount)+'</b></div></div><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Barang</th><th class="text-end">PO</th><th class="text-end">Diterima Ini</th><th class="text-end">Total Diterima</th><th>Status</th></tr></thead><tbody>'+lines.map(function(l){var ok=Math.abs(Number(l.ordered_qty||0)-Number(l.total_received_qty||0))<.001;return '<tr><td>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||l.description))+'</td><td class="text-end">'+qty(l.ordered_qty)+'</td><td class="text-end">'+qty(l.qty)+'</td><td class="text-end">'+qty(l.total_received_qty)+'</td><td><span class="badge bg-'+(ok?'success':'warning')+'">'+(ok?'Sesuai':'Parsial/Selisih')+'</span></td></tr>'}).join('')+'</tbody></table></div><div class="alert alert-'+(mismatch?'warning':'success')+' small mb-2">'+(mismatch?'Jumlah PO dan total penerimaan belum seluruhnya sama. Invoice ini hanya mencatat barang pada penerimaan yang dipilih.':'Jumlah PO dan penerimaan sudah sesuai.')+'</div><div class="alert alert-info small mb-0"><b>Langkah berikutnya:</b> '+esc(next)+'</div>');}catch(error){box.html('<div class="alert alert-danger">'+esc(error.message)+'</div>')}});
<?php if($this->session->userdata('role')==='Admin'||check_permission('purchases','can_update')):?>
var suppliers=<?=json_encode(array_map(function($s){return array('id'=>$s->id,'text'=>$s->supplier_code.' - '.$s->name);},$suppliers),JSON_UNESCAPED_UNICODE)?>,warehouses=<?=json_encode(array_map(function($w){return array('id'=>$w->id,'text'=>$w->nama);},$warehouses),JSON_UNESCAPED_UNICODE)?>;
var editModal=$('<div class="modal fade" id="editPoModal"><div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"><div class="modal-content"><form method="post"><div class="modal-header"><h5 class="modal-title">Edit Draft Purchase Order</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="return_step" value="order"><div class="row g-3"><div class="col-md-6"><label class="form-label">Supplier</label><select class="form-select" name="supplier_id"></select></div><div class="col-md-6"><label class="form-label">Gudang</label><select class="form-select" name="warehouse_id"></select></div><div class="col-md-3"><label class="form-label">Tanggal PO</label><input class="form-control" type="date" name="date"></div><div class="col-md-3"><label class="form-label">Estimasi Datang</label><input class="form-control" type="date" name="expected_date"></div><div class="col-md-3"><label class="form-label">Metode</label><select class="form-select" name="procurement_method"><option value="online">Online</option><option value="direct">Pembelian Langsung</option><option value="supplier">Supplier Biasa</option></select></div><div class="col-md-3"><label class="form-label">Pembayaran</label><select class="form-select" name="payment_timing"><option value="prepaid">Sebelum Diterima</option><option value="on_receipt">Setelah Diterima</option><option value="installment">Cicilan</option><option value="credit">Kredit/Jatuh Tempo</option></select></div><div class="col-md-3"><input class="form-control" name="marketplace" placeholder="Marketplace"></div><div class="col-md-3"><input class="form-control" name="external_order_no" placeholder="Nomor pesanan"></div><div class="col-md-3"><input class="form-control" name="courier" placeholder="Kurir"></div><div class="col-md-3"><input class="form-control" name="tracking_no" placeholder="Nomor resi"></div><div class="col-12" id="editPoLines"></div><div class="col-md-4"><label>Ongkos Kirim</label><input class="form-control format-rupiah" name="shipping_cost"></div><div class="col-md-4"><label>Asuransi</label><input class="form-control format-rupiah" name="insurance_cost"></div><div class="col-md-4"><label>Biaya Layanan</label><input class="form-control format-rupiah" name="service_fee"></div><div class="col-md-4"><label>Diskon/Voucher</label><input class="form-control format-rupiah" name="discount_amount"></div><div class="col-md-4"><label>Cashback</label><input class="form-control format-rupiah" name="cashback_amount"></div><div class="col-12"><label>Catatan</label><textarea class="form-control" name="notes"></textarea></div></div></div><div class="modal-footer"><button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button><button class="btn btn-primary">Simpan Perubahan</button></div></form></div></div></div>').appendTo(document.body);editModal.find('[name=supplier_id]').html(suppliers.map(x=>'<option value="'+x.id+'">'+$('<div>').text(x.text).html()+'</option>'));editModal.find('[name=warehouse_id]').html(warehouses.map(x=>'<option value="'+x.id+'">'+$('<div>').text(x.text).html()+'</option>'));
editModal.find('[name="payment_timing"]').closest('.col-md-3').after('<div class="col-12 edit-installment-fields d-none"><div class="row g-2 border rounded p-2"><div class="col-md-4"><label>Jumlah Cicilan</label><input class="form-control" type="number" min="2" name="installment_count"></div><div class="col-md-4"><label>Jatuh Tempo Pertama</label><input class="form-control" type="date" name="first_due_date"></div><div class="col-md-4"><div class="form-check mt-4"><input class="form-check-input" type="checkbox" name="down_payment_required" value="1" id="editDownPayment"><label class="form-check-label" for="editDownPayment">Wajib DP</label></div><input class="form-control format-rupiah mt-1" name="down_payment_amount" placeholder="Nominal DP"></div></div></div>');
function toggleEditInstallment(){var installment=editModal.find('[name="payment_timing"]').val()==='installment';editModal.find('.edit-installment-fields').toggleClass('d-none',!installment).find('[name="installment_count"],[name="first_due_date"]').prop('required',installment);editModal.find('[name="down_payment_amount"]').prop('required',installment&&editModal.find('[name="down_payment_required"]').is(':checked'));}editModal.on('change','[name="payment_timing"],[name="down_payment_required"]',toggleEditInstallment);
$('form[action*="purchases/submit_order/"]').each(function(){var submit=$(this),id=submit.attr('action').split('/').pop(),button=$('<button type="button" class="btn btn-sm btn-outline-primary me-1"><i class="bi bi-pencil"></i> Edit</button>');submit.before(button);button.on('click',async function(){try{var r=await fetch('<?=base_url('purchasedocuments/detail/purchase_order/')?>'+id),j=await r.json();if(!j.status)throw Error(j.message);var h=j.header,form=editModal.find('form')[0];form.action='<?=base_url('purchases/update_order/')?>'+id;['supplier_id','order_date','expected_date','procurement_method','payment_timing','marketplace','external_order_no','courier','tracking_no','shipping_cost','insurance_cost','service_fee','discount_amount','cashback_amount','notes','installment_count','first_due_date','down_payment_amount'].forEach(function(k){var name=k==='order_date'?'date':k,x=form.elements[name];if(x)x.value=h[k]??''});form.elements.down_payment_required.checked=Number(h.down_payment_required||0)===1;form.elements.warehouse_id.value=h.warehouse_id||'';toggleEditInstallment();$('#editPoLines').html('<div class="table-responsive"><table class="table table-sm"><thead><tr><th>Barang</th><th class="text-end">Qty</th><th>Harga Satuan</th><th>Pajak %</th></tr></thead><tbody>'+j.lines.map(l=>'<tr><td>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||l.description))+'</td><td class="text-end">'+qty(l.qty)+'</td><td><input class="form-control format-rupiah" name="unit_price['+l.id+']" value="'+(window.formatNumber?window.formatNumber(l.unit_price):l.unit_price)+'"></td><td><input class="form-control" type="number" step=".01" min="0" name="tax_rate['+l.id+']" value="'+Number(l.tax_rate||0)+'"></td></tr>').join('')+'</tbody></table></div>');bootstrap.Modal.getOrCreateInstance(editModal[0]).show();}catch(e){AppAlert.toast(e.message,'error')}});});
<?php endif?>
});
</script>
<script>
$(function(){
$('input[type="file"][name="purchase_documents[]"],input[type="file"][name="payment_documents[]"],input[type="file"][name="receipt_documents[]"],input[type="file"][name="invoice_documents[]"],input[type="file"][name="invoice_payment_documents[]"]').attr('accept','image/*,.pdf,application/pdf');
$('#purchaseAttachmentForm [name="attachment"]').attr('accept','image/*,.pdf,application/pdf');$(document).on('click','.purchase-invoice-detail',function(){var id=this.dataset.id;setTimeout(function(){var form=$('#purchaseAttachmentForm');form.removeClass('d-none');form.find('[name="entity_type"]').val('supplier_invoice');form.find('[name="entity_id"]').val(id);},0);});
var documentInput=document.getElementById('poDocuments'),preview=document.getElementById('poDocumentPreview'),objectUrls=[];
function documentCopy(){var method=$('#procurementMethod').val(),copy={online:['Bukti / Detail Pesanan Online','Unggah screenshot detail pesanan, konfirmasi pembayaran, atau dokumen order marketplace. Invoice final tetap pada tahap Invoice Supplier.'],direct:['Struk Pembelian Langsung','Unggah foto/scan struk toko. Jika memperoleh invoice pajak/final, dokumen tersebut tetap dicatat pada tahap Invoice Supplier.'],supplier:['Dokumen Pendukung Order Supplier','Unggah penawaran, quotation, proforma, atau konfirmasi order supplier. Invoice final tetap pada tahap Invoice Supplier.']}[method];if(!copy)return;$('#poDocumentLabel').text(copy[0]);$('#poDocumentHelp').text(copy[1]);}
if($('#procurementMethod').length){$('#procurementMethod').on('change',documentCopy);documentCopy();}
if(documentInput)documentInput.addEventListener('change',function(){objectUrls.forEach(URL.revokeObjectURL);objectUrls=[];preview.innerHTML='';var files=Array.from(this.files||[]);if(files.length>10){AppAlert.toast('Maksimal 10 lampiran.','error');this.value='';return;}files.forEach(function(file){var col=document.createElement('div');col.className='col-6 col-md-3';var card=document.createElement('div');card.className='card h-100';var body=document.createElement('div');body.className='card-body p-2';if(file.type.indexOf('image/')===0){var url=URL.createObjectURL(file);objectUrls.push(url);var img=document.createElement('img');img.src=url;img.alt=file.name;img.style.cssText='width:100%;height:110px;object-fit:cover;border-radius:8px';body.appendChild(img);}else{body.innerHTML='<div class="text-center py-4"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>';}var name=document.createElement('small');name.className='d-block text-truncate mt-1';name.textContent=file.name;body.appendChild(name);card.appendChild(body);col.appendChild(card);preview.appendChild(col);});});
$(document).on('click','.purchase-order-detail',async function(){var id=this.dataset.id,body=$('#purchaseDocumentBody'),attachmentForm=$('#purchaseAttachmentForm');$('#purchaseDocumentModal .modal-title').text('Detail Purchase Order');attachmentForm.addClass('d-none');body.html('<div class="text-center p-4">Memuat...</div>');bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseDocumentModal')).show();try{var res=await fetch('<?=base_url('purchasedocuments/detail/purchase_order/')?>'+id),json=await res.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],attachments=json.attachments||[],methods={online:'Pesanan Online',direct:'Pembelian Langsung di Toko',supplier:'Supplier Biasa'},timings={prepaid:'Bayar Sebelum Barang Diterima',on_receipt:'Bayar Saat Barang Diterima',installment:'Cicilan',credit:'Kredit/Jatuh Tempo'},money=n=>Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2}),esc=v=>$('<div>').text(v??'-').html();var info='<div class="row g-3 mb-3"><div class="col-md-6"><div class="border rounded p-3 h-100"><small class="text-muted">Supplier</small><h6>'+esc((h.supplier_code||'')+' - '+(h.supplier_name||''))+'</h6><div>'+esc(h.supplier_address||'-')+'</div><div>'+esc(h.supplier_phone||'-')+'</div></div></div><div class="col-md-6"><div class="border rounded p-3 h-100"><div><small class="text-muted">Nomor PO</small><b class="float-end">'+esc(h.po_no)+'</b></div><div><small class="text-muted">Tanggal</small><span class="float-end">'+esc(h.order_date)+'</span></div><div><small class="text-muted">PR</small><span class="float-end">'+esc(h.request_no)+'</span></div><div><small class="text-muted">Gudang</small><span class="float-end">'+esc(h.warehouse_name)+'</span></div></div></div></div><div class="row g-2 mb-3"><div class="col-md-6"><small class="text-muted">Metode Pengadaan</small><div>'+esc(methods[h.procurement_method]||h.procurement_method)+'</div></div><div class="col-md-6"><small class="text-muted">Rencana Pembayaran</small><div>'+esc(timings[h.payment_timing]||h.payment_timing)+'</div></div>'+(h.procurement_method==='online'?'<div class="col-md-4"><small class="text-muted">Marketplace</small><div>'+esc(h.marketplace)+'</div></div><div class="col-md-4"><small class="text-muted">Nomor Pesanan</small><div>'+esc(h.external_order_no)+'</div></div><div class="col-md-4"><small class="text-muted">Kurir/Resi</small><div>'+esc((h.courier||'-')+' / '+(h.tracking_no||'-'))+'</div></div>':'')+'</div>';var table='<h6 class="mb-2">Barang yang Dipesan</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Kode - Barang</th><th>Spesifikasi</th><th class="text-end">Qty</th><th class="text-end">Harga</th><th class="text-end">Pajak</th><th class="text-end">Subtotal</th></tr></thead><tbody>'+lines.map(l=>'<tr><td>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||'-'))+'</td><td>'+esc(l.description)+'</td><td class="text-end">'+money(l.qty)+'</td><td class="text-end">'+money(l.unit_price)+'</td><td class="text-end">'+money(l.tax_rate)+'%</td><td class="text-end">'+money(l.line_total)+'</td></tr>').join('')+'</tbody></table></div>';var totals='<div class="row justify-content-end mt-3"><div class="col-md-5"><table class="table table-sm"><tr><td>Subtotal</td><td class="text-end">'+money(h.subtotal)+'</td></tr><tr><td>Pajak</td><td class="text-end">'+money(h.tax_total)+'</td></tr><tr><td>Ongkir + Asuransi + Layanan</td><td class="text-end">'+money(Number(h.shipping_cost)+Number(h.insurance_cost)+Number(h.service_fee))+'</td></tr><tr><td>Diskon + Cashback</td><td class="text-end">('+money(Number(h.discount_amount)+Number(h.cashback_amount))+')</td></tr><tr class="fw-bold"><td>Total PO</td><td class="text-end">'+money(h.total)+'</td></tr></table></div></div>';var files='<h6 class="mt-3">Dokumen Pendukung</h6><div class="row g-2">'+(attachments.length?attachments.map(a=>{var image=String(a.mime_type||'').indexOf('image/')===0;return '<div class="col-6 col-md-3"><a href="'+a.download_url+'" target="_blank" class="card text-decoration-none h-100"><div class="card-body p-2">'+(image?'<img src="'+a.download_url+'" style="width:100%;height:110px;object-fit:cover;border-radius:8px" alt="">':'<div class="text-center py-4"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>')+'<small class="d-block text-truncate">'+esc(a.original_name)+'</small></div></a></div>';}).join(''):'<div class="col-12 text-muted">Belum ada dokumen.</div>')+'</div>';body.html(info+table+totals+files);}catch(e){body.html('<div class="alert alert-danger">'+esc(e.message)+'</div>');}});
$(document).on('click','.purchase-detail',function(){$('#purchaseAttachmentForm').removeClass('d-none');});
function openPurchaseAttachment(url,type,name){var body=$('#purchaseAttachmentPreviewBody'),title=$('#purchaseAttachmentPreviewTitle');title.text(name||'Preview Lampiran');body.empty();if(String(type||'').indexOf('application/pdf')===0||/\.pdf(?:\?|$)/i.test(url)){body.html('<iframe title="Preview PDF"></iframe>');body.find('iframe').attr('src',url);}else{body.html('<img alt="">');body.find('img').attr({src:url,alt:name||'Preview lampiran'});}bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseAttachmentPreviewModal')).show();}
if(documentInput)documentInput.addEventListener('change',function(){var files=Array.from(this.files||[]),cards=preview.querySelectorAll('.card');files.forEach(function(file,index){var card=cards[index];if(!card)return;var media=card.querySelector('img'),url=media?media.src:URL.createObjectURL(file);if(!media)objectUrls.push(url);card.dataset.previewUrl=url;card.dataset.previewType=file.type;card.dataset.previewName=file.name;card.setAttribute('role','button');card.setAttribute('tabindex','0');card.setAttribute('title','Klik untuk preview');});});
$(document).on('click keydown','#poDocumentPreview .card',function(e){if(e.type==='keydown'&&e.key!=='Enter'&&e.key!==' ')return;e.preventDefault();openPurchaseAttachment(this.dataset.previewUrl,this.dataset.previewType,this.dataset.previewName);});
$(document).on('click','#purchaseDocumentBody a.card',function(e){e.preventDefault();var image=$(this).find('img').length>0,name=$(this).find('small').text();openPurchaseAttachment(this.href,image?'image/*':'application/pdf',name);});
$('.purchase-evidence-input,.receipt-evidence-input,.invoice-evidence-input').each(function(){var input=this,box=$(this).siblings('.purchase-evidence-preview,.receipt-evidence-preview,.invoice-evidence-preview')[0],urls=[];input.addEventListener('change',function(){urls.forEach(URL.revokeObjectURL);urls=[];box.innerHTML='';var files=Array.from(this.files||[]);if(files.length>10){AppAlert.toast('Maksimal 10 lampiran.','error');this.value='';return;}files.forEach(function(file){var url=URL.createObjectURL(file);urls.push(url);var image=file.type.indexOf('image/')===0,card=document.createElement('div');card.className='card';card.tabIndex=0;card.dataset.previewUrl=url;card.dataset.previewType=file.type;card.dataset.previewName=file.name;card.innerHTML='<div class="card-body">'+(image?'<img alt="">':'<div class="text-center py-2"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>')+'<small title=""></small></div>';if(image)card.querySelector('img').src=url;card.querySelector('small').textContent=file.name;card.querySelector('small').title=file.name;box.appendChild(card);});});});
$(document).on('click keydown','.purchase-evidence-preview .card,.receipt-evidence-preview .card,.invoice-evidence-preview .card',function(e){if(e.type==='keydown'&&e.key!=='Enter'&&e.key!==' ')return;e.preventDefault();openPurchaseAttachment(this.dataset.previewUrl,this.dataset.previewType,this.dataset.previewName);});
$(document).on('click','.purchase-payment-detail',async function(){var id=this.dataset.id,body=$('#purchaseDocumentBody');$('#purchaseDocumentModal .modal-title').text('Detail Pembayaran Pembelian');$('#purchaseAttachmentForm').addClass('d-none');body.html('<div class="text-center p-4">Memuat...</div>');bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseDocumentModal')).show();try{var res=await fetch('<?=base_url('purchasedocuments/detail/supplier_payment/')?>'+id),json=await res.json();if(!json.status)throw Error(json.message);var h=json.header||{},sources=json.lines||[],items=json.items||[],attachments=json.attachments||[],esc=v=>$('<div>').text(v??'-').html(),money=n=>'Rp '+Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});var info='<div class="row g-3"><div class="col-md-6"><div class="border rounded p-3 h-100"><small class="text-muted">Supplier</small><h6>'+esc((h.supplier_code||'')+' - '+(h.supplier_name||''))+'</h6><div>'+esc(h.supplier_address||'-')+'</div><div class="mt-2"><small class="text-muted">Purchase Order</small><div>'+esc(h.po_no||'-')+'</div></div></div></div><div class="col-md-6"><div class="border rounded p-3 h-100"><div><small class="text-muted">Nomor Pembayaran</small><b class="float-end">'+esc(h.payment_no)+'</b></div><div><small class="text-muted">Tanggal</small><span class="float-end">'+esc(h.payment_date)+'</span></div><div><small class="text-muted">Referensi</small><span class="float-end">'+esc(h.reference_no||'-')+'</span></div><div><small class="text-muted">Status</small><span class="float-end badge bg-success">'+esc(h.status)+'</span></div><div><small class="text-muted">Finance</small><span class="float-end">'+esc(h.created_name||'-')+'</span></div></div></div></div>';var itemTable='<h6 class="mt-3">Barang yang Dibayar</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Kode - Barang</th><th>Spesifikasi</th><th class="text-end">Qty</th><th class="text-end">Harga</th><th class="text-end">Total</th></tr></thead><tbody>'+items.map(i=>'<tr><td><b>'+esc((i.kode_barang||'-')+' - '+(i.nama_barang||'-'))+'</b><small class="d-block text-muted">'+esc(i.tracking_type||'-')+'</small></td><td>'+esc(i.description||'-')+'</td><td class="text-end">'+Number(i.qty||0).toLocaleString('id-ID')+'</td><td class="text-end">'+money(i.unit_price)+'</td><td class="text-end fw-semibold">'+money(i.line_total)+'</td></tr>').join('')+'</tbody></table></div>';var poSummary='<div class="row justify-content-end mb-3"><div class="col-md-7 col-lg-6"><div class="border rounded p-3 bg-light"><h6>Rincian Nilai Purchase Order</h6><div class="d-flex justify-content-between"><span>Subtotal Barang</span><span>'+money(h.po_subtotal)+'</span></div><div class="d-flex justify-content-between"><span>Pajak</span><span>'+money(h.po_tax_total)+'</span></div><div class="d-flex justify-content-between"><span>Ongkos Kirim</span><span>'+money(h.shipping_cost)+'</span></div><div class="d-flex justify-content-between"><span>Asuransi</span><span>'+money(h.insurance_cost)+'</span></div><div class="d-flex justify-content-between"><span>Biaya Layanan</span><span>'+money(h.service_fee)+'</span></div><div class="d-flex justify-content-between text-success"><span>Diskon/Voucher</span><span>- '+money(h.discount_amount)+'</span></div><div class="d-flex justify-content-between text-success"><span>Cashback</span><span>- '+money(h.cashback_amount)+'</span></div><div class="d-flex justify-content-between fw-bold border-top mt-2 pt-2"><span>Total PO</span><span>'+money(h.po_total)+'</span></div><div class="d-flex justify-content-between text-primary fw-semibold"><span>Pembayaran Ini</span><span>'+money(h.amount)+'</span></div><div class="d-flex justify-content-between"><span>Sisa Setelah Pembayaran</span><span>'+money(Math.max(0,Number(h.po_total||0)-Number(h.po_paid_amount||0)))+'</span></div></div></div></div>';var table='<h6 class="mt-3">Sumber Pembayaran</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Akun Kas/Bank</th><th class="text-end">Nominal</th></tr></thead><tbody>'+sources.map(s=>'<tr><td>'+esc((s.kode_akun||'-')+' - '+(s.nama_akun||'-'))+'</td><td class="text-end">'+money(s.amount)+'</td></tr>').join('')+'</tbody><tfoot><tr class="fw-bold"><td>Total Pembayaran</td><td class="text-end">'+money(h.amount)+'</td></tr></tfoot></table></div>';var notes=h.notes?'<div class="alert alert-light border"><small class="text-muted">Catatan</small><div>'+esc(h.notes)+'</div></div>':'';var files='<h6>Bukti Pembayaran</h6><div class="d-flex flex-wrap gap-2">'+attachments.map(a=>{var image=String(a.mime_type||'').indexOf('image/')===0;return '<a href="'+a.preview_url+'" class="card text-decoration-none" style="width:92px"><div class="card-body p-1">'+(image?'<img src="'+a.preview_url+'" style="width:100%;height:62px;object-fit:cover;border-radius:5px" alt="">':'<div class="text-center py-2"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>')+'<small class="d-block text-truncate">'+esc(a.original_name)+'</small></div></a>';}).join('')+'</div>';body.html(info+itemTable+poSummary+table+notes+files);}catch(e){body.html('<div class="alert alert-danger">'+esc(e.message)+'</div>');}});
$(document).on('click','.purchase-receipt-detail',async function(){var id=this.dataset.id,body=$('#purchaseDocumentBody'),esc=v=>$('<div>').text(v??'-').html(),qty=n=>Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:0,maximumFractionDigits:2});$('#purchaseDocumentModal .modal-title').text('Detail Penerimaan Barang');$('#purchaseAttachmentForm').addClass('d-none');body.html('<div class="text-center p-4">Memuat...</div>');bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseDocumentModal')).show();try{var response=await fetch('<?=base_url('purchasedocuments/detail/goods_receipt/')?>'+id),json=await response.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],attachments=json.attachments||[];var info='<div class="row g-3 mb-3"><div class="col-md-6"><div class="border rounded p-3 h-100"><small class="text-muted">Supplier</small><h6>'+esc((h.supplier_code||'')+' - '+(h.supplier_name||''))+'</h6><div>'+esc(h.supplier_address||'-')+'</div><div class="mt-2"><small class="text-muted">Purchase Order</small><div>'+esc(h.po_no||'-')+'</div></div></div></div><div class="col-md-6"><div class="border rounded p-3 h-100"><div><small class="text-muted">Nomor Penerimaan</small><b class="float-end">'+esc(h.receipt_no)+'</b></div><div><small class="text-muted">Tanggal Diterima</small><span class="float-end">'+esc(h.receipt_date)+'</span></div><div><small class="text-muted">Surat Jalan / Resi</small><span class="float-end">'+esc(h.supplier_delivery_no||'-')+'</span></div><div><small class="text-muted">Petugas Gudang</small><span class="float-end">'+esc(h.received_name||'-')+'</span></div><div><small class="text-muted">Status</small><span class="float-end badge bg-success">'+esc(h.status)+'</span></div></div></div></div>';var table='<h6>Barang yang Diterima</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Kode - Barang</th><th>Spesifikasi</th><th>Gudang</th><th>Tracking</th><th class="text-end">Qty Diterima</th></tr></thead><tbody>'+lines.map(l=>'<tr><td>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||'-'))+'</td><td>'+esc(l.description||'-')+'</td><td>'+esc(l.warehouse_name||'-')+'</td><td>'+esc(l.tracking_type||'-')+'</td><td class="text-end fw-semibold">'+qty(l.qty)+'</td></tr>').join('')+'</tbody></table></div>';var files='<h6 class="mt-3">Bukti Penerimaan</h6><div class="d-flex flex-wrap gap-2">'+(attachments.length?attachments.map(a=>{var image=String(a.mime_type||'').indexOf('image/')===0;return '<a href="'+a.preview_url+'" class="card text-decoration-none" style="width:92px"><div class="card-body p-1">'+(image?'<img src="'+a.preview_url+'" style="width:100%;height:62px;object-fit:cover;border-radius:5px" alt="">':'<div class="text-center py-2"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>')+'<small class="d-block text-truncate">'+esc(a.original_name)+'</small></div></a>';}).join(''):'<span class="text-muted">Belum ada bukti penerimaan.</span>')+'</div>';body.html(info+table+files);}catch(error){body.html('<div class="alert alert-danger">'+esc(error.message||'Gagal memuat detail penerimaan.')+'</div>');}});
$('#invoiceReceiptId').on('change',async function(){var option=this.options[this.selectedIndex],box=$('#invoiceSourceSummary'),id=this.value;$('#invoicePoId').val(option?option.dataset.po:'');if(!id){box.html('<div class="text-center text-muted py-5"><i class="bi bi-receipt fs-2 d-block mb-2"></i>Pilih penerimaan untuk melihat PO, supplier, dan barang.</div>');return;}box.html('<div class="text-center py-4">Memuat dokumen sumber...</div>');try{var response=await fetch('<?=base_url('purchasedocuments/detail/goods_receipt/')?>'+id),json=await response.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],esc=v=>$('<div>').text(v??'-').html(),qty=n=>Number(n||0).toLocaleString('id-ID',{maximumFractionDigits:2});box.html('<dl class="row small mb-3"><dt class="col-5">Supplier</dt><dd class="col-7">'+esc(h.supplier_name)+'</dd><dt class="col-5">Purchase Order</dt><dd class="col-7">'+esc(h.po_no)+'</dd><dt class="col-5">Penerimaan</dt><dd class="col-7">'+esc(h.receipt_no)+'</dd><dt class="col-5">Surat Jalan</dt><dd class="col-7">'+esc(h.supplier_delivery_no||'-')+'</dd></dl><h6 class="small">Barang yang dapat dicocokkan</h6><div class="list-group list-group-flush">'+lines.map(l=>'<div class="list-group-item px-0 d-flex justify-content-between gap-2"><span>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||l.description))+'</span><b>'+qty(l.qty)+'</b></div>').join('')+'</div><div class="alert alert-info small mt-3 mb-0">Harga dan pajak mengikuti PO. Sistem hanya menagihkan kuantitas penerimaan yang belum pernah masuk invoice.</div>');}catch(error){box.html('<div class="alert alert-danger">'+$('<div>').text(error.message||'Gagal memuat penerimaan.').html()+'</div>');}});
$(document).off('click','.purchase-invoice-detail').on('click','.purchase-invoice-detail',async function(){var id=this.dataset.id,body=$('#purchaseDocumentBody'),esc=v=>$('<div>').text(v??'-').html(),money=n=>'Rp '+Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});$('#purchaseDocumentModal .modal-title').text('Detail Invoice Supplier');$('#purchaseAttachmentForm').addClass('d-none');body.html('<div class="text-center p-4">Memuat...</div>');bootstrap.Modal.getOrCreateInstance(document.getElementById('purchaseDocumentModal')).show();try{var response=await fetch('<?=base_url('purchasedocuments/detail/supplier_invoice/')?>'+id),json=await response.json();if(!json.status)throw Error(json.message);var h=json.header||{},lines=json.lines||[],attachments=json.attachments||[],invoiceTotal=Number(h.total??h.po_total??0),poPaid=Math.max(Number(h.po_paid_amount||0),Number(h.paid_amount||0)),invoiceBalance=Math.max(0,invoiceTotal-poPaid);var info='<div class="row g-3 mb-3"><div class="col-md-6"><div class="border rounded p-3 h-100"><small class="text-muted">Supplier</small><h6>'+esc((h.supplier_code||'')+' - '+(h.supplier_name||''))+'</h6><div>'+esc(h.supplier_address||'-')+'</div><div class="mt-2"><small class="text-muted">Dokumen Sumber</small><div>PO '+esc(h.po_no||'-')+' · Penerimaan '+esc(h.receipt_no||'-')+'</div></div></div></div><div class="col-md-6"><div class="border rounded p-3 h-100"><div><small class="text-muted">Nomor Internal</small><b class="float-end">'+esc(h.internal_no)+'</b></div><div><small class="text-muted">Invoice Supplier</small><span class="float-end">'+esc(h.supplier_invoice_no)+'</span></div><div><small class="text-muted">Tanggal / Jatuh Tempo</small><span class="float-end">'+esc(h.invoice_date)+' / '+esc(h.due_date)+'</span></div><div><small class="text-muted">Status</small><span class="float-end badge bg-warning text-dark">'+esc(h.status_label||h.status)+'</span></div><div><small class="text-muted">Rencana Pembayaran</small><span class="float-end">'+esc(h.payment_plan)+'</span></div></div></div></div>';var table='<h6>Barang yang Ditagihkan</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Kode - Barang</th><th>Spesifikasi</th><th class="text-end">Qty</th><th class="text-end">Harga</th><th class="text-end">Pajak</th><th class="text-end">Total</th><th>Matching</th></tr></thead><tbody>'+lines.map(l=>'<tr><td>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||'-'))+'</td><td>'+esc(l.description||'-')+'</td><td class="text-end">'+Number(l.qty||0).toLocaleString('id-ID')+'</td><td class="text-end">'+money(l.unit_price)+'</td><td class="text-end">'+money(l.tax_amount)+'</td><td class="text-end fw-semibold">'+money(l.line_total)+'</td><td><span class="badge bg-success">'+esc(l.match_status||'matched')+'</span></td></tr>').join('')+'</tbody></table></div>';var totals='<div class="row justify-content-end"><div class="col-md-6"><table class="table table-sm"><tr><td>Subtotal Barang</td><td class="text-end">'+money(h.po_subtotal??h.subtotal)+'</td></tr><tr><td>Pajak</td><td class="text-end">'+money(h.po_tax_total??h.tax_total)+'</td></tr><tr><td>Ongkir, Asuransi & Layanan</td><td class="text-end">'+money(Number(h.shipping_cost||0)+Number(h.insurance_cost||0)+Number(h.service_fee||0))+'</td></tr><tr><td>Diskon & Cashback</td><td class="text-end">- '+money(Number(h.discount_amount||0)+Number(h.cashback_amount||0))+'</td></tr><tr class="fw-bold"><td>Total Invoice</td><td class="text-end">'+money(invoiceTotal)+'</td></tr><tr class="table-primary"><td>Total Sudah Dibayar</td><td class="text-end">'+money(poPaid)+'</td></tr><tr class="fw-bold"><td>Sisa Tagihan</td><td class="text-end">'+money(invoiceBalance)+'</td></tr></table></div></div>';var files='<h6>Dokumen Invoice Supplier</h6><div class="d-flex flex-wrap gap-2">'+(attachments.length?attachments.map(a=>{var image=String(a.mime_type||'').indexOf('image/')===0;return '<a href="'+a.preview_url+'" class="card text-decoration-none" style="width:92px"><div class="card-body p-1">'+(image?'<img src="'+a.preview_url+'" style="width:100%;height:62px;object-fit:cover;border-radius:5px" alt="">':'<div class="text-center py-2"><i class="bi bi-file-earmark-pdf fs-2 text-danger"></i></div>')+'<small class="d-block text-truncate">'+esc(a.original_name)+'</small></div></a>';}).join(''):'<span class="text-muted">Belum ada dokumen invoice.</span>')+'</div>';body.html(info+table+totals+files);}catch(error){body.html('<div class="alert alert-danger">'+esc(error.message||'Gagal memuat invoice supplier.')+'</div>');}});
$('#invoiceReceiptId').off('change').on('change',async function(){
var option=this.options[this.selectedIndex],box=$('#invoiceSourceSummary'),id=this.value;$('#invoicePoId').val(option?option.dataset.po:'');
if(!id){box.html('<div class="text-center text-muted py-5">Pilih salah satu penerimaan untuk memeriksa akumulasi seluruh penerimaan pada PO.</div>');return;}
box.html('<div class="text-center py-4"><span class="spinner-border spinner-border-sm me-2"></span>Mengakumulasi PO dan seluruh penerimaan...</div>');
try{
var response=await fetch('<?=base_url('purchasedocuments/detail/goods_receipt/')?>'+id),json=await response.json();if(!json.status)throw Error(json.message);
var h=json.header||{},lines=json.lines||[],esc=v=>$('<div>').text(v??'-').html(),qty=n=>Number(n||0).toLocaleString('id-ID',{maximumFractionDigits:2}),money=n=>'Rp '+Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});
var netQty=l=>Number(l.accumulated_received_qty||0)-Number(l.accumulated_returned_qty||0),shortage=lines.some(l=>netQty(l)<Number(l.ordered_qty||0)-.001),excess=lines.some(l=>netQty(l)>Number(l.ordered_qty||0)+.001),fullyPaid=Number(h.po_paid_amount||0)>=Number(h.po_total||0)-.001,returnDeduction=lines.reduce((sum,l)=>sum+(Number(l.accumulated_returned_qty||0)*Number(l.unit_price||0)*(1+Number(l.tax_rate||0)/100)),0),estimatedInvoiceTotal=Math.max(0,Number(h.po_total||0)-returnDeduction);
var procurementLabels={online:'Pembelian Online / Marketplace',direct:'Pembelian Langsung di Toko',supplier:'Pemesanan Langsung ke Supplier'};
var paymentLabels={prepaid:'Bayar Sebelum Barang Diterima',on_receipt:'Bayar Setelah Barang Diterima',installment:'Cicilan / Pembayaran Bertahap',credit:'Kredit / Jatuh Tempo'};
function addDaysDisplay(value,days){var parts=String(value||'').split('-');if(parts.length!==3)return'-';var date=new Date(Date.UTC(Number(parts[0]),Number(parts[1])-1,Number(parts[2])));date.setUTCDate(date.getUTCDate()+Number(days||0));return date.getUTCFullYear()+'/'+String(date.getUTCMonth()+1).padStart(2,'0')+'/'+String(date.getUTCDate()).padStart(2,'0');}
var paymentScheduleDetail='';
if(h.payment_timing==='installment')paymentScheduleDetail='<div class="col-5 text-muted">Cicilan / Due Date</div><div class="col-7 fw-semibold">'+esc(Number(h.installment_count||0)+' Kali / Tanggal '+(h.first_due_date?Number(String(h.first_due_date).slice(-2)):'-'))+'</div>';
else if(h.payment_timing==='credit'){var invoiceDate=$('#supplierInvoiceForm [name="date"]').val();paymentScheduleDetail='<div class="col-5 text-muted">Tanggal Jatuh Tempo</div><div class="col-7 fw-semibold">'+esc(addDaysDisplay(invoiceDate,h.payment_term_days||0))+'</div>';}
var next='';
if(excess)next='Ada penerimaan melebihi PO. Jangan lanjutkan invoice; periksa Penerimaan dan lakukan Retur Pembelian atas kelebihan barang.';
else if(shortage&&fullyPaid)next='Barang belum lengkap tetapi PO sudah dibayar. Tunggu penerimaan berikutnya. Jika supplier memastikan kekurangan tidak akan dikirim, proses pengembalian dana melalui Retur/Refund.';
else if(shortage)next='Barang belum lengkap. Tunggu penerimaan berikutnya. Jika sisa dibatalkan supplier, minta invoice sesuai barang aktual dan sesuaikan PO; jangan membayar bagian yang belum diterima.';
else if(h.payment_timing==='prepaid')next='Barang sudah lengkap dan pembayaran sudah tercatat. Simpan invoice lalu lakukan rekonsiliasi pembayaran.';
else if(h.payment_timing==='installment')next='Barang sudah lengkap. Simpan dan posting invoice sebagai hutang; DP (jika ada) direkonsiliasi, cicilan berikutnya dibayar melalui Hutang & Pembayaran.';
else if(h.payment_timing==='credit')next='Barang sudah lengkap. Simpan dan posting invoice sebagai Hutang Usaha, lalu bayar sesuai jatuh tempo di Hutang & Pembayaran.';
else next='Barang sudah lengkap. Simpan invoice dan lakukan pembayaran setelah penerimaan dari Invoice Supplier.';
var rows=lines.map(function(l){var ordered=Number(l.ordered_qty||0),received=Number(l.accumulated_received_qty||0),returned=Number(l.accumulated_returned_qty||0),net=received-returned,state=net>ordered+.001?'Berlebih':(net<ordered-.001?'Belum Lengkap':'Lengkap'),color=state==='Lengkap'?'success':(state==='Berlebih'?'danger':'warning');return '<tr><td><b>'+esc((l.kode_barang||'-')+' - '+(l.nama_barang||'-'))+'</b><small class="d-block text-muted">'+esc(l.description||'-')+' · '+esc(l.tracking_type||'-')+'</small></td><td class="text-end">'+qty(ordered)+'</td><td class="text-end">'+qty(l.selected_receipt_qty)+'</td><td class="text-end fw-semibold">'+qty(received)+'</td><td class="text-end text-danger">'+qty(returned)+'</td><td class="text-end fw-semibold">'+qty(net)+'</td><td class="text-end">'+qty(l.invoice_available_qty)+'</td><td><span class="badge bg-'+color+'">'+state+'</span></td></tr>'}).join('');
box.html('<div class="border rounded p-3 mb-3"><b>'+esc((h.supplier_code||'')+' - '+(h.supplier_name||''))+'</b><div class="small text-muted">PO '+esc(h.po_no)+' · '+qty(h.receipt_count)+' dokumen penerimaan</div><div class="row g-1 small mt-2"><div class="col-5 text-muted">Cara Pemesanan</div><div class="col-7 fw-semibold">'+esc(procurementLabels[h.procurement_method]||'Metode pemesanan belum ditentukan')+'</div><div class="col-5 text-muted">Rencana Pembayaran</div><div class="col-7 fw-semibold">'+esc(paymentLabels[h.payment_timing]||'Rencana pembayaran belum ditentukan')+'</div><div class="col-5 text-muted">Marketplace / Nomor Pesanan</div><div class="col-7">'+esc((h.marketplace||'-')+' / '+(h.external_order_no||'-'))+'</div><div class="col-5 text-muted">Kurir / Nomor Resi</div><div class="col-7">'+esc((h.courier||'-')+' / '+(h.tracking_no||'-'))+'</div></div><div class="small mt-2"><b>Riwayat penerimaan:</b> '+esc(h.receipt_numbers||h.receipt_no)+'</div><hr class="my-2"><div class="d-flex justify-content-between"><span>Subtotal + pajak</span><span>'+money(Number(h.po_subtotal||0)+Number(h.po_tax_total||0))+'</span></div><div class="d-flex justify-content-between"><span>Biaya tambahan bersih</span><span>'+money(Number(h.shipping_cost||0)+Number(h.insurance_cost||0)+Number(h.service_fee||0)-Number(h.discount_amount||0)-Number(h.cashback_amount||0))+'</span></div><div class="d-flex justify-content-between"><span>Total PO awal</span><b>'+money(h.po_total)+'</b></div><div class="d-flex justify-content-between text-danger"><span>Pengurang retur sebelum invoice</span><b>- '+money(returnDeduction)+'</b></div><div class="d-flex justify-content-between fw-bold"><span>Estimasi invoice neto</span><b>'+money(estimatedInvoiceTotal)+'</b></div><div class="d-flex justify-content-between"><span>Sudah dibayar</span><b>'+money(h.po_paid_amount)+'</b></div></div><h6 class="small">Detail Pesanan dan Akumulasi Penerimaan</h6><div class="table-responsive"><table class="table table-sm align-middle"><thead><tr><th>Barang / Spesifikasi</th><th class="text-end">Dipesan</th><th class="text-end">Dokumen Dipilih</th><th class="text-end">Akumulasi Diterima</th><th class="text-end">Diretur</th><th class="text-end">Neto Sah</th><th class="text-end">Belum Ditagihkan</th><th>Kondisi</th></tr></thead><tbody>'+rows+'</tbody></table></div><div class="alert alert-'+(excess?'danger':(shortage?'warning':'success'))+' small mb-2">'+(excess?'Matching gagal: jumlah neto setelah retur masih melebihi pesanan.':(shortage?'Matching parsial: jumlah neto setelah retur belum sama dengan pesanan.':'Matching kuantitas lengkap: jumlah neto setelah retur sesuai dengan PO.'))+'</div><div class="alert alert-info small mb-0"><b>Langkah berikutnya:</b> '+esc(next)+'<br><span class="text-muted">Invoice akan mengakumulasi seluruh penerimaan posted pada PO ini yang belum pernah ditagihkan, bukan hanya dokumen yang dipilih.</span></div>');
if(paymentScheduleDetail){var paymentPlanLabel=box.find('.text-muted').filter(function(){return $(this).text().trim()==='Rencana Pembayaran';}).first();if(paymentPlanLabel.length)$(paymentScheduleDetail).insertAfter(paymentPlanLabel.next());}
}catch(error){box.html('<div class="alert alert-danger">'+$('<div>').text(error.message||'Gagal memuat matching.').html()+'</div>');}
});
$('#supplierInvoiceForm [name="date"]').off('change.invoiceDueDate').on('change.invoiceDueDate',function(){if($('#invoiceReceiptId').val())$('#invoiceReceiptId').trigger('change');});
function attachmentContainer(input){return input.id==='poDocuments'?document.getElementById('poDocumentPreview'):$(input).siblings('.purchase-evidence-preview,.receipt-evidence-preview,.invoice-evidence-preview')[0];}
function decorateAttachmentPicker(input){var box=attachmentContainer(input);if(!box)return;box.classList.add('attachment-gallery');Array.from(box.querySelectorAll('.card')).forEach(function(card,index){card.classList.add('position-relative');card.dataset.fileIndex=index;if(!card.querySelector('.attachment-remove')){var remove=document.createElement('button');remove.type='button';remove.className='attachment-remove';remove.innerHTML='&times;';remove.setAttribute('aria-label','Hapus lampiran');remove.title='Hapus lampiran';remove.addEventListener('click',function(e){e.preventDefault();e.stopImmediatePropagation();removeAttachment(input,Number(card.dataset.fileIndex));});card.appendChild(remove);}});var add=document.createElement('button');add.type='button';add.className='attachment-add-tile';add.innerHTML='<i class="bi bi-plus-lg fs-3"></i><small>Tambah</small>';add.setAttribute('aria-label','Pilih lampiran');add.addEventListener('click',function(){input.click();});box.appendChild(add);}
function removeAttachment(input,index){if(typeof DataTransfer==='undefined'){AppAlert.toast('Browser tidak mendukung penghapusan file satu per satu. Pilih ulang lampiran.','error');return;}var transfer=new DataTransfer();Array.from(input.files||[]).forEach(function(file,i){if(i!==index)transfer.items.add(file);});input.files=transfer.files;input.dispatchEvent(new Event('change',{bubbles:true}));}
$('#poDocuments,.purchase-evidence-input,.receipt-evidence-input,.invoice-evidence-input').each(function(){var input=this;input.classList.add('attachment-picker-hidden');input.removeAttribute('tabindex');decorateAttachmentPicker(input);input.addEventListener('change',function(){decorateAttachmentPicker(input);});});
function invoicePaymentNumber(value){var cleaned=window.cleanNumber?window.cleanNumber(value):String(value||'').replace(/\./g,'').replace(',','.');var number=Number(cleaned);return Number.isFinite(number)?number:0;}function invoicePaymentMoney(value){return 'Rp '+Number(value||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});}function invoicePaymentTotal(){var form=$('#supplierInvoicePostForm'),principal=Number(form.data('principal')||0),charge=Math.max(0,invoicePaymentNumber(form.find('.invoice-bank-charge').val())),required=principal+charge,total=0;form.find('.invoice-source-amount').each(function(){total+=invoicePaymentNumber(this.value)});var valid=Math.abs(total-required)<.005&&total>0&&(!charge||form.find('.invoice-bank-charge-account').val());$('#invoicePrincipalRequired').text(invoicePaymentMoney(principal));$('#invoiceBankChargeDisplay').text(invoicePaymentMoney(charge));$('#invoiceCashOutRequired').text(invoicePaymentMoney(required));$('#invoiceSourceTotal').text(invoicePaymentMoney(total));$('#invoicePaymentStatus').attr('class','small mt-2 '+(valid?'text-success':'text-danger')).html(valid?'<i class="bi bi-check-circle me-1"></i>Balance dan siap diposting.':'<i class="bi bi-exclamation-circle me-1"></i>Total sumber harus sama dengan pokok invoice + admin bank.');form.data('payment-valid',valid);if(!$('#invoicePaymentFields').hasClass('d-none'))$('#invoicePostSubmit').prop('disabled',!valid);return total;}$('#invoiceAddSource').on('click',function(){var row=$('.invoice-source-row').first().clone();row.find('input').val('');$('#invoicePaymentSources').append(row);invoicePaymentTotal();});$(document).on('click','.invoice-remove-source',function(){var rows=$('.invoice-source-row');if(rows.length>1)$(this).closest('.invoice-source-row').remove();else $(this).closest('.invoice-source-row').find('input').val('');invoicePaymentTotal();});$(document).on('input change','.invoice-source-amount,.invoice-bank-charge,.invoice-bank-charge-account',invoicePaymentTotal);$('#supplierInvoicePostForm').on('submit',function(event){if(!$('#invoicePaymentFields').hasClass('d-none')){invoicePaymentTotal();if(!$(this).data('payment-valid')){event.preventDefault();AppAlert.toast('Total sumber Kas/Bank harus balance dengan pokok invoice + admin bank.','error');}}});
$(document).on('click','.bill-post-action',function(){var id=this.dataset.id,plan=this.dataset.plan||'credit',prepaid=this.dataset.prepaid==='1',credit=plan==='credit'&&!prepaid,installment=plan==='installment'&&!prepaid,hidePayment=credit||prepaid,total=Number(this.dataset.total||this.dataset.balance||0),form=$('#supplierInvoicePostForm');form.attr('action','<?=base_url('purchases/post_bill/')?>'+id);$('#invoicePaymentFields').toggleClass('d-none',hidePayment).find('select,input,button').prop('disabled',hidePayment);$('#invoicePaymentFields [name="source_account_id[]"],#invoicePaymentFields [name="source_amount[]"],#invoicePaymentFields [name="payment_date"],#invoicePaymentFields [name="invoice_payment_documents[]"]').prop('required',!hidePayment);if(!hidePayment){while($('.invoice-source-row').length>1)$('.invoice-source-row').last().remove();$('.invoice-source-amount').val(installment?'':(window.formatNumber?window.formatNumber(total):total));$('[name="invoice_payment_documents[]"]',form)[0].value='';$('[name="invoice_payment_documents[]"]',form).trigger('change');invoicePaymentTotal();}if(prepaid){$('#supplierInvoicePostModal .modal-title').text('Rekonsiliasi Invoice Supplier');$('#invoicePostExplanation').html('Pembayaran sudah tercatat sebelumnya. Proses ini hanya mengalokasikan pembayaran ke invoice supplier. <b>Tidak ada uang keluar atau bukti pembayaran baru.</b>');$('#invoicePostSubmit').text('Rekonsiliasi & Selesaikan');}else{$('#supplierInvoicePostModal .modal-title').text(credit?'Posting Hutang Supplier':'Pembayaran Invoice Supplier');$('#invoicePostExplanation').html(credit?'Invoice akan diposting sebagai <b>Hutang Usaha</b>. Pelunasan dilakukan melalui menu <b>Hutang & Pembayaran</b>.':(installment?'Masukkan nominal cicilan pertama dari satu atau beberapa akun dan unggah bukti pembayaran. Sisa invoice menjadi Hutang Usaha.':'Pembayaran setelah barang diterima dilakukan di sini. Total semua akun harus sama dengan total invoice dan bukti pembayaran wajib dilampirkan.'));$('#invoicePostSubmit').text(credit?'Posting Hutang':'Post Pembayaran');}bootstrap.Modal.getOrCreateInstance(document.getElementById('supplierInvoicePostModal')).show();});
$('#poDocuments,.purchase-evidence-input,.receipt-evidence-input,.invoice-evidence-input,#purchaseAttachmentForm [name="attachment"],#supplierInvoicePostForm [name="invoice_payment_documents[]"]').attr('accept','image/*,.pdf,application/pdf');
$(document).on('click','.purchase-receipt-detail',function(){var id=this.dataset.id;setTimeout(function(){var form=$('#purchaseAttachmentForm');form.removeClass('d-none');form.find('[name="entity_type"]').val('goods_receipt');form.find('[name="entity_id"]').val(id);form.find('label').first().text('Tambahkan bukti penerimaan yang belum tersedia');},0);});
$('.bill-post-action[data-plan="installment"]').text('Posting Hutang Cicilan');
$(document).off('click','.bill-post-action').on('click','.bill-post-action',function(){
var id=this.dataset.id,plan=this.dataset.plan||'credit',prepaid=this.dataset.prepaid==='1',credit=plan==='credit'&&!prepaid,installment=plan==='installment'&&!prepaid,hidePayment=credit||installment||prepaid,total=Number(this.dataset.total||this.dataset.balance||0),form=$('#supplierInvoicePostForm');
form.attr('action','<?=base_url('purchases/post_bill/')?>'+id).data('principal',total);$('#invoicePaymentFields').toggleClass('d-none',hidePayment).find('select,input,button').prop('disabled',hidePayment);$('#invoicePaymentFields [name="source_account_id[]"],#invoicePaymentFields [name="source_amount[]"],#invoicePaymentFields [name="payment_date"],#invoicePaymentFields [name="invoice_payment_documents[]"]').prop('required',!hidePayment);$('#invoicePostSubmit').prop('disabled',false);
if(!hidePayment){while($('.invoice-source-row').length>1)$('.invoice-source-row').last().remove();$('.invoice-bank-charge').val('0');$('.invoice-source-amount').val(window.formatNumber?window.formatNumber(total):total);$('[name="invoice_payment_documents[]"]',form)[0].value='';$('[name="invoice_payment_documents[]"]',form).trigger('change');invoicePaymentTotal();}
if(prepaid){$('#supplierInvoicePostModal .modal-title').text('Rekonsiliasi Invoice Supplier');$('#invoicePostExplanation').html('Pembayaran sudah tercatat sebelumnya. Proses ini hanya mengalokasikan pembayaran ke invoice supplier. <b>Tidak ada uang keluar atau bukti pembayaran baru.</b>');$('#invoicePostSubmit').text('Rekonsiliasi & Selesaikan');}
else if(installment){$('#supplierInvoicePostModal .modal-title').text('Posting Hutang Cicilan');$('#invoicePostExplanation').html('Invoice diposting sebagai <b>Hutang Cicilan</b>. DP yang sudah dibayar akan direkonsiliasi. Cicilan berikutnya mengikuti jadwal dan dibayar melalui <b>Hutang & Pembayaran</b>.');$('#invoicePostSubmit').text('Posting Hutang Cicilan');}
else{$('#supplierInvoicePostModal .modal-title').text(credit?'Posting Hutang Supplier':'Pembayaran Invoice Supplier');$('#invoicePostExplanation').html(credit?'Invoice akan diposting sebagai <b>Hutang Usaha</b>. Pelunasan dilakukan melalui menu <b>Hutang & Pembayaran</b>.':'Pembayaran setelah barang diterima dilakukan di sini. Total semua akun harus sama dengan total invoice dan bukti pembayaran wajib dilampirkan.');$('#invoicePostSubmit').text(credit?'Posting Hutang':'Post Pembayaran');}
bootstrap.Modal.getOrCreateInstance(document.getElementById('supplierInvoicePostModal')).show();
});
$('#purchaseAttachmentPreviewModal').on('hidden.bs.modal',function(){$('#purchaseAttachmentPreviewBody').empty();});
});
</script>