Files
accounting_dev_v2/application/views/inventory_professional/operations.php
T
2026-09-14 11:14:25 +07:00

69 lines
30 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.
<?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',timeout:30000})}
function hideGlobalLoading(){let loading=document.getElementById('globalLoading');if(loading)loading.hidden=true}
function fail(xhr){hideGlobalLoading();let message=xhr.responseJSON&&xhr.responseJSON.message;if(!message&&xhr.statusText==='timeout')message='Waktu proses habis. Silakan coba kembali.';AppAlert.toast(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 type="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 type="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 type="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 type="button" class="btn btn-sm btn-success btn-state" data-action="approve" data-id="'+r.id+'">Setujui</button><button type="button" class="btn btn-sm btn-outline-danger btn-reject" data-id="'+r.id+'">Tolak</button>';if(r.status==='approved'&&permissions.post)a+='<button type="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 type="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',original=b.html();hideGlobalLoading();AppAlert.confirm({title:'Konfirmasi '+word+' dokumen',text:'Pastikan detail barang dan gudang sudah benar.'}).then(x=>{if(!x.isConfirmed){hideGlobalLoading();return}b.prop('disabled',true).html('<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span> Memproses');api('<?=base_url('inventoryprofessional')?>/'+action+'/'+b.data('id')).done(r=>{table.ajax.reload(null,false);AppAlert.toast(r.message,'success')}).fail(fail).always(()=>{hideGlobalLoading();b.prop('disabled',false).html(original)})}).catch(()=>{hideGlobalLoading();b.prop('disabled',false).html(original)})});
$(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>