Files
accounting_dev_v2/application/views/payables/index.php
T
2026-09-11 16:03:00 +07:00

49 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div class="container-fluid py-3">
<div class="mb-3"><h4 class="mb-1">Hutang & Pembayaran Supplier</h4><p class="text-muted mb-0">Pilih supplier dan invoice, tentukan nominal pembayaran di panel kanan, lalu gunakan satu atau beberapa akun Kas/Bank.</p></div>
<div class="row g-3">
<div class="col-lg-5"><div class="card h-100"><div class="card-header bg-body"><b>Daftar Tagihan Belum Lunas</b><small class="d-block text-muted">Daftar ini hanya untuk memilih tagihan; nominal pembayaran diisi di panel kanan.</small></div><div class="card-body">
<label class="form-label">Supplier</label><select id="supplierSelect" class="form-select select-search" data-placeholder="Pilih supplier"><option value=""></option><?php foreach($suppliers as$s):?><option value="<?=$s->id?>"><?=html_escape($s->supplier_code.' - '.$s->name)?></option><?php endforeach?></select>
<div id="billScopeWrap" class="mt-3 d-none"><label class="form-label">PO / Invoice Supplier</label><select id="billScope" class="form-select"><option value="all">Semua tagihan terbuka</option></select><small class="text-muted">Pilih semua untuk mengalokasikan pembayaran dari tagihan terlama, atau pilih satu invoice tertentu.</small></div>
<div id="billList" class="mt-3"><div class="text-center text-muted py-5"><i class="bi bi-receipt fs-2 d-block"></i>Pilih supplier untuk melihat tagihan.</div></div>
</div></div></div>
<div class="col-lg-7"><div class="card h-100"><div class="card-header bg-body"><b>Detail & Pembayaran</b><small class="d-block text-muted">Kredit dan cicilan boleh dibayar sebagian, maksimal sebesar sisa tagihan.</small></div><div class="card-body">
<div id="orderDetail" class="alert alert-light border text-muted">Pilih tagihan di sebelah kiri untuk melihat detail pesanan.</div>
<form id="supplierPayment" method="post" enctype="multipart/form-data" data-ajax="true" action="<?=base_url('payables/payment')?>"><input type="hidden" name="<?=$this->security->get_csrf_token_name()?>" value="<?=$this->security->get_csrf_hash()?>"><input type="hidden" name="supplier_id" id="paySupplier"><div id="allocationFields"></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="date" value="<?=date('Y-m-d')?>" required></div><div class="col-md-6"><label class="form-label">Referensi Bank</label><input class="form-control" name="reference_no" placeholder="Nomor transfer/cek"></div>
<div class="col-12"><label class="form-label">Sumber Kas/Bank dan Total Uang Keluar</label><div id="paymentSources"><div class="row g-2 source-row mb-2"><div class="col-7"><select class="form-select" name="source_account_id[]" required><?php foreach($accounts 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 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" title="Hapus sumber"><i class="bi bi-x-lg"></i></button></div></div></div><button type="button" class="btn btn-sm btn-outline-primary" id="addSource"><i class="bi bi-plus-lg"></i> Tambah Akun</button><small class="d-block text-muted mt-1">Isi nilai yang benar-benar keluar dari rekening. Sistem memisahkan admin bank dari pokok pembayaran.</small></div>
<div class="col-md-5"><label class="form-label">Admin Bank <span class="text-muted">(opsional)</span></label><input id="bankChargeAmount" class="form-control format-rupiah" name="bank_charge_amount" value="0" inputmode="decimal"><small class="text-muted">Ditambahkan ke total uang keluar.</small></div><div class="col-md-7"><label class="form-label">Akun Biaya Admin</label><select id="bankChargeAccount" class="form-select" name="bank_charge_account_id"><?php foreach($bank_charge_accounts as$a):?><option value="<?=$a->id?>" <?=$a->id===$bank_charge_account_id?'selected':''?>><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div>
<div class="col-12"><div class="border rounded p-3 bg-light"><div class="d-flex justify-content-between"><span>Total tagihan dipilih</span><b id="selectedBalance">Rp 0,00</b></div><div class="d-flex justify-content-between"><span>Pokok pembayaran</span><b id="principalTotal">Rp 0,00</b></div><div class="d-flex justify-content-between"><span>Admin bank</span><b id="bankChargeDisplay">Rp 0,00</b></div><div class="d-flex justify-content-between fw-semibold"><span>Total sumber Kas/Bank</span><b id="sourceTotal">Rp 0,00</b></div><div class="d-flex justify-content-between"><span>Dialokasikan ke invoice</span><b id="allocatedTotal">Rp 0,00</b></div><div class="d-flex justify-content-between border-top mt-2 pt-2"><span>Sisa tagihan setelah pembayaran</span><b id="paymentDifference">Rp 0,00</b></div><div class="small text-muted mt-2" id="paymentGuidance">Nominal pembayaran boleh lebih kecil dari saldo untuk kredit atau cicilan.</div></div></div>
<div class="col-12"><div class="border rounded p-3 bg-light"><label class="form-label mb-1">Bukti Pembayaran <span class="text-danger">*</span></label><p class="small text-muted mb-2">Unggah bukti transfer, foto struk, atau dokumen pembayaran. Bisa memilih beberapa file sekaligus.</p><input id="paymentEvidence" class="attachment-picker-hidden" type="file" name="payment_documents[]" accept="image/*,.pdf,application/pdf" multiple><small class="text-muted">Wajib. Maksimal 10 file, masing-masing 5 MB.</small><div id="paymentEvidenceGallery" class="attachment-gallery"></div><div id="paymentEvidenceStatus" class="small text-danger mt-2"><i class="bi bi-exclamation-circle me-1"></i>Belum ada bukti pembayaran.</div></div></div>
<div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="allow_advance" value="1" id="allowAdvance"><label class="form-check-label" for="allowAdvance">Catat kelebihan pembayaran sebagai uang muka supplier</label></div></div><div class="col-12 text-end"><button class="btn btn-success" id="postPayment" disabled><i class="bi bi-check2-circle me-1"></i>Post Pembayaran</button></div>
</div>
</form><hr><h6>Riwayat Pembayaran Supplier</h6><div id="paymentHistory" class="text-muted">Belum ada supplier yang dipilih.</div>
</div></div></div>
</div>
<div class="card mt-3"><div class="card-header bg-body"><b>Ringkasan Aging Hutang</b></div><div class="table-responsive"><table class="table table-sm align-middle mb-0"><thead><tr><th>Supplier</th><th class="text-end">Belum Jatuh Tempo</th><th class="text-end">1–30 Hari</th><th class="text-end">31–60 Hari</th><th class="text-end">61–90 Hari</th><th class="text-end">&gt;90 Hari</th><th class="text-end">Total</th><th></th></tr></thead><tbody><?php foreach($aging as$r):?><tr><td><?=html_escape($r->name)?></td><?php foreach(array('current_amount','days_1_30','days_31_60','days_61_90','over_90','total')as$f):?><td class="text-end"><?=number_format($r->$f,2,',','.')?></td><?php endforeach?><td><a class="btn btn-sm btn-outline-secondary" href="<?=base_url('payables/statement/'.$r->supplier_id)?>">Statement</a></td></tr><?php endforeach?></tbody></table></div></div>
</div>
<div class="modal fade attachment-preview-modal" id="paymentEvidencePreview" 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="paymentEvidencePreviewTitle">Preview Bukti Pembayaran</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Tutup"></button></div><div class="modal-body p-2 text-center" id="paymentEvidencePreviewBody"></div></div></div></div>
<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-gallery .card{position:relative;width:92px;flex:0 0 92px;overflow:visible!important;cursor:pointer}.attachment-gallery img{width:100%;height:62px;object-fit:cover;border-radius:5px}.attachment-gallery .card-body{padding:.35rem}.attachment-gallery small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attachment-remove{position:absolute;right:-7px;top:-7px;width:25px;height:25px;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-preview-modal{z-index:2070}.attachment-preview-modal+.modal-backdrop{z-index:2065}.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:160px}.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>
<script>
document.addEventListener('DOMContentLoaded',function(){
const sel=document.getElementById('supplierSelect'),form=document.getElementById('supplierPayment'),list=document.getElementById('billList'),detail=document.getElementById('orderDetail'),history=document.getElementById('paymentHistory'),billScope=document.getElementById('billScope'),billScopeWrap=document.getElementById('billScopeWrap'),allocationFields=document.getElementById('allocationFields'),selectedBalance=document.getElementById('selectedBalance'),principalTotal=document.getElementById('principalTotal'),bankChargeAmount=document.getElementById('bankChargeAmount'),bankChargeAccount=document.getElementById('bankChargeAccount'),bankChargeDisplay=document.getElementById('bankChargeDisplay'),allocatedTotal=document.getElementById('allocatedTotal'),sourceTotal=document.getElementById('sourceTotal'),paymentDifference=document.getElementById('paymentDifference'),paymentGuidance=document.getElementById('paymentGuidance'),postPayment=document.getElementById('postPayment'),allowAdvance=document.getElementById('allowAdvance'),addSource=document.getElementById('addSource'),paymentSources=document.getElementById('paymentSources'),paySupplier=document.getElementById('paySupplier'),evidenceInput=document.getElementById('paymentEvidence'),evidenceGallery=document.getElementById('paymentEvidenceGallery'),evidenceStatus=document.getElementById('paymentEvidenceStatus'),previewTitle=document.getElementById('paymentEvidencePreviewTitle'),previewBody=document.getElementById('paymentEvidencePreviewBody'),globalLoading=document.getElementById('globalLoading');
const money=n=>'Rp '+Number(n||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2}),esc=v=>$('<div>').text(v??'-').html();let bills=[],evidenceFiles=[],previewUrls=[];
function num(v){return window.parseMoney?window.parseMoney(v):(Number(String(v||0).replace(/\./g,'').replace(',','.'))||0)}
function evidenceExtension(file){return String(file.name||'').split('.').pop().toLowerCase()}
function isPdf(file){return file.type==='application/pdf'||evidenceExtension(file)==='pdf'}
function validEvidence(file){let extension=evidenceExtension(file),allowed=['pdf','jpg','jpeg','jpe','jfif','png','webp','gif','bmp','tif','tiff','heic','heif','avif','jp2','j2k','jpf','jpg2','jpx','ico'];if(file.size>5*1024*1024){AppAlert.toast('Ukuran '+file.name+' melebihi 5 MB.','error');return false}if(!allowed.includes(extension)){AppAlert.toast('File '+file.name+' bukan gambar atau PDF.','error');return false}return true}
function syncEvidenceInput(){if(typeof DataTransfer==='undefined'){AppAlert.toast('Browser tidak mendukung daftar lampiran. Gunakan Chrome, Edge, Firefox, atau Safari terbaru.','error');return false}let transfer=new DataTransfer();evidenceFiles.forEach(file=>transfer.items.add(file));evidenceInput.files=transfer.files;return true}
function renderEvidence(){previewUrls.forEach(url=>URL.revokeObjectURL(url));previewUrls=[];evidenceGallery.innerHTML='';evidenceFiles.forEach(function(file,index){let url=URL.createObjectURL(file);previewUrls.push(url);let card=document.createElement('div');card.className='card';card.title='Klik untuk preview '+file.name;let body=document.createElement('div');body.className='card-body';if(isPdf(file)){let icon=document.createElement('div');icon.className='text-center py-2';icon.innerHTML='<i class="bi bi-file-earmark-pdf fs-2 text-danger"></i>';body.appendChild(icon)}else{let img=document.createElement('img');img.src=url;img.alt='Preview '+file.name;body.appendChild(img)}let name=document.createElement('small');name.textContent=file.name;body.appendChild(name);card.appendChild(body);let remove=document.createElement('button');remove.type='button';remove.className='attachment-remove';remove.innerHTML='&times;';remove.title='Hapus '+file.name;remove.setAttribute('aria-label','Hapus '+file.name);remove.addEventListener('click',function(event){event.preventDefault();event.stopPropagation();evidenceFiles.splice(index,1);syncEvidenceInput();renderEvidence();calculate()});card.addEventListener('click',function(){previewTitle.textContent=file.name;previewBody.innerHTML='';if(isPdf(file)){let frame=document.createElement('iframe');frame.src=url;frame.title=file.name;previewBody.appendChild(frame)}else{let image=document.createElement('img');image.src=url;image.alt=file.name;previewBody.appendChild(image)}bootstrap.Modal.getOrCreateInstance(document.getElementById('paymentEvidencePreview')).show()});card.appendChild(remove);evidenceGallery.appendChild(card)});let 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.title='Tambah bukti pembayaran';add.addEventListener('click',()=>evidenceInput.click());evidenceGallery.appendChild(add);evidenceStatus.className='small mt-2 '+(evidenceFiles.length?'text-success':'text-danger');evidenceStatus.innerHTML=evidenceFiles.length?'<i class="bi bi-check-circle me-1"></i>'+evidenceFiles.length+' bukti pembayaran siap diunggah.':'<i class="bi bi-exclamation-circle me-1"></i>Belum ada bukti pembayaran.'}
evidenceInput.addEventListener('change',function(){let additions=Array.from(this.files||[]),remaining=Math.max(0,10-evidenceFiles.length);if(additions.length>remaining)AppAlert.toast('Maksimal 10 bukti pembayaran. Hanya '+remaining+' file tambahan yang dapat dimasukkan.','error');additions.slice(0,remaining).forEach(function(file){let duplicate=evidenceFiles.some(existing=>existing.name===file.name&&existing.size===file.size&&existing.lastModified===file.lastModified);if(!duplicate&&validEvidence(file))evidenceFiles.push(file)});syncEvidenceInput();renderEvidence();calculate()});
document.getElementById('paymentEvidencePreview').addEventListener('hidden.bs.modal',function(){previewBody.innerHTML=''});renderEvidence();
function selectedBills(){let scope=billScope.value||'all';return bills.filter(b=>scope==='all'||String(b.id)===scope)}
function planLabel(value){return value==='installment'?'Cicilan':(value==='credit'?'Kredit/Jatuh Tempo':'Pembayaran')}
function calculate(){let selected=selectedBills(),available=selected.reduce((n,b)=>n+Number(b.balance||0),0),source=0,charge=Math.max(0,num(bankChargeAmount.value));document.querySelectorAll('.source-amount').forEach(x=>source+=num(x.value));let principal=Math.max(0,source-charge),remaining=Math.min(principal,available),allocated=0;document.querySelectorAll('.allocation').forEach(x=>x.value=0);selected.forEach(function(b){let value=Math.min(remaining,Number(b.balance||0)),hidden=document.querySelector('.allocation[data-id="'+b.id+'"]');if(hidden)hidden.value=value;allocated+=value;remaining-=value;});let excess=Math.max(0,principal-available),balance=Math.max(0,available-allocated),validCharge=!charge||!!bankChargeAccount.value;selectedBalance.textContent=money(available);principalTotal.textContent=money(principal);bankChargeDisplay.textContent=money(charge);sourceTotal.textContent=money(source);allocatedTotal.textContent=money(allocated);paymentDifference.textContent=money(balance);paymentDifference.className=balance<=.001?'text-success':'text-warning';let installment=selected.length===1&&selected[0].payment_plan==='installment',credit=selected.some(b=>b.payment_plan==='credit');if(source<=charge&&source>0)paymentGuidance.innerHTML='<span class="text-danger">Total sumber Kas/Bank harus lebih besar daripada admin bank.</span>';else if(!validCharge)paymentGuidance.innerHTML='<span class="text-danger">Pilih akun biaya admin bank.</span>';else if(excess>.001&&!allowAdvance.checked)paymentGuidance.innerHTML='<span class="text-danger">Pokok pembayaran melebihi sisa tagihan sebesar <b>'+money(excess)+'</b>. Kurangi nominal atau aktifkan uang muka.</span>';else if(excess>.001)paymentGuidance.innerHTML='<span class="text-primary">Kelebihan pokok <b>'+money(excess)+'</b> akan dicatat sebagai uang muka supplier. Admin '+money(charge)+' dicatat sebagai beban.</span>';else if(installment)paymentGuidance.textContent='Pokok cicilan '+money(principal)+' mengurangi jadwal terlama; admin '+money(charge)+' otomatis menjadi beban.';else if(credit)paymentGuidance.textContent='Pokok pembayaran '+money(principal)+' mengurangi hutang; admin '+money(charge)+' otomatis menjadi beban.';else paymentGuidance.textContent='Pokok pembayaran dialokasikan ke invoice terlama. Admin bank tidak mengurangi saldo invoice.';postPayment.disabled=!(selected.length&&principal>0&&source>charge&&validCharge&&evidenceFiles.length&&(excess<=.001||allowAdvance.checked))}
function showBill(id){let b=bills.find(x=>Number(x.id)===Number(id));if(!b)return;let overdue=b.due_state==='overdue',extra=Number(b.shipping_cost||0)+Number(b.insurance_cost||0)+Number(b.service_fee||0),deduction=Number(b.discount_amount||0)+Number(b.cashback_amount||0);detail.innerHTML='<div class="d-flex justify-content-between gap-2"><div><small class="text-muted">Invoice Supplier</small><h5>'+esc(b.internal_no)+' / '+esc(b.supplier_invoice_no)+'</h5></div><span class="badge bg-'+(overdue?'danger':'warning')+' align-self-start">'+(overdue?'Sudah jatuh tempo':'Belum jatuh tempo')+' · '+esc(b.due_date)+'</span></div><div class="row g-2 small mb-3"><div class="col-6">PO: <b>'+esc(b.po_no)+'</b></div><div class="col-6">Penerimaan: <b>'+esc(b.receipt_no)+'</b></div><div class="col-6">Rencana: <b>'+planLabel(b.payment_plan)+'</b></div><div class="col-6">Total Invoice/PO: <b>'+money(b.total)+'</b></div><div class="col-6">Sudah Dibayar: <b>'+money(b.paid_amount)+'</b></div><div class="col-6">Sisa Hutang: <b>'+money(b.balance)+'</b></div></div><div class="table-responsive"><table class="table table-sm"><thead><tr><th>Barang</th><th class="text-end">Qty</th><th class="text-end">Harga</th><th class="text-end">Total</th></tr></thead><tbody>'+b.items.map(i=>'<tr><td>'+esc((i.kode_barang||'-')+' - '+(i.nama_barang||'-'))+'</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">'+money(i.line_total)+'</td></tr>').join('')+'</tbody></table></div><div class="row justify-content-end"><div class="col-md-7 small"><div class="d-flex justify-content-between"><span>Subtotal barang + pajak</span><span>'+money(Number(b.po_subtotal||0)+Number(b.po_tax_total||0))+'</span></div><div class="d-flex justify-content-between"><span>Ongkir, asuransi & layanan</span><span>'+money(extra)+'</span></div><div class="d-flex justify-content-between"><span>Diskon & cashback</span><span>- '+money(deduction)+'</span></div><div class="d-flex justify-content-between fw-bold border-top mt-1 pt-1"><span>Total Invoice/PO</span><span>'+money(b.total)+'</span></div></div></div>';if((b.installment_schedules||[]).length)detail.insertAdjacentHTML('beforeend','<h6 class="mt-3">Jadwal Cicilan</h6><div class="table-responsive"><table class="table table-sm"><thead><tr><th>Ke</th><th>Jatuh Tempo</th><th class="text-end">Nominal</th><th class="text-end">Terbayar</th><th>Status</th></tr></thead><tbody>'+b.installment_schedules.map(s=>'<tr><td>'+Number(s.installment_no)+'</td><td>'+esc(s.due_date)+'</td><td class="text-end">'+money(s.amount)+'</td><td class="text-end">'+money(s.paid_amount)+'</td><td><span class="badge bg-'+(s.status==='paid'?'success':(s.status==='partial'?'warning':'secondary'))+'">'+esc(s.status)+'</span></td></tr>').join('')+'</tbody></table></div>');document.querySelectorAll('.bill-card').forEach(x=>x.classList.toggle('border-primary',Number(x.dataset.id)===Number(id)))}
function applyBillScope(){let selected=selectedBills();if(selected.length===1)showBill(selected[0].id);else if(selected.length>1)detail.innerHTML='<div class="alert alert-primary mb-3"><b>'+selected.length+' tagihan dipilih.</b><div>Total saldo yang dapat dibayar: '+money(selected.reduce((n,b)=>n+Number(b.balance||0),0))+'</div><div class="small mt-1">Pembayaran akan dialokasikan dari invoice dengan jatuh tempo terlama.</div></div>';calculate()}
async function load(){let id=sel.value;paySupplier.value=id;bills=[];billScopeWrap.classList.add('d-none');billScope.innerHTML='<option value="all">Semua tagihan terbuka</option>';allocationFields.innerHTML='';postPayment.disabled=true;if(!id){list.innerHTML='<div class="text-center text-muted py-5">Pilih supplier.</div>';detail.innerHTML='<div class="text-muted">Pilih supplier dan tagihan yang akan dibayar.</div>';calculate();return}list.innerHTML='<div class="text-center py-4"><span class="spinner-border spinner-border-sm me-2"></span>Memuat tagihan...</div>';try{let r=await fetch('<?=base_url('payables/open_bills/')?>'+id,{headers:{Accept:'application/json','X-Requested-With':'XMLHttpRequest'}}),j=await r.json();if(!r.ok||!j.status)throw Error(j.message||'Gagal memuat tagihan');bills=j.bills||[];let pending=j.pending||[];allocationFields.innerHTML=bills.map(b=>'<input type="hidden" class="allocation" data-id="'+Number(b.id)+'" name="allocations['+Number(b.id)+']" value="0">').join('');billScope.innerHTML='<option value="all">Semua tagihan terbuka ('+bills.length+')</option>'+bills.map(b=>'<option value="'+Number(b.id)+'">'+esc((b.po_no||'-')+' / '+b.internal_no+' — '+money(b.balance))+'</option>').join('');billScopeWrap.classList.toggle('d-none',!bills.length);list.innerHTML=bills.map(b=>'<button type="button" class="bill-card text-start border rounded p-3 w-100 mb-2 bg-body" data-id="'+Number(b.id)+'"><div class="d-flex justify-content-between gap-2"><div><b>'+esc(b.po_no||'-')+'</b><small class="d-block">'+esc(b.internal_no)+' / '+esc(b.supplier_invoice_no)+'</small></div><span class="badge bg-'+(b.due_state==='overdue'?'danger':'warning')+' text-'+(b.due_state==='overdue'?'white':'dark')+'">'+planLabel(b.payment_plan)+'</span></div><div class="row g-1 small mt-2"><div class="col-6 text-muted">Total Invoice/PO</div><div class="col-6 text-end fw-semibold">'+money(b.total)+'</div><div class="col-6 text-muted">Sudah Dibayar</div><div class="col-6 text-end">'+money(b.paid_amount)+'</div><div class="col-6 text-muted">Sisa Tagihan</div><div class="col-6 text-end fw-bold text-primary">'+money(b.balance)+'</div></div><small class="text-muted">'+(b.due_state==='overdue'?'Sudah jatuh tempo':'Dapat dibayar sebelum jatuh tempo')+' · '+esc(b.due_date)+'</small></button>').join('');if(!bills.length){list.innerHTML=pending.length?'<div class="alert alert-warning"><b>'+pending.length+' invoice belum menjadi hutang.</b><div class="small mt-1">Selesaikan finalisasi di Purchase Workflow → Invoice Supplier.</div></div>':'<div class="alert alert-success">Tidak ada hutang terbuka untuk supplier ini.</div>';detail.innerHTML='<div class="text-muted">Tidak ada invoice berstatus Parsial yang dapat dibayar.</div>'}history.innerHTML=(j.history||[]).length?'<div class="table-responsive"><table class="table table-sm"><thead><tr><th>Tanggal/No.</th><th>Tagihan</th><th>Sumber</th><th class="text-end">Alokasi</th></tr></thead><tbody>'+j.history.map(h=>'<tr><td>'+esc(h.payment_date)+'<small class="d-block">'+esc(h.payment_no)+'</small></td><td>'+esc(h.internal_no||'Uang muka')+'</td><td>'+((h.sources||[]).map(s=>esc(s.kode_akun)+' '+money(s.amount)).join('<br>')||'-')+'</td><td class="text-end">'+money(h.allocated_amount||h.amount)+'</td></tr>').join('')+'</tbody></table></div>':'<div class="text-muted">Belum ada riwayat pembayaran.</div>';if(bills.length){billScope.value='all';applyBillScope()}else calculate()}catch(e){list.innerHTML='<div class="alert alert-danger"><b>Tagihan gagal dimuat.</b><div>'+esc(e.message)+'</div></div>';detail.innerHTML='<div class="alert alert-danger">Periksa koneksi dan data invoice supplier.</div>';calculate()}}
$(sel).on('change',load);billScope.addEventListener('change',applyBillScope);document.addEventListener('click',function(e){let card=e.target.closest('.bill-card');if(card){billScope.value=String(card.dataset.id);applyBillScope()}if(e.target.closest('.remove-source')){let rows=document.querySelectorAll('.source-row');if(rows.length>1)e.target.closest('.source-row').remove();else e.target.closest('.source-row').querySelector('.source-amount').value='';calculate()}});document.addEventListener('input',e=>{if(e.target.classList.contains('source-amount')||e.target===bankChargeAmount)calculate()});bankChargeAccount.addEventListener('change',calculate);allowAdvance.addEventListener('change',calculate);addSource.onclick=function(){let row=document.querySelector('.source-row').cloneNode(true);row.querySelector('.source-amount').value='';paymentSources.appendChild(row);calculate()};form.onsubmit=async function(e){e.preventDefault();calculate();if(postPayment.disabled){AppAlert.toast('Periksa tagihan, total sumber, admin bank, dan bukti pembayaran.','error');return}if(!evidenceFiles.length){AppAlert.toast('Bukti pembayaran wajib diunggah.','error');return}let gross=0;document.querySelectorAll('.source-amount').forEach(x=>gross+=num(x.value));let charge=Math.max(0,num(bankChargeAmount.value)),principal=Math.max(0,gross-charge),result=await Swal.fire({icon:'question',title:'Post pembayaran supplier?',html:'Pokok pembayaran <b>'+money(principal)+'</b><br>Admin bank <b>'+money(charge)+'</b><br>Total keluar Kas/Bank <b>'+money(gross)+'</b><br><small>Jurnal dan bukti pembayaran akan disimpan.</small>',showCancelButton:true,confirmButtonText:'Ya, post',cancelButtonText:'Batal'});if(!result.isConfirmed)return;postPayment.disabled=true;if(globalLoading)globalLoading.hidden=false;try{form.querySelectorAll('.format-rupiah').forEach(x=>x.value=num(x.value));let response=await fetch(form.action,{method:'POST',body:new FormData(form),headers:{Accept:'application/json','X-Requested-With':'XMLHttpRequest'}}),raw=await response.text(),json;try{json=JSON.parse(raw)}catch(parseError){throw Error('Respons server tidak valid. Periksa log aplikasi.')}if(!response.ok||!json.status)throw Error(json.message||'Pembayaran gagal');if(globalLoading)globalLoading.hidden=true;await Swal.fire({toast:true,position:'top-end',icon:'success',title:json.message+' '+(json.result?.payment_no||''),showConfirmButton:false,timer:2200});location.reload()}catch(error){if(globalLoading)globalLoading.hidden=true;form.querySelectorAll('.format-rupiah').forEach(x=>{if(window.formatNumber)x.value=window.formatNumber(x.value)});Swal.fire({icon:'error',title:'Pembayaran gagal',text:error.message});calculate()}};calculate();
});
</script>