Update besar apk finance

This commit is contained in:
Wian Drs
2026-09-11 16:03:00 +07:00
parent d42483b350
commit 4ef15c22ae
897 changed files with 98056 additions and 15979 deletions
+48
View File
@@ -0,0 +1,48 @@
<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>
+1
View File
@@ -0,0 +1 @@
<div class="container py-3"><h4>Supplier Statement</h4><p><?=html_escape($supplier->name)?></p><div class="table-responsive"><table class="table"><thead><tr><th>Tanggal</th><th>Dokumen</th><th>Debit/Pembayaran</th><th>Kredit/Tagihan</th><th>Saldo</th></tr></thead><tbody><?php foreach($bills as$b):?><tr><td><?=$b->invoice_date?></td><td><?=html_escape($b->supplier_invoice_no)?></td><td></td><td><?=number_format($b->total,0,',','.')?></td><td><?=number_format($b->balance,0,',','.')?></td></tr><?php endforeach?><?php foreach($payments as$p):?><tr><td><?=$p->payment_date?></td><td><?=$p->payment_no?></td><td><?=number_format($p->amount,0,',','.')?></td><td></td><td></td></tr><?php endforeach?></tbody></table></div></div>