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
@@ -0,0 +1,67 @@
<?php $inventory_title='Operasional Stok';$inventory_subtitle='Buat, setujui, posting, dan telusuri setiap mutasi gudang.';$this->load->view('inventory_professional/_module_nav',get_defined_vars());?>
<nav class="inventory-operation-tabs mb-3" aria-label="Alur operasional stok">
<button data-inventory-tab="manual"><i class="bi bi-box-arrow-in-down"></i><span>Masuk/Keluar Manual</span></button>
<button data-inventory-tab="transfer"><i class="bi bi-arrow-left-right"></i><span>Transfer Gudang</span></button>
<button data-inventory-tab="reservation"><i class="bi bi-bookmark-check"></i><span>Reservasi</span></button>
<button data-inventory-tab="adjustment"><i class="bi bi-sliders"></i><span>Penyesuaian</span></button>
<button data-inventory-tab="opname"><i class="bi bi-clipboard-check"></i><span>Stock Opname</span></button>
<button data-inventory-tab="damage"><i class="bi bi-exclamation-diamond"></i><span>Rusak/Hilang</span></button>
<button class="active" data-inventory-tab=""><i class="bi bi-clock-history"></i><span>Riwayat Dokumen</span></button>
</nav>
<section class="inventory-card" id="documentSection">
<div class="inventory-card-head"><div><h5>Dokumen Operasional</h5><p>Penerimaan dan retur pembelian tetap dibuat dari Purchase Workflow.</p></div><?php if($inventory_permissions['create']):?><button class="btn btn-primary" id="newDocument"><i class="bi bi-plus-lg"></i> Buat Dokumen</button><?php endif?></div>
<div class="p-3 border-bottom inventory-toolbar">
<div class="filter-field"><label class="form-label">Jenis Mutasi</label><select id="filterType" class="form-select form-select-sm"><option value="">Semua</option><option value="manual">Masuk/Keluar Manual</option><option value="opening">Saldo Awal</option><option value="in">Barang Masuk Manual</option><option value="out">Barang Keluar Manual</option><option value="transfer">Transfer Gudang</option><option value="adjustment">Penyesuaian</option><option value="opname">Stock Opname</option><option value="damage">Rusak/Hilang</option><option value="damaged">Barang Rusak</option><option value="lost">Barang Hilang</option></select></div>
<div class="filter-field"><label class="form-label">Status</label><select id="filterStatus" class="form-select form-select-sm"><option value="">Semua</option><option value="draft">Draft</option><option value="submitted">Diajukan</option><option value="approved">Disetujui</option><option value="posted">Diposting</option><option value="rejected">Ditolak</option><option value="reversed">Dibalik</option></select></div>
<button class="btn btn-sm btn-outline-secondary" id="refreshDocuments"><i class="bi bi-arrow-clockwise"></i> Muat Ulang</button>
</div>
<div class="table-responsive"><table id="documentTable" class="table inventory-table align-middle w-100 mb-0"><thead><tr><th>Dokumen</th><th>Tanggal</th><th>Jenis</th><th>Gudang</th><th>Baris</th><th>Status</th><th>Pembuat</th><th>Aksi</th></tr></thead></table></div>
</section>
<section class="inventory-card mt-3 d-none" id="reservationSection">
<div class="inventory-card-head"><div><h5>Reservasi / Alokasi Stok</h5><p>Stok dialokasikan ke dokumen sumber dan dapat dilepas atau digunakan bertahap.</p></div><?php if($inventory_permissions['create']):?><button class="btn btn-outline-primary" id="newReservation"><i class="bi bi-bookmark-plus"></i> Buat Reservasi</button><?php endif?></div>
<div class="table-responsive"><table class="table inventory-table align-middle mb-0" id="reservationTable"><thead><tr><th>Reservasi</th><th>Barang</th><th>Gudang</th><th>Sumber</th><th>Qty</th><th>Berlaku Sampai</th><th>Status</th><th>Aksi</th></tr></thead><tbody></tbody></table></div>
</section>
<div class="modal fade inventory-modal" id="documentModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-xl"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">DOKUMEN PERSEDIAAN</small><h5 class="modal-title">Buat Dokumen Mutasi</h5></div><button class="btn-close" data-bs-dismiss="modal"></button></div><form id="documentForm"><div class="modal-body">
<input type="hidden" id="idempotencyKey"><div class="row g-3"><div class="col-md-3"><label class="form-label">Jenis Mutasi</label><select id="documentType" class="form-select" required><option value="">-- Pilih --</option><option value="opening">Saldo Awal</option><option value="in">Barang Masuk Manual</option><option value="out">Barang Keluar Manual</option><option value="transfer">Transfer Antar-gudang</option><option value="adjustment">Penyesuaian Stok</option><option value="opname">Stock Opname</option><option value="damaged">Barang Rusak</option><option value="lost">Barang Hilang</option></select></div><div class="col-md-3"><label class="form-label">Tanggal</label><input id="documentDate" type="date" class="form-control" value="<?=date('Y-m-d')?>" required></div><div class="col-md-3 warehouse-from"><label class="form-label">Gudang Asal / Lokasi Opname</label><select id="fromWarehouse" class="form-select"><option value="">-- Pilih --</option><?php foreach($warehouses as$w):?><option value="<?=$w->id?>"><?=html_escape($w->nama)?></option><?php endforeach?></select></div><div class="col-md-3 warehouse-to"><label class="form-label">Gudang Tujuan</label><select id="toWarehouse" class="form-select"><option value="">-- Pilih --</option><?php foreach($warehouses as$w):?><option value="<?=$w->id?>"><?=html_escape($w->nama)?></option><?php endforeach?></select></div></div>
<div class="row g-3 mt-0"><div class="col-md-3"><label class="form-label">Jenis Dokumen Sumber</label><input id="sourceType" class="form-control" placeholder="Contoh: memo_gudang"></div><div class="col-md-3"><label class="form-label">Nomor/ID Sumber</label><input id="sourceId" type="number" min="1" class="form-control" placeholder="Opsional"></div><div class="col-md-6"><label class="form-label">Alasan / Referensi</label><input id="documentReason" class="form-control" maxlength="500" required placeholder="Jelaskan tujuan dan dasar mutasi"></div></div>
<div class="d-flex justify-content-between align-items-center mt-4 mb-2"><div><h6 class="mb-0">Detail Barang</h6><small class="text-muted">Satu dokumen dapat memuat beberapa barang.</small></div><button type="button" class="btn btn-sm btn-outline-primary" id="addLine"><i class="bi bi-plus"></i> Tambah Barang</button></div>
<div class="table-responsive"><table class="table inventory-table inventory-line-table align-middle"><thead><tr><th>Barang</th><th>Barcode/SN</th><th>Batch</th><th class="direction-column">Arah</th><th>Qty / Hitung Fisik</th><th>Biaya Satuan</th><th>Catatan</th><th></th></tr></thead><tbody id="documentLines"></tbody></table></div>
<div><label class="form-label">Lampiran Pendukung <span class="text-muted">(gambar/PDF, opsional)</span></label><input id="documentAttachments" type="file" class="d-none" accept="image/*,application/pdf" multiple><div id="attachmentNames" class="inventory-attachments"><button type="button" class="attachment-add" id="chooseAttachments"><i class="bi bi-plus-lg"></i><span>Tambah</span></button></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" id="saveDocument"><i class="bi bi-save"></i> Simpan Draft</button></div></form></div></div></div>
<div class="modal fade inventory-modal" id="detailModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-xl"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">AUDIT DOKUMEN</small><h5 class="modal-title" id="detailTitle">Detail Dokumen</h5></div><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body" id="detailBody"></div><div class="modal-footer" id="detailActions"><button class="btn btn-light" data-bs-dismiss="modal">Tutup</button></div></div></div></div>
<div class="modal fade inventory-modal" id="pdfModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-xl"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Dokumen Persediaan</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body p-0" style="height:min(82vh,900px)"><iframe title="Dokumen persediaan" style="width:100%;height:100%;border:0"></iframe></div></div></div></div>
<div class="modal fade inventory-modal" id="reservationModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-lg"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">ALOKASI STOK</small><h5 class="modal-title">Buat Reservasi</h5></div><button class="btn-close" data-bs-dismiss="modal"></button></div><form id="reservationForm"><div class="modal-body"><div class="row g-3"><div class="col-md-6"><label class="form-label">Barang</label><select id="reservationItem" class="form-select"></select></div><div class="col-md-6"><label class="form-label">Gudang</label><select id="reservationWarehouse" class="form-select"><option value="">-- Pilih --</option><?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">Barcode/SN <span class="text-muted">(opsional)</span></label><select id="reservationBarcode" class="form-select"></select></div><div class="col-md-3"><label class="form-label">Qty</label><input id="reservationQty" inputmode="decimal" class="form-control" required></div><div class="col-md-3"><label class="form-label">Berlaku Sampai</label><input id="reservationExpiry" type="datetime-local" class="form-control"></div><div class="col-md-4"><label class="form-label">Jenis Sumber</label><input id="reservationSourceType" class="form-control" value="sales_order" required></div><div class="col-md-4"><label class="form-label">ID Dokumen Sumber</label><input id="reservationSourceId" type="number" min="1" class="form-control" required></div><div class="col-md-4"><label class="form-label">Catatan</label><input id="reservationNotes" class="form-control"></div></div><div id="reservationAvailability" class="inventory-balance-box mt-3"><div><span>Stok akan diperiksa saat disimpan.</span></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 Reservasi</button></div></form></div></div></div>
<script>
$(function(){
const permissions=<?=json_encode($inventory_permissions)?>,csrf=()=>({[APP_CSRF.name]:APP_CSRF.value}),esc=v=>$('<div>').text(v==null?'-':v).html(),fmt=v=>Number(v||0).toLocaleString('id-ID',{maximumFractionDigits:4}),money=v=>'Rp '+Number(v||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});
const labels={opening:'Saldo Awal',in:'Masuk Manual',out:'Keluar Manual',transfer:'Transfer Gudang',adjustment:'Penyesuaian',opname:'Stock Opname',damaged:'Barang Rusak',lost:'Barang Hilang',draft:'Draft',submitted:'Diajukan',approved:'Disetujui',posted:'Diposting',rejected:'Ditolak',reversed:'Dibalik'};
function api(url,data){return $.ajax({url:url,type:'POST',data:Object.assign({},data||{},csrf()),dataType:'json'})}
function fail(xhr){AppAlert.toast(xhr.responseJSON&&xhr.responseJSON.message||'Proses tidak berhasil.','error')}
const table=$('#documentTable').DataTable({processing:true,serverSide:true,responsive:true,searchDelay:350,pageLength:10,order:[],ajax:{url:'<?=base_url('inventoryprofessional/document_data')?>',data:d=>{d.type=$('#filterType').val();d.status=$('#filterStatus').val()}},columns:[{data:'document_no',render:(v,t,r)=>'<button class="btn btn-link p-0 fw-semibold btn-detail" data-id="'+r.id+'">'+esc(v)+'</button>'},{data:'document_date',render:v=>v?v.split('-').reverse().join('/'):'-'},{data:'document_type',render:v=>labels[v]||v},{data:null,render:r=>esc((r.from_warehouse||'-')+' → '+(r.to_warehouse||'-'))},{data:'line_count',className:'text-center'},{data:'status',render:v=>'<span class="status-chip status-'+v+'">'+esc(labels[v]||v)+'</span>'},{data:'creator_name',defaultContent:'-'},{data:null,orderable:false,searchable:false,render:r=>actions(r)}],language:{search:'Cari:',lengthMenu:'Tampilkan _MENU_',info:'_START_–_END_ dari _TOTAL_',processing:'Memuat data...',paginate:{previous:'Sebelumnya',next:'Berikutnya'}}});
const focusDocument=new URLSearchParams(window.location.search).get('focus');if(focusDocument)table.one('draw.dt',function(){setTimeout(function(){$('<button type="button" class="d-none btn-detail" data-id="'+String(focusDocument).replace(/[^0-9]/g,'')+'"></button>').appendTo(document.body).trigger('click').remove()},100)});
function actions(r){let a='<div class="d-flex flex-wrap gap-1"><button class="btn btn-sm btn-outline-primary btn-detail" data-id="'+r.id+'"><i class="bi bi-eye"></i></button>';if(permissions.export)a+='<button class="btn btn-sm btn-outline-danger btn-pdf" data-id="'+r.id+'"><i class="bi bi-file-earmark-pdf"></i></button>';if(r.status==='draft'&&permissions.create)a+='<button class="btn btn-sm btn-primary btn-state" data-action="submit" data-id="'+r.id+'">Ajukan</button>';if(r.status==='submitted'&&permissions.approve)a+='<button class="btn btn-sm btn-success btn-state" data-action="approve" data-id="'+r.id+'">Setujui</button><button class="btn btn-sm btn-outline-danger btn-reject" data-id="'+r.id+'">Tolak</button>';if(r.status==='approved'&&permissions.post)a+='<button class="btn btn-sm btn-success btn-state" data-action="post" data-id="'+r.id+'">Posting</button>';if(r.status==='posted'&&permissions.post)a+='<button class="btn btn-sm btn-outline-danger btn-reverse" data-id="'+r.id+'">Reversal</button>';return a+'</div>'}
$('#filterType,#filterStatus').on('change',()=>table.ajax.reload());$('#refreshDocuments').on('click',()=>table.ajax.reload(null,false));
$('[data-inventory-tab]').on('click',function(){let tab=$(this).data('inventory-tab');$('[data-inventory-tab]').removeClass('active');$(this).addClass('active');if(tab==='reservation'){$('#documentSection').addClass('d-none');$('#reservationSection').removeClass('d-none');loadReservations();return}$('#reservationSection').addClass('d-none');$('#documentSection').removeClass('d-none');$('#filterType').val(tab);table.ajax.reload()});
function addLine(){let id=Date.now()+Math.random().toString(16).slice(2),tr=$('<tr class="document-line"><td><select class="form-select line-item"></select></td><td><select class="form-select line-barcode"><option value="">Tanpa barcode</option></select></td><td><select class="form-select line-batch"><option value="">Tanpa batch</option></select></td><td class="direction-column"><select class="form-select line-direction"><option value="in">Tambah</option><option value="out">Kurangi</option></select></td><td><input class="form-control line-qty" inputmode="decimal" placeholder="0" required></td><td><input class="form-control line-cost format-rupiah" inputmode="decimal" placeholder="0"></td><td><input class="form-control line-reason" placeholder="Catatan baris"></td><td><button type="button" class="btn btn-sm btn-outline-danger remove-line"><i class="bi bi-x"></i></button></td></tr>');tr.attr('data-key',id);$('#documentLines').append(tr);initLine(tr);toggleType()}
function initLine(tr){tr.find('.line-item').select2({width:'100%',dropdownParent:$('#documentModal'),placeholder:'Cari kode / nama',ajax:{url:'<?=base_url('inventoryprofessional/lookup_items')?>',dataType:'json',delay:250,data:p=>({q:p.term||'',warehouse_id:$('#fromWarehouse').val()||$('#toWarehouse').val()}),processResults:r=>({results:r.results})}}).on('select2:select',function(e){tr.data('item',e.params.data);tr.find('.line-cost').val(Number(e.params.data.harga_beli||0).toLocaleString('id-ID'));if(String(e.params.data.tracking_type).toUpperCase()==='UNIT'&&['out','transfer','damaged','lost'].includes($('#documentType').val()))tr.find('.line-qty').val('1');tr.find('.line-barcode').val(null).trigger('change');tr.find('.line-batch').val(null).trigger('change')});tr.find('.line-barcode').select2({width:'100%',dropdownParent:$('#documentModal'),placeholder:'Opsional',allowClear:true,ajax:{url:'<?=base_url('inventoryprofessional/lookup_barcodes')?>',dataType:'json',delay:200,data:p=>({q:p.term||'',item_id:tr.find('.line-item').val(),warehouse_id:$('#fromWarehouse').val()}),processResults:r=>({results:r.results})}});tr.find('.line-batch').select2({width:'100%',dropdownParent:$('#documentModal'),placeholder:'Opsional',allowClear:true,ajax:{url:'<?=base_url('inventoryprofessional/lookup_batches')?>',dataType:'json',delay:200,data:p=>({q:p.term||'',item_id:tr.find('.line-item').val()}),processResults:r=>({results:r.results})}})}
function toggleType(){let t=$('#documentType').val(),needsFrom=['out','transfer','adjustment','opname','damaged','lost'].includes(t),needsTo=['opening','in','transfer'].includes(t);$('.warehouse-from').toggle(needsFrom);$('.warehouse-to').toggle(needsTo);$('.direction-column').toggle(t==='adjustment');$('.line-cost').prop('disabled',['out','transfer','opname','damaged','lost'].includes(t));$('.line-qty').attr('placeholder',t==='opname'?'Hasil hitung fisik':'Qty')}
$('#newDocument').on('click',function(){$('#documentForm')[0].reset();selectedFiles=[];renderAttachments();$('#documentDate').val('<?=date('Y-m-d')?>');$('#idempotencyKey').val('WEB-INV-'+Date.now()+'-'+Math.random().toString(36).slice(2));$('#documentLines').empty();addLine();toggleType();bootstrap.Modal.getOrCreateInstance('#documentModal').show()});$('#addLine').on('click',addLine);$('#documentType').on('change',toggleType);$(document).on('click','.remove-line',function(){if($('.document-line').length>1)$(this).closest('tr').remove()});
let selectedFiles=[];$(document).on('click','#chooseAttachments',()=>$('#documentAttachments').click());$('#documentAttachments').on('change',function(){selectedFiles=selectedFiles.concat(Array.from(this.files));this.value='';renderAttachments()});function renderAttachments(){let h=selectedFiles.map((f,i)=>{let preview=f.type.indexOf('image/')===0?'<img src="'+URL.createObjectURL(f)+'" alt="">':'<i class="bi bi-file-earmark-pdf attachment-file-icon"></i>';return'<div class="attachment-tile">'+preview+'<button type="button" class="remove-attachment" data-index="'+i+'" aria-label="Hapus"><i class="bi bi-x"></i></button><small>'+esc(f.name)+'</small></div>'}).join('');$('#attachmentNames').html(h+'<button type="button" class="attachment-add" id="chooseAttachments"><i class="bi bi-plus-lg"></i><span>Tambah</span></button>')}$(document).on('click','.remove-attachment',function(){selectedFiles.splice(Number($(this).data('index')),1);renderAttachments()});
$('#documentForm').on('submit',function(e){e.preventDefault();let lines=[];$('.document-line').each(function(){let r=$(this);lines.push({item_id:r.find('.line-item').val(),barcode_id:r.find('.line-barcode').val(),batch_id:r.find('.line-batch').val(),movement_direction:r.find('.line-direction').val(),qty:r.find('.line-qty').val().replace(/\./g,'').replace(',','.'),unit_cost:r.find('.line-cost').val().replace(/\./g,'').replace(',','.'),reason:r.find('.line-reason').val()})});if(lines.some(x=>!x.item_id||!x.qty))return AppAlert.toast('Barang dan qty setiap baris wajib diisi.','warning');let btn=$('#saveDocument').prop('disabled',true);api('<?=base_url('inventoryprofessional/create')?>',{type:$('#documentType').val(),date:$('#documentDate').val(),from_warehouse_id:$('#fromWarehouse').val(),to_warehouse_id:$('#toWarehouse').val(),source_type:$('#sourceType').val(),source_id:$('#sourceId').val(),reason:$('#documentReason').val(),idempotency_key:$('#idempotencyKey').val(),lines:JSON.stringify(lines)}).done(async function(r){let files=selectedFiles.slice();try{for(let f of files){let fd=new FormData();fd.append(APP_CSRF.name,APP_CSRF.value);fd.append('entity_type','stock_document');fd.append('entity_id',r.document_id);fd.append('attachment',f);await $.ajax({url:'<?=base_url('accountingattachments/upload')?>',type:'POST',data:fd,processData:false,contentType:false})}bootstrap.Modal.getInstance(document.getElementById('documentModal')).hide();table.ajax.reload(null,false);AppAlert.toast(r.message,'success')}catch(x){AppAlert.toast('Draft tersimpan, tetapi ada lampiran yang gagal diunggah. Buka detail untuk memeriksa.','warning')}}).fail(fail).always(()=>btn.prop('disabled',false))});
$(document).on('click','.btn-state',function(){let b=$(this),action=b.data('action'),word=action==='submit'?'mengajukan':action==='approve'?'menyetujui':'memposting';AppAlert.confirm({title:'Konfirmasi '+word+' dokumen',text:'Pastikan detail barang dan gudang sudah benar.'}).then(x=>{if(!x.isConfirmed)return;b.prop('disabled',true);api('<?=base_url('inventoryprofessional')?>/'+action+'/'+b.data('id')).done(r=>{table.ajax.reload(null,false);AppAlert.toast(r.message,'success')}).fail(fail).always(()=>b.prop('disabled',false))})});
$(document).on('click','.btn-reject',function(){let id=$(this).data('id');AppAlert.prompt({title:'Tolak dokumen stok',input:'textarea',inputLabel:'Alasan penolakan',inputValidator:v=>!v.trim()?'Alasan wajib diisi.':undefined,confirmButtonText:'Tolak Dokumen'}).then(x=>{if(x.isConfirmed)api('<?=base_url('inventoryprofessional/reject')?>/'+id,{reason:x.value}).done(r=>{table.ajax.reload(null,false);AppAlert.toast(r.message,'success')}).fail(fail)})});
$(document).on('click','.btn-reverse',function(){let id=$(this).data('id');AppAlert.prompt({title:'Reversal dokumen stok',html:'<label class="form-label d-block text-start">Tanggal reversal</label><input id="reverseDate" type="date" class="form-control mb-3" value="<?=date('Y-m-d')?>"><label class="form-label d-block text-start">Alasan</label><textarea id="reverseReason" class="form-control"></textarea>',preConfirm:()=>{let d=$('#reverseDate').val(),r=$('#reverseReason').val().trim();if(!d||!r){Swal.showValidationMessage('Tanggal dan alasan wajib diisi.');return false}return{date:d,reason:r}},confirmButtonText:'Posting Reversal'}).then(x=>{if(x.isConfirmed)api('<?=base_url('inventoryprofessional/reverse')?>/'+id,x.value).done(r=>{table.ajax.reload(null,false);AppAlert.toast(r.message,'success')}).fail(fail)})});
$(document).on('click','.btn-detail',function(){let id=$(this).data('id');$('#detailBody').html('<div class="inventory-loading"><span class="spinner-border spinner-border-sm"></span> Memuat detail…</div>');bootstrap.Modal.getOrCreateInstance('#detailModal').show();$.get('<?=base_url('inventoryprofessional/document_detail')?>/'+id).done(function(r){let d=r.document,lines=d.lines.map(l=>'<tr><td>'+esc(l.kode_detail+' - '+l.nama_barang)+'</td><td>'+esc(l.barcode)+(l.serial_number?'<small class="d-block">SN '+esc(l.serial_number)+'</small>':'')+'</td><td>'+esc(l.batch_no)+'</td><td class="text-end">'+fmt(d.document_type==='opname'?l.counted_qty:l.qty)+' '+esc(l.unit)+'</td><td class="text-end">'+money(l.unit_cost)+'</td><td>'+esc(l.reason)+'</td></tr>').join(''),hist=d.history.map(h=>'<li><strong>'+esc(labels[h.to_status]||h.to_status)+'</strong><span>'+esc(h.user_name)+' · '+esc(h.created_at)+'</span><span>'+esc(h.notes)+'</span></li>').join(''),atts=d.attachments.map(a=>'<button class="btn btn-sm btn-outline-secondary attachment-preview" data-url="'+a.preview_url+'"><i class="bi bi-paperclip"></i> '+esc(a.original_name)+'</button>').join(' ');$('#detailTitle').text(d.document_no);$('#detailBody').html('<div class="document-meta"><span>Jenis<strong>'+esc(labels[d.document_type]||d.document_type)+'</strong></span><span>Tanggal<strong>'+esc(d.document_date)+'</strong></span><span>Status<strong><span class="status-chip status-'+d.status+'">'+esc(labels[d.status]||d.status)+'</span></strong></span><span>Gudang Asal<strong>'+esc(d.from_warehouse)+'</strong></span><span>Gudang Tujuan<strong>'+esc(d.to_warehouse)+'</strong></span><span>Dokumen Sumber<strong>'+esc(d.source_type)+' #'+esc(d.source_id)+'</strong></span><span>Pembuat<strong>'+esc(d.creator_name)+'</strong></span><span>Pengaju<strong>'+esc(d.submitter_name)+'</strong></span><span>Approver / Poster<strong>'+esc(d.approver_name)+' / '+esc(d.poster_name)+'</strong></span></div><div class="alert alert-light border mt-3"><strong>Alasan:</strong> '+esc(d.reason)+'</div><div class="table-responsive"><table class="table inventory-table"><thead><tr><th>Barang</th><th>Barcode/SN</th><th>Batch</th><th class="text-end">Qty</th><th class="text-end">Biaya</th><th>Catatan</th></tr></thead><tbody>'+lines+'</tbody></table></div><h6>Lampiran</h6><div class="mb-3 d-flex flex-wrap gap-2">'+(atts||'<span class="text-muted">Tidak ada lampiran.</span>')+'</div><h6>Riwayat Status</h6><ul class="timeline-list">'+hist+'</ul>');$('#detailActions').html('<button class="btn btn-outline-danger btn-pdf" data-id="'+d.id+'"><i class="bi bi-file-earmark-pdf"></i> PDF</button><button class="btn btn-light" data-bs-dismiss="modal">Tutup</button>')}).fail(fail)});
$(document).on('click','.btn-pdf',function(){let id=$(this).data('id');$('#pdfModal iframe').attr('src','<?=base_url('inventoryprofessional/document_pdf')?>/'+id);bootstrap.Modal.getOrCreateInstance('#pdfModal').show()});
$(document).on('click','.attachment-preview',function(){$('#pdfModal iframe').attr('src',$(this).data('url'));bootstrap.Modal.getOrCreateInstance('#pdfModal').show()});
function initReservationSelects(){$('#reservationItem').select2({width:'100%',dropdownParent:$('#reservationModal'),placeholder:'Cari barang',ajax:{url:'<?=base_url('inventoryprofessional/lookup_items')?>',dataType:'json',delay:250,data:p=>({q:p.term||'',warehouse_id:$('#reservationWarehouse').val()}),processResults:r=>({results:r.results})}}).on('select2:select',function(e){let x=e.params.data;$('#reservationAvailability').html('<div><span>Stok fisik</span><strong>'+fmt(x.on_hand)+'</strong></div><div><span>Sudah direservasi</span><strong>'+fmt(x.reserved)+'</strong></div><div><span>Tersedia</span><strong>'+fmt(Number(x.on_hand)-Number(x.reserved))+'</strong></div>')});$('#reservationBarcode').select2({width:'100%',dropdownParent:$('#reservationModal'),placeholder:'Opsional',allowClear:true,ajax:{url:'<?=base_url('inventoryprofessional/lookup_barcodes')?>',dataType:'json',delay:200,data:p=>({q:p.term||'',item_id:$('#reservationItem').val(),warehouse_id:$('#reservationWarehouse').val()}),processResults:r=>({results:r.results})}})}initReservationSelects();
$('#newReservation').on('click',function(){$('#reservationForm')[0].reset();$('#reservationItem,#reservationBarcode').val(null).trigger('change');bootstrap.Modal.getOrCreateInstance('#reservationModal').show()});$('#reservationForm').on('submit',function(e){e.preventDefault();let b=$(this).find('button[type=submit]').prop('disabled',true);api('<?=base_url('inventoryprofessional/reserve')?>',{item_id:$('#reservationItem').val(),warehouse_id:$('#reservationWarehouse').val(),barcode_id:$('#reservationBarcode').val(),qty:$('#reservationQty').val().replace(',','.'),source_type:$('#reservationSourceType').val(),source_id:$('#reservationSourceId').val(),expires_at:$('#reservationExpiry').val(),notes:$('#reservationNotes').val()}).done(r=>{bootstrap.Modal.getInstance(document.getElementById('reservationModal')).hide();loadReservations();AppAlert.toast(r.message,'success')}).fail(fail).always(()=>b.prop('disabled',false))});
function loadReservations(){$.get('<?=base_url('inventoryprofessional/reservation_data')?>').done(r=>{$('#reservationTable tbody').html(r.data.map(x=>'<tr><td><strong>'+esc(x.reservation_no)+'</strong><small class="d-block text-muted">'+esc(x.barcode||x.serial_number)+'</small></td><td>'+esc(x.kode_detail+' - '+x.nama_barang)+'</td><td>'+esc(x.warehouse_name)+'</td><td>'+esc(x.source_type)+' #'+esc(x.source_id)+'</td><td>'+fmt(Number(x.qty)-Number(x.released_qty))+' / '+fmt(x.qty)+'</td><td>'+esc(x.expires_at)+'</td><td><span class="status-chip status-'+x.status+'">'+esc(x.status)+'</span></td><td>'+reservationActions(x)+'</td></tr>').join('')||'<tr><td colspan="8" class="text-center text-muted py-4">Belum ada reservasi.</td></tr>')})}function reservationActions(x){if(!permissions.update||!['active','partially_released'].includes(x.status))return'-';return'<div class="btn-group btn-group-sm"><button class="btn btn-outline-primary reservation-action" data-action="release" data-id="'+x.id+'" data-max="'+(Number(x.qty)-Number(x.released_qty))+'">Lepas</button><button class="btn btn-outline-success reservation-action" data-action="consume" data-id="'+x.id+'" data-max="'+(Number(x.qty)-Number(x.released_qty))+'">Gunakan</button><button class="btn btn-outline-danger reservation-action" data-action="cancel" data-id="'+x.id+'" data-max="'+(Number(x.qty)-Number(x.released_qty))+'">Batal</button></div>'}$(document).on('click','.reservation-action',function(){let b=$(this),action=b.data('action'),max=b.data('max');AppAlert.prompt({title:'Perbarui reservasi',html:action==='cancel'?'<p>Seluruh sisa reservasi akan dibatalkan.</p><textarea id="resNotes" class="form-control" placeholder="Catatan"></textarea>':'<label class="form-label d-block text-start">Qty (maks. '+fmt(max)+')</label><input id="resQty" class="form-control" value="'+max+'"><textarea id="resNotes" class="form-control mt-2" placeholder="Catatan"></textarea>',preConfirm:()=>({qty:action==='cancel'?0:$('#resQty').val().replace(',','.'),notes:$('#resNotes').val()})}).then(v=>{if(v.isConfirmed)api('<?=base_url('inventoryprofessional/update_reservation')?>/'+b.data('id'),{action:action,qty:v.value.qty,notes:v.value.notes}).done(r=>{loadReservations();AppAlert.toast(r.message,'success')}).fail(fail)})});loadReservations();
let focus=new URLSearchParams(location.search).get('focus');if(focus)setTimeout(()=>$('.btn-detail[data-id="'+focus+'"]').first().click(),800);
});
</script>