Update besar apk finance
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<link rel="stylesheet" href="<?=base_url('assets/css/inventory-professional.css')?>?v=<?=filemtime(FCPATH.'assets/css/inventory-professional.css')?>">
|
||||
<section class="inventory-hero mb-3">
|
||||
<div><span class="inventory-kicker">PERSEDIAAN TERINTEGRASI</span><h2 class="mb-1"><?=html_escape($inventory_title??'Persediaan')?></h2><p class="mb-0"><?=html_escape($inventory_subtitle??'Kontrol barang, gudang, dan nilai persediaan dalam satu alur.')?></p></div>
|
||||
<a class="btn btn-light inventory-scan-link" href="<?=base_url('inventoryprofessional')?>#scanner"><i class="bi bi-qr-code-scan"></i> Scan Barcode</a>
|
||||
</section>
|
||||
<nav class="inventory-module-nav mb-4" aria-label="Menu Persediaan">
|
||||
<a class="<?=($active_menu??'')==='inventory_overview'?'active':''?>" href="<?=base_url('inventoryprofessional')?>"><i class="bi bi-grid-1x2"></i><span>Ringkasan</span></a>
|
||||
<a class="<?=($active_menu??'')==='draft_items'?'active':''?>" href="<?=base_url('items/draft_items')?>"><i class="bi bi-hourglass-split"></i><span>Barang Pending</span></a>
|
||||
<a class="<?=($active_menu??'')==='items'?'active':''?>" href="<?=base_url('items')?>"><i class="bi bi-box-seam"></i><span>Daftar Barang</span></a>
|
||||
<a class="<?=($active_menu??'')==='inventory_operations'?'active':''?>" href="<?=base_url('inventoryprofessional/operations')?>"><i class="bi bi-arrow-left-right"></i><span>Operasional Stok</span></a>
|
||||
<a class="<?=($active_menu??'')==='inventory_locations'?'active':''?>" href="<?=base_url('inventoryprofessional/locations')?>"><i class="bi bi-geo-alt"></i><span>Gudang & Rak</span></a>
|
||||
<a class="<?=($active_menu??'')==='kode_barang'?'active':''?>" href="<?=base_url('kodebarang')?>"><i class="bi bi-tags"></i><span>Jenis & Kode Barang</span></a>
|
||||
<?php if(is_master_admin_user()||check_permission('technician_equipment','can_view')||check_permission('items','can_view')):?><a class="<?=($active_menu??'')==='peralatan_teknisi'?'active':''?>" href="<?=base_url('teknisi')?>"><i class="bi bi-tools"></i><span>Peralatan Teknisi</span></a><?php endif?>
|
||||
<a class="<?=($active_menu??'')==='inventory_reports'?'active':''?>" href="<?=base_url('inventoryprofessional/reports')?>"><i class="bi bi-clipboard-data"></i><span>Laporan</span></a>
|
||||
</nav>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php $inventory_title='Ringkasan Persediaan';$inventory_subtitle='Pekerjaan gudang, nilai stok, dan pengecualian yang perlu ditangani.';$this->load->view('inventory_professional/_module_nav',get_defined_vars());?>
|
||||
<div class="row g-3 inventory-metrics">
|
||||
<?php foreach(array(
|
||||
array('Barang Aktif',$summary->active_items,'bi-box-seam','primary'),array('Perlu Diaktifkan',$summary->pending_items,'bi-hourglass-split','warning'),
|
||||
array('Nilai Persediaan','Rp '.number_format($summary->inventory_value,2,',','.'),'bi-cash-stack','success'),array('Perlu Reorder',$summary->reorder_items,'bi-exclamation-triangle','danger'),
|
||||
array('Dokumen Terbuka',$summary->open_documents,'bi-file-earmark-text','info'),array('Reservasi Aktif',$summary->open_reservations,'bi-bookmark-check','secondary')
|
||||
)as$m):?><div class="col-6 col-xl-2"><div class="metric-card"><span class="metric-icon text-<?=$m[3]?>"><i class="bi <?=$m[2]?>"></i></span><strong><?=$m[1]?></strong><small><?=$m[0]?></small></div></div><?php endforeach?>
|
||||
</div>
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-xl-7"><section class="inventory-card h-100"><div class="inventory-card-head"><div><h5>Pekerjaan Gudang</h5><p>Barang yang sudah diterima tetapi belum siap digunakan atau dijual.</p></div><a href="<?=base_url('items/draft_items')?>" class="btn btn-sm btn-outline-primary">Buka Barang Pending</a></div>
|
||||
<?php if(!$action_items):?><div class="inventory-empty"><i class="bi bi-check-circle"></i><strong>Tidak ada barang pending</strong><span>Semua penerimaan sudah ditangani.</span></div><?php else:?><div class="inventory-list"><?php foreach($action_items as$i):?><a href="<?=base_url('items/draft_items?focus='.$i->id)?>"><span class="item-avatar"><i class="bi bi-box"></i></span><span class="flex-grow-1"><strong><?=html_escape($i->kode_detail.' - '.$i->nama_barang)?></strong><small><?=number_format($i->ready_barcodes,0,',','.')?> siap dari <?=number_format($i->pending_barcodes,0,',','.')?> barcode pending</small></span><i class="bi bi-chevron-right"></i></a><?php endforeach?></div><?php endif?>
|
||||
</section></div>
|
||||
<div class="col-xl-5"><section class="inventory-card h-100" id="scanner"><div class="inventory-card-head"><div><h5>Scan Barcode / Serial</h5><p>Lihat posisi, status, saldo, dan riwayat unit.</p></div></div><div class="p-3"><div class="input-group input-group-lg"><span class="input-group-text"><i class="bi bi-upc-scan"></i></span><input id="scanCode" class="form-control" placeholder="Scan atau ketik kode"><button id="scanButton" class="btn btn-primary">Periksa</button></div><div id="scanResult" class="scan-result mt-3"><div class="inventory-empty py-4"><i class="bi bi-qr-code-scan"></i><span>Hasil scan tampil di sini.</span></div></div></div></section></div>
|
||||
</div>
|
||||
<section class="inventory-card mt-3"><div class="inventory-card-head"><div><h5>Dokumen Mutasi Terbaru</h5><p>Jejak dokumen stok dari seluruh gudang.</p></div><a href="<?=base_url('inventoryprofessional/operations')?>" class="btn btn-sm btn-outline-primary">Lihat Semua</a></div><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Dokumen</th><th>Jenis</th><th>Tanggal</th><th>Lokasi</th><th>Status</th><th>Pembuat</th></tr></thead><tbody><?php foreach($documents as$d):?><tr><td><a href="<?=base_url('inventoryprofessional/operations?focus='.$d->id)?>" class="fw-semibold"><?=html_escape($d->document_no)?></a></td><td><?=html_escape(ucwords(str_replace('_',' ',$d->document_type)))?></td><td><?=date('d/m/Y',strtotime($d->document_date))?></td><td><?=html_escape(($d->from_warehouse?:'-').' → '.($d->to_warehouse?:'-'))?></td><td><span class="status-chip status-<?=$d->status?>"><?=html_escape(strtoupper($d->status))?></span></td><td><?=html_escape($d->creator_name?:'-')?></td></tr><?php endforeach?></tbody></table></div></section>
|
||||
<script>
|
||||
$(function(){
|
||||
function esc(v){return $('<div>').text(v==null?'-':v).html()}
|
||||
$('#scanButton').on('click',function(){var code=$('#scanCode').val().trim();if(!code)return AppAlert.toast('Barcode atau serial wajib diisi.','warning');var b=$(this).prop('disabled',true);$('#scanResult').html('<div class="inventory-loading"><span class="spinner-border spinner-border-sm"></span> Memeriksa barcode…</div>');$.ajax({url:'<?=base_url('inventoryprofessional/scan')?>',data:{code:code},dataType:'json'}).done(function(r){if(!r||!r.status||!r.item){var invalidMessage=r&&r.message?r.message:'Respons scan tidak lengkap.';AppAlert.toast(invalidMessage,'error');$('#scanResult').html('<div class="alert alert-danger mb-0">'+esc(invalidMessage)+'</div>');return;}var x=r.item,available=Math.max(0,Number(x.qty_sisa||0)-Number(x.reserved_qty||0)-Number(x.return_reserved_qty||0));var history=(Array.isArray(x.history)?x.history:[]).map(function(h){return '<li><strong>'+esc(h.movement_type)+'</strong><span>'+esc(h.notes)+' · '+esc(h.created_at)+'</span></li>'}).join('');$('#scanResult').html('<div class="scan-product"><span class="status-chip status-'+esc(x.status)+'">'+esc(x.status)+'</span><h5>'+esc((x.kode_detail||'')+' - '+(x.nama_barang||''))+'</h5><div class="scan-grid"><span>Barcode<strong>'+esc(x.barcode)+'</strong></span><span>Serial<strong>'+esc(x.serial_number)+'</strong></span><span>Gudang / Rak<strong>'+esc(x.warehouse_name)+' / '+esc(x.bin_code)+'</strong></span><span>Qty Tersedia<strong>'+available.toLocaleString('id-ID')+' '+esc(x.unit)+'</strong></span><span>Batch<strong>'+esc(x.batch_no)+'</strong></span></div><ul class="scan-history">'+(history||'<li>Belum ada riwayat perpindahan.</li>')+'</ul></div>')}).fail(function(xhr){var message=xhr.responseJSON&&xhr.responseJSON.message?xhr.responseJSON.message:'Barcode atau serial tidak ditemukan.';AppAlert.toast(message,'error');$('#scanResult').html('<div class="alert alert-danger mb-0">'+esc(message)+'</div>')}).always(function(){b.prop('disabled',false)})});
|
||||
$('#scanCode').on('keydown',function(e){if(e.key==='Enter')$('#scanButton').click()});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
$formatQty = static function ($value) {
|
||||
$number = (float) $value;
|
||||
$decimals = abs($number - round($number)) < 0.0001 ? 0 : 4;
|
||||
return number_format($number, $decimals, ',', '.');
|
||||
};
|
||||
$companyLabel = trim(($document->company_code ? $document->company_code.' - ' : '').($document->company_name ?: 'Perusahaan'));
|
||||
?>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body{font-family:DejaVu Sans,sans-serif;font-size:10px;color:#263044}
|
||||
h1{font-size:18px;margin:2px 0 0}.company{font-size:11px;font-weight:700}.subtitle{color:#657086}
|
||||
p{margin:3px 0}.head{border-bottom:2px solid #ee7800;padding-bottom:12px;margin-bottom:14px}
|
||||
.meta{width:100%;margin-bottom:14px}.meta td{width:33%;vertical-align:top;padding:4px}
|
||||
.label{font-size:8px;color:#6e7888;text-transform:uppercase}.value{font-weight:bold;margin-top:2px}
|
||||
table.lines{width:100%;border-collapse:collapse}.lines th{background:#edf2f8;text-align:left;font-size:8px}
|
||||
.lines th,.lines td{border:1px solid #dfe5ed;padding:6px;vertical-align:top}.right{text-align:right}
|
||||
.attachments{margin-top:12px;padding:8px;border:1px solid #dfe5ed}.attachments ul{margin:5px 0 0;padding-left:18px}
|
||||
.sign{margin-top:35px;width:100%;text-align:center}.sign td{width:25%;height:70px;vertical-align:top}
|
||||
.foot{position:fixed;bottom:0;font-size:8px;color:#788292}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="head">
|
||||
<div class="company"><?=html_escape($companyLabel)?></div>
|
||||
<h1>Dokumen Mutasi Persediaan</h1>
|
||||
<p class="subtitle"><?=html_escape($document->document_no)?> · <?=html_escape(strtoupper(str_replace('_', ' ', $document->document_type)))?></p>
|
||||
</div>
|
||||
|
||||
<table class="meta">
|
||||
<tr>
|
||||
<td><div class="label">Tanggal</div><div class="value"><?=date('d/m/Y', strtotime($document->document_date))?></div></td>
|
||||
<td><div class="label">Gudang Asal</div><div class="value"><?=html_escape($document->from_warehouse ?: '-')?></div></td>
|
||||
<td><div class="label">Gudang Tujuan</div><div class="value"><?=html_escape($document->to_warehouse ?: '-')?></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="label">Status</div><div class="value"><?=html_escape(strtoupper($document->status))?></div></td>
|
||||
<td><div class="label">Dokumen Sumber</div><div class="value"><?=html_escape(($document->source_type ?: '-').' #'.($document->source_id ?: '-'))?></div></td>
|
||||
<td><div class="label">Alasan</div><div class="value"><?=html_escape($document->reason ?: '-')?></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="lines">
|
||||
<thead>
|
||||
<tr><th>No</th><th>Barang</th><th>Barcode / Serial</th><th>Batch</th><th class="right">Qty</th><th class="right">Biaya Satuan</th><th>Catatan</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($document->lines as $line): ?>
|
||||
<tr>
|
||||
<td><?=$line->line_no?></td>
|
||||
<td><b><?=html_escape($line->kode_detail)?></b><br><?=html_escape($line->nama_barang)?></td>
|
||||
<td><?=html_escape($line->barcode ?: '-')?><?php if ($line->serial_number): ?><br>SN <?=html_escape($line->serial_number)?><?php endif ?></td>
|
||||
<td><?=html_escape($line->batch_no ?: '-')?></td>
|
||||
<td class="right"><?=$formatQty($document->document_type === 'opname' ? $line->counted_qty : $line->qty)?> <?=html_escape($line->unit ?: '')?></td>
|
||||
<td class="right">Rp <?=number_format((float) $line->unit_cost, 2, ',', '.')?></td>
|
||||
<td><?=html_escape($line->reason ?: '-')?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="attachments">
|
||||
<div class="label">Lampiran Pendukung</div>
|
||||
<?php if ($document->attachments): ?>
|
||||
<ul><?php foreach ($document->attachments as $attachment): ?><li><?=html_escape($attachment->original_name)?></li><?php endforeach ?></ul>
|
||||
<?php else: ?>
|
||||
<p>Tidak ada lampiran.</p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<table class="sign">
|
||||
<tr>
|
||||
<td>Dibuat oleh<br><br><br><b><?=html_escape($document->creator_name ?: '-')?></b></td>
|
||||
<td>Diajukan oleh<br><br><br><b><?=html_escape($document->submitter_name ?: '-')?></b></td>
|
||||
<td>Disetujui oleh<br><br><br><b><?=html_escape($document->approver_name ?: '-')?></b></td>
|
||||
<td>Diposting oleh<br><br><br><b><?=html_escape($document->poster_name ?: '-')?></b></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="foot">Dicetak <?=date('d/m/Y H:i')?> · Dokumen elektronik dapat diverifikasi melalui riwayat status aplikasi.</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php $inventory_title='Gudang & Lokasi Rak';$inventory_subtitle='Kelola gudang, rak/bin, serta batch tanpa menghapus histori transaksi.';$this->load->view('inventory_professional/_module_nav',get_defined_vars());?>
|
||||
<section class="inventory-card">
|
||||
<div class="inventory-card-head"><div><h5>Master Lokasi Persediaan</h5><p>Data yang sudah digunakan dinonaktifkan, bukan dihapus.</p></div></div>
|
||||
<ul class="nav nav-tabs px-3 pt-3" role="tablist"><li class="nav-item"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#warehousePane">Gudang</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#binPane">Rak / Bin</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#batchPane">Batch / Lot</button></li></ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="warehousePane"><div class="inventory-card-head"><div><h5>Daftar Gudang</h5><p>Pusat penyimpanan fisik barang.</p></div><?php if($inventory_permissions['update']):?><button class="btn btn-sm btn-primary add-master" data-type="warehouse"><i class="bi bi-plus"></i> Gudang</button><?php endif?></div><div class="table-responsive"><table class="table inventory-table mb-0"><thead><tr><th>Kode</th><th>Nama</th><th>Alamat</th><th>Status</th><th>Aksi</th></tr></thead><tbody id="warehouseRows"></tbody></table></div></div>
|
||||
<div class="tab-pane fade" id="binPane"><div class="inventory-card-head"><div><h5>Lokasi Rak / Bin</h5><p>Lokasi spesifik di dalam gudang.</p></div><?php if($inventory_permissions['update']):?><button class="btn btn-sm btn-primary add-master" data-type="bin"><i class="bi bi-plus"></i> Rak</button><?php endif?></div><div class="table-responsive"><table class="table inventory-table mb-0"><thead><tr><th>Gudang</th><th>Kode Rak</th><th>Nama</th><th>Status</th><th>Aksi</th></tr></thead><tbody id="binRows"></tbody></table></div></div>
|
||||
<div class="tab-pane fade" id="batchPane"><div class="inventory-card-head"><div><h5>Batch / Lot</h5><p>Pelacakan tanggal produksi dan kedaluwarsa.</p></div><?php if($inventory_permissions['update']):?><button class="btn btn-sm btn-primary add-master" data-type="batch"><i class="bi bi-plus"></i> Batch</button><?php endif?></div><div class="table-responsive"><table class="table inventory-table mb-0"><thead><tr><th>Barang</th><th>Batch</th><th>Produksi</th><th>Kedaluwarsa</th><th>Supplier</th><th>Status</th><th>Aksi</th></tr></thead><tbody id="batchRows"></tbody></table></div></div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="modal fade inventory-modal" id="masterModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">MASTER PERSEDIAAN</small><h5 class="modal-title">Data Master</h5></div><button class="btn-close" data-bs-dismiss="modal"></button></div><form id="masterForm"><div class="modal-body" id="masterFields"></div><div class="modal-footer"><button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button><button class="btn btn-primary">Simpan</button></div></form></div></div></div>
|
||||
<script>
|
||||
$(function(){
|
||||
const canUpdate=<?=json_encode($inventory_permissions['update'])?>,csrf=()=>({[APP_CSRF.name]:APP_CSRF.value}),esc=v=>$('<div>').text(v==null?'-':v).html();let data={warehouses:[],bins:[],batches:[]},type='warehouse',editing=null;
|
||||
function badge(v){return '<span class="status-chip '+(Number(v)?'status-approved':'status-rejected')+'">'+(Number(v)?'Aktif':'Nonaktif')+'</span>'}function button(t,x){return canUpdate?'<button class="btn btn-sm btn-outline-primary edit-master" data-type="'+t+'" data-id="'+x.id+'"><i class="bi bi-pencil"></i> Ubah</button>':'-'}
|
||||
function load(){$.get('<?=base_url('inventoryprofessional/master_data')?>').done(r=>{data=r;$('#warehouseRows').html(r.warehouses.map(x=>'<tr><td><strong>'+esc(x.code)+'</strong></td><td>'+esc(x.nama)+'</td><td>'+esc(x.address)+'</td><td>'+badge(x.is_active)+'</td><td>'+button('warehouse',x)+'</td></tr>').join('')||empty(5));$('#binRows').html(r.bins.map(x=>'<tr><td>'+esc(x.warehouse_name)+'</td><td><strong>'+esc(x.code)+'</strong></td><td>'+esc(x.name)+'</td><td>'+badge(x.is_active)+'</td><td>'+button('bin',x)+'</td></tr>').join('')||empty(5));$('#batchRows').html(r.batches.map(x=>'<tr><td>'+esc(x.kode_detail+' - '+x.nama_barang)+'</td><td><strong>'+esc(x.batch_no)+'</strong></td><td>'+esc(x.manufactured_date)+'</td><td>'+esc(x.expiry_date)+'</td><td>'+esc(x.supplier_name)+'</td><td>'+badge(x.is_active)+'</td><td>'+button('batch',x)+'</td></tr>').join('')||empty(7))}).fail(fail)}function empty(n){return'<tr><td colspan="'+n+'" class="text-center text-muted py-4">Belum ada data.</td></tr>'}function fail(x){AppAlert.toast(x.responseJSON&&x.responseJSON.message||'Data tidak dapat diproses.','error')}
|
||||
function warehouseOptions(selected){return'<option value="">-- Pilih Gudang --</option>'+data.warehouses.filter(x=>Number(x.is_active)||String(x.id)===String(selected)).map(x=>'<option value="'+x.id+'" '+(String(x.id)===String(selected)?'selected':'')+'>'+esc(x.code+' - '+x.nama)+'</option>').join('')}
|
||||
function fields(t,x){x=x||{};if(t==='warehouse')return'<input type="hidden" id="masterId" value="'+(x.id||'')+'"><div class="mb-3"><label class="form-label">Kode Gudang</label><input id="warehouseCode" class="form-control" value="'+esc(x.code||'')+'" required></div><div class="mb-3"><label class="form-label">Nama Gudang</label><input id="warehouseName" class="form-control" value="'+esc(x.nama||'')+'" required></div><div class="mb-3"><label class="form-label">Alamat</label><textarea id="warehouseAddress" class="form-control">'+esc(x.address||'')+'</textarea></div>'+active(x);if(t==='bin')return'<input type="hidden" id="masterId" value="'+(x.id||'')+'"><div class="mb-3"><label class="form-label">Gudang</label><select id="binWarehouse" class="form-select" required>'+warehouseOptions(x.warehouse_id)+'</select></div><div class="mb-3"><label class="form-label">Kode Rak / Bin</label><input id="binCode" class="form-control" value="'+esc(x.code||'')+'" required></div><div class="mb-3"><label class="form-label">Nama Lokasi</label><input id="binName" class="form-control" value="'+esc(x.name||'')+'"></div>'+active(x);return'<input type="hidden" id="masterId" value="'+(x.id||'')+'"><div class="mb-3"><label class="form-label">Barang</label><select id="batchItem" class="form-select"></select></div><div class="mb-3"><label class="form-label">Nomor Batch / Lot</label><input id="batchNo" class="form-control" value="'+esc(x.batch_no||'')+'" required></div><div class="row g-3"><div class="col-6"><label class="form-label">Tanggal Produksi</label><input id="batchMfg" type="date" class="form-control" value="'+esc(x.manufactured_date||'')+'"></div><div class="col-6"><label class="form-label">Kedaluwarsa</label><input id="batchExpiry" type="date" class="form-control" value="'+esc(x.expiry_date||'')+'"></div></div>'+active(x)}function active(x){return'<div class="form-check form-switch mt-3"><input id="masterActive" class="form-check-input" type="checkbox" '+(x.id&&!Number(x.is_active)?'':'checked')+'><label class="form-check-label">Aktif dan dapat dipilih pada transaksi baru</label></div>'}
|
||||
function open(t,x){type=t;editing=x||null;$('.modal-title').text((x?'Ubah ':'Tambah ')+(t==='warehouse'?'Gudang':t==='bin'?'Rak / Bin':'Batch / Lot'));$('#masterFields').html(fields(t,x));if(t==='batch'){$('#batchItem').select2({width:'100%',dropdownParent:$('#masterModal'),placeholder:'Cari barang',ajax:{url:'<?=base_url('inventoryprofessional/lookup_items')?>',dataType:'json',delay:250,data:p=>({q:p.term||''}),processResults:r=>({results:r.results})}});if(x&&x.item_id){let o=new Option(x.kode_detail+' - '+x.nama_barang,x.item_id,true,true);$('#batchItem').append(o).trigger('change')}}bootstrap.Modal.getOrCreateInstance('#masterModal').show()}
|
||||
$('.add-master').on('click',function(){open($(this).data('type'))});$(document).on('click','.edit-master',function(){let t=$(this).data('type'),list=t==='warehouse'?data.warehouses:t==='bin'?data.bins:data.batches;open(t,list.find(x=>String(x.id)===String($(this).data('id'))))});
|
||||
$('#masterForm').on('submit',function(e){e.preventDefault();let payload={id:$('#masterId').val(),is_active:$('#masterActive').is(':checked')?1:0},url='';if(type==='warehouse'){url='save_warehouse';Object.assign(payload,{code:$('#warehouseCode').val(),name:$('#warehouseName').val(),address:$('#warehouseAddress').val()})}else if(type==='bin'){url='save_bin';Object.assign(payload,{warehouse_id:$('#binWarehouse').val(),code:$('#binCode').val(),name:$('#binName').val()})}else{url='save_batch';Object.assign(payload,{item_id:$('#batchItem').val(),batch_no:$('#batchNo').val(),manufactured_date:$('#batchMfg').val(),expiry_date:$('#batchExpiry').val()})}let b=$(this).find('button[type=submit]').prop('disabled',true);$.post('<?=base_url('inventoryprofessional')?>/'+url,Object.assign(payload,csrf())).done(r=>{bootstrap.Modal.getInstance(document.getElementById('masterModal')).hide();AppAlert.toast(r.message,'success');load()}).fail(fail).always(()=>b.prop('disabled',false))});load();
|
||||
});
|
||||
</script>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html><head><meta charset="utf-8"><style>
|
||||
body{font-family:DejaVu Sans,sans-serif;font-size:8px;color:#253047}h1{font-size:17px;margin:0}.head{border-bottom:2px solid #ee7800;padding-bottom:10px;margin-bottom:14px}.company{font-size:11px;font-weight:700}.period{color:#657086}table{width:100%;border-collapse:collapse}th{background:#edf2f8;text-align:left}th,td{border:1px solid #dfe5ed;padding:5px;vertical-align:top}.num{text-align:right}.foot{margin-top:12px;color:#788292}
|
||||
</style></head><body>
|
||||
<div class="head"><div class="company"><?=html_escape($company->name??$company->company_name??'Perusahaan')?></div><h1><?=html_escape($definition['title'])?></h1><div class="period">Periode <?=date('d/m/Y',strtotime($filters['date_from']))?> sampai <?=date('d/m/Y',strtotime($filters['date_to']))?></div></div>
|
||||
<table><thead><tr><?php foreach($definition['columns']as$label):?><th><?=html_escape($label)?></th><?php endforeach?></tr></thead><tbody>
|
||||
<?php if(!$rows):?><tr><td colspan="<?=count($definition['columns'])?>" style="text-align:center">Tidak ada data pada filter yang dipilih.</td></tr><?php endif?>
|
||||
<?php foreach($rows as$row):?><tr><?php foreach($definition['columns']as$key=>$label):$value=$row->$key??null;$numeric=is_numeric($value)&&!in_array($key,array('source_id'),true);$money=in_array($key,array('inventory_value','value','unit_cost','ledger_value','stock_log_value','gl_value','difference'),true);?><td class="<?=$numeric?'num':''?>"><?php if($money):?>Rp <?=number_format((float)$value,2,',','.')?><?php elseif($numeric):?><?=number_format((float)$value,abs((float)$value-round((float)$value))<.0001?0:4,',','.')?><?php else:?><?=html_escape($value===null||$value===''?'-':$value)?><?php endif?></td><?php endforeach?></tr><?php endforeach?>
|
||||
</tbody></table><div class="foot">Dicetak <?=date('d/m/Y H:i')?>. Sumber saldo: inventory ledger. Filter dan histori dokumen dipertahankan untuk kebutuhan audit.</div>
|
||||
</body></html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
$inventory_title='Laporan Persediaan';
|
||||
$inventory_subtitle='Laporan operasional, penelusuran barcode, nilai stok, dan rekonsiliasi accounting dalam satu tempat.';
|
||||
$this->load->view('inventory_professional/_module_nav',get_defined_vars());
|
||||
?>
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-9">
|
||||
<section class="inventory-card">
|
||||
<div class="inventory-card-head">
|
||||
<div><h5 id="reportTitle">Nilai Persediaan</h5><p>Gunakan filter untuk menelusuri saldo dan dokumen sumber.</p></div>
|
||||
<?php if($inventory_permissions['export']):?><div class="d-flex gap-2"><button class="btn btn-sm btn-outline-success" id="exportExcel"><i class="bi bi-file-earmark-spreadsheet"></i> Excel</button><button class="btn btn-sm btn-outline-danger" id="exportPdf"><i class="bi bi-file-earmark-pdf"></i> PDF</button></div><?php endif?>
|
||||
</div>
|
||||
<div class="p-3 border-bottom inventory-toolbar">
|
||||
<div class="filter-field"><label class="form-label">Jenis Laporan</label><select id="reportType" class="form-select"><option value="valuation">Nilai Persediaan</option><option value="stock_card">Kartu Stok</option><option value="location">Stok per Gudang & Rak</option><option value="barcode">Barcode & Serial</option><option value="batch">Batch & Kedaluwarsa</option><option value="reservation">Reservasi Stok</option><option value="reorder">Stok Minimum / Reorder</option><option value="slow">Slow-moving / Dead Stock</option><option value="damage">Barang Rusak / Hilang</option><option value="opname">Stock Opname</option><option value="reconciliation">Riwayat Rekonsiliasi</option></select></div>
|
||||
<div class="filter-field"><label class="form-label">Gudang</label><select id="reportWarehouse" class="form-select"><option value="">Semua Gudang</option><?php foreach($warehouses as$w):?><option value="<?=$w->id?>"><?=html_escape($w->nama)?></option><?php endforeach?></select></div>
|
||||
<div class="filter-field item-filter"><label class="form-label">Barang</label><select id="reportItem" class="form-select"></select></div>
|
||||
<div class="filter-field date-filter"><label class="form-label">Dari Tanggal</label><input id="dateFrom" type="date" class="form-control" value="<?=date('Y-m-01')?>"></div>
|
||||
<div class="filter-field date-filter"><label class="form-label">Sampai Tanggal</label><input id="dateTo" type="date" class="form-control" value="<?=date('Y-m-d')?>"></div>
|
||||
<div class="filter-field slow-filter d-none"><label class="form-label">Tidak Bergerak</label><select id="reportDays" class="form-select"><option value="30">Minimal 30 hari</option><option value="60">Minimal 60 hari</option><option value="90" selected>Minimal 90 hari</option><option value="180">Minimal 180 hari</option></select></div>
|
||||
<button class="btn btn-primary align-self-end" id="loadReport"><i class="bi bi-search"></i> Tampilkan</button>
|
||||
</div>
|
||||
<div class="table-responsive"><table class="table inventory-table align-middle mb-0 w-100" id="reportTable"><thead></thead><tbody></tbody></table></div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xl-3">
|
||||
<section class="inventory-card">
|
||||
<div class="inventory-card-head"><div><h5>Rekonsiliasi</h5><p>Ledger dibandingkan dengan stock log, barcode, cache, dan GL.</p></div></div>
|
||||
<div class="p-3"><label class="form-label">Posisi per Tanggal</label><input id="reconcileDate" type="date" class="form-control" value="<?=date('Y-m-d')?>"><div id="reconcileResult" class="inventory-balance-box my-3"><div><span>Belum diperiksa.</span></div></div><?php if($inventory_permissions['post']):?><button id="runReconciliation" class="btn btn-outline-primary w-100"><i class="bi bi-arrow-repeat"></i> Jalankan Rekonsiliasi</button><?php endif?><div id="reconcileIssues" class="mt-3"></div><small class="d-block text-muted mt-3">Selisih tidak membuat jurnal koreksi otomatis. Anomali ditampilkan agar dapat ditelusuri ke dokumen sumber.</small></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade inventory-modal" id="reportPdfModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-xl"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Laporan 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="Laporan persediaan" style="width:100%;height:100%;border:0"></iframe></div></div></div></div>
|
||||
<script>
|
||||
$(function(){
|
||||
const csrf=()=>({[APP_CSRF.name]:APP_CSRF.value}),esc=v=>$('<div>').text(v==null||v===''?'-':v).html(),qty=v=>Number(v||0).toLocaleString('id-ID',{maximumFractionDigits:4}),money=v=>'Rp '+Number(v||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});
|
||||
const reports={
|
||||
valuation:{title:'Nilai Persediaan',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['costing_method','Metode'],['qty','Qty','qty'],['inventory_value','Nilai','money']]},
|
||||
stock_card:{title:'Kartu Stok',cols:[['movement_date','Tanggal'],['document_no','Dokumen'],['warehouse_name','Gudang'],['direction','Arah'],['qty','Qty','qty'],['unit_cost','Biaya Satuan','money'],['value','Nilai','money'],['reason','Keterangan']]},
|
||||
location:{title:'Stok per Gudang dan Rak',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['bin_code','Rak/Bin'],['qty','Qty','qty']]},
|
||||
barcode:{title:'Barcode dan Serial',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['barcode','Barcode'],['serial_number','Serial'],['warehouse_name','Gudang'],['bin_code','Rak'],['batch_no','Batch'],['qty_sisa','Qty','qty'],['reserved_qty','Reservasi','qty'],['status','Status','status']]},
|
||||
batch:{title:'Batch dan Kedaluwarsa',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['batch_no','Batch'],['warehouse_name','Gudang'],['manufactured_date','Produksi'],['expiry_date','Kedaluwarsa'],['qty','Qty','qty']]},
|
||||
reservation:{title:'Reservasi Stok',cols:[['reservation_no','Reservasi'],['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['source_type','Jenis Sumber'],['source_id','ID Sumber'],['qty','Qty','qty'],['released_qty','Diselesaikan','qty'],['expires_at','Berlaku Sampai'],['status','Status','status']]},
|
||||
reorder:{title:'Stok Minimum dan Reorder',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['qty','Qty','qty'],['reorder_point','Reorder Point','qty'],['minimum_stock','Minimum','qty']]},
|
||||
slow:{title:'Slow-moving dan Dead Stock',cols:[['kode_detail','Kode'],['nama_barang','Barang'],['stok','Stok','qty'],['last_movement_at','Terakhir Bergerak'],['idle_days','Tidak Bergerak (hari)','qty']]},
|
||||
damage:{title:'Barang Rusak dan Hilang',cols:[['document_no','Dokumen'],['document_date','Tanggal'],['document_type','Jenis'],['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['qty','Qty','qty'],['value','Nilai','money'],['status','Status','status'],['reason','Alasan']]},
|
||||
opname:{title:'Stock Opname',cols:[['document_no','Dokumen'],['document_date','Tanggal'],['kode_detail','Kode'],['nama_barang','Barang'],['warehouse_name','Gudang'],['system_qty','Stok Sistem','qty'],['counted_qty','Fisik','qty'],['difference','Selisih','qty'],['status','Status','status']]},
|
||||
reconciliation:{title:'Riwayat Rekonsiliasi',cols:[['as_of_date','Tanggal'],['ledger_value','Ledger','money'],['stock_log_value','Stock Log','money'],['gl_value','General Ledger','money'],['difference','Selisih','money'],['barcode_variance_count','Selisih Barcode','qty'],['cache_variance_count','Selisih Cache','qty'],['status','Status','status']]}
|
||||
};
|
||||
let current=[],dataTable=null;
|
||||
$('#reportItem').select2({width:'100%',placeholder:'Semua / cari barang',allowClear:true,ajax:{url:'<?=base_url('inventoryprofessional/lookup_items')?>',dataType:'json',delay:250,data:p=>({q:p.term||'',warehouse_id:$('#reportWarehouse').val()}),processResults:r=>({results:r.results})}});
|
||||
function params(){return{type:$('#reportType').val(),warehouse_id:$('#reportWarehouse').val(),item_id:$('#reportItem').val(),date_from:$('#dateFrom').val(),date_to:$('#dateTo').val(),days:$('#reportDays').val()}}
|
||||
function fail(x){AppAlert.toast(x.responseJSON&&x.responseJSON.message||'Laporan tidak dapat dimuat.','error')}
|
||||
function cell(value,kind,key,row){if(key==='document_no'&&row.document_id&&['opening','in','out','transfer','adjustment','opname','damaged','lost'].includes(row.document_type))return'<a class="fw-semibold" href="<?=base_url('inventoryprofessional/operations')?>?focus='+encodeURIComponent(row.document_id)+'">'+esc(value)+'</a>';if(kind==='money')return money(value);if(kind==='qty')return qty(value);if(kind==='status')return'<span class="status-chip status-'+esc(value)+'">'+esc(String(value||'-').replaceAll('_',' '))+'</span>';return esc(value)}
|
||||
function render(type,rows){current=rows;const def=reports[type];if(dataTable){dataTable.destroy();dataTable=null}$('#reportTable thead').html('<tr>'+def.cols.map(c=>'<th>'+esc(c[1])+'</th>').join('')+'</tr>');$('#reportTable tbody').html(rows.map(r=>'<tr>'+def.cols.map(c=>'<td class="'+(c[2]==='money'||c[2]==='qty'?'text-end':'')+'">'+cell(r[c[0]],c[2],c[0],r)+'</td>').join('')+'</tr>').join('')||'<tr><td colspan="'+def.cols.length+'" class="text-center text-muted py-5">Tidak ada data pada filter yang dipilih.</td></tr>');if(rows.length)dataTable=$('#reportTable').DataTable({responsive:true,pageLength:25,order:[],searchDelay:250,language:{search:'Cari:',lengthMenu:'Tampilkan _MENU_',info:'_START_–_END_ dari _TOTAL_',paginate:{previous:'Sebelumnya',next:'Berikutnya'}}})}
|
||||
function load(){let type=$('#reportType').val(),b=$('#loadReport').prop('disabled',true);$('#reportTitle').text(reports[type].title);$('#reportTable tbody').html('<tr><td class="text-center py-5"><span class="spinner-border spinner-border-sm"></span> Memuat laporan...</td></tr>');$.get('<?=base_url('inventoryprofessional/report_data')?>',params()).done(r=>render(type,r.data)).fail(fail).always(()=>b.prop('disabled',false))}
|
||||
$('#reportType').on('change',function(){let type=this.value;$('.slow-filter').toggleClass('d-none',type!=='slow');$('.item-filter').toggleClass('d-none',type==='reconciliation');$('.date-filter').toggleClass('d-none',['valuation','location','barcode','batch','reorder','slow'].includes(type));load()});$('#loadReport').on('click',load);
|
||||
$('#exportPdf').on('click',function(){let q=$.param(params());$('#reportPdfModal .modal-title').text(reports[$('#reportType').val()].title);$('#reportPdfModal iframe').attr('src','<?=base_url('inventoryprofessional/report_pdf')?>?'+q);bootstrap.Modal.getOrCreateInstance(document.getElementById('reportPdfModal')).show()});
|
||||
$('#exportExcel').on('click',function(){window.location.href='<?=base_url('inventoryprofessional/report_excel')?>?'+$.param(params())});
|
||||
function issueList(details){let rows=[];(details.ledger_vs_stock_log||[]).forEach(x=>rows.push('<li><strong>'+esc(x.kode_detail+' - '+x.nama_barang)+'</strong><span>'+esc(x.warehouse_name)+' · Selisih qty '+qty(x.qty_difference)+' · Selisih nilai '+money(x.value_difference)+'</span></li>'));(details.barcode||[]).forEach(x=>rows.push('<li><strong>'+esc(x.kode_detail+' - '+x.nama_barang)+'</strong><span>'+esc(x.warehouse_name)+' · Ledger '+qty(x.ledger_qty)+' · Barcode '+qty(x.barcode_qty)+' · Selisih '+qty(x.difference)+'</span></li>'));(details.cache||[]).forEach(x=>rows.push('<li><strong>'+esc(x.kode_detail+' - '+x.nama_barang)+'</strong><span>Cache '+qty(x.cache_qty)+' · Ledger '+qty(x.ledger_qty)+'</span></li>'));return rows.length?'<div class="alert alert-warning py-2"><strong>Sumber anomali</strong><ul class="timeline-list mt-2 mb-0">'+rows.slice(0,12).join('')+'</ul></div>':'<div class="alert alert-success py-2">Tidak ada rincian anomali operasional.</div>'}
|
||||
$('#runReconciliation').on('click',function(){let b=$(this).prop('disabled',true);$.post('<?=base_url('inventoryprofessional/reconciliation')?>',Object.assign({date:$('#reconcileDate').val()},csrf())).done(r=>{let x=r.result;$('#reconcileResult').html('<div><span>Inventory Ledger</span><strong>'+money(x.ledger_value)+'</strong></div><div><span>Stock Log</span><strong>'+money(x.stock_log_value)+'</strong></div><div><span>General Ledger</span><strong>'+money(x.gl_value)+'</strong></div><div><span>Selisih Ledger–GL</span><strong>'+money(x.difference)+'</strong></div><div><span>Selisih Cache</span><strong>'+qty(x.cache_variance_count)+' barang</strong></div><div><span>Selisih Barcode</span><strong>'+qty(x.barcode_variance_count)+' posisi</strong></div><div><span>Status</span><strong><span class="status-chip status-'+(x.status==='matched'?'approved':'rejected')+'">'+esc(x.status)+'</span></strong></div>');$('#reconcileIssues').html(issueList(x.details||{}));AppAlert.toast(r.message,x.status==='matched'?'success':'warning')}).fail(fail).always(()=>b.prop('disabled',false))});
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user