Update besar apk finance

This commit is contained in:
Wian Drs
2026-09-11 16:03:00 +07:00
parent d42483b350
commit 4ef15c22ae
897 changed files with 98056 additions and 15979 deletions
+78
View File
@@ -0,0 +1,78 @@
(function($){
'use strict';
const cfg=window.TECH_EQUIPMENT||{};const base=cfg.base||'/';const perms=cfg.permissions||{};let scanned=null,replacementAllocations=[],technicianTable=null,documentTable=null,customerNotesTable=null;
const esc=v=>$('<div>').text(v==null?'':String(v)).html();
const qty=v=>{const n=Number(v||0);return Number.isInteger(n)?n.toLocaleString('id-ID'):n.toLocaleString('id-ID',{maximumFractionDigits:4})};
const labels={company:'Milik Perusahaan',customer:'Milik Customer',transferred_non_inventory:'Diserahkan Non-Persediaan',unknown:'Belum Diketahui',warehouse:'Gudang',technician:'Teknisi',customer_custody:'Customer',quarantine:'Menunggu Pemeriksaan',service_center:'Pusat Perbaikan',carried:'Dibawa',installed:'Dipasang',removed:'Dilepas',returned:'Dikembalikan',good:'Baik',inspection_required:'Perlu Diperiksa',repair_required:'Perlu Perbaikan',under_repair:'Dalam Perbaikan',damaged:'Rusak',lost:'Hilang',scrapped:'Dihapuskan'};
const actionLabels={opening:'Saldo Awal Migrasi',handover:'Serahkan ke Teknisi',install:'Pasang di Customer',remove:'Lepas ke Teknisi',transfer:'Transfer Teknisi',return:'Kembali ke Gudang',return_customer:'Kembali ke Pemilik',inspect:'Periksa Barang',inspection:'Pemeriksaan',replacement:'Penggantian',correction:'Koreksi / Reversal'};
const toast=(ok,msg)=>{if(window.Swal)Swal.fire({toast:true,position:'top-end',icon:ok?'success':'error',title:msg,showConfirmButton:false,timer:ok?2600:4500,timerProgressBar:true});else alert(msg)};
const confirmPost=async title=>{if(!window.Swal)return confirm(title);const r=await Swal.fire({icon:'question',title:title,text:'Pastikan data, jumlah, dan dokumen sudah benar.',showCancelButton:true,confirmButtonText:'Ya, posting',cancelButtonText:'Periksa lagi',allowOutsideClick:false});return r.isConfirmed};
function modal(id,show=true){const el=document.getElementById(id);if(!el)return;if(window.bootstrap&&bootstrap.Modal){const m=bootstrap.Modal.getOrCreateInstance(el,{backdrop:'static',keyboard:false});show?m.show():m.hide()}else $(el).modal(show?'show':'hide')}
function randomKey(){return 'TEQ-'+Date.now()+'-'+Math.random().toString(36).slice(2)+cryptoPart()}
function cryptoPart(){try{const a=new Uint32Array(1);crypto.getRandomValues(a);return '-'+a[0]}catch(e){return ''}}
function positionText(a){if(a.custodian_type==='technician')return 'Teknisi: '+(a.technician_name||'-');if(a.custodian_type==='customer')return 'Customer: '+(a.customer_name||'-');if(a.custodian_type==='quarantine')return 'Karantina: '+(a.warehouse_name||'-');return labels[a.custodian_type]||a.custodian_type}
function can(action){const map={handover:'handover',install:'install',remove:'remove',transfer:'transfer',return:'return',return_customer:'return',inspect:'inspect',replacement:'install'};return !!perms[map[action]]}
const tableLanguage={search:'Cari:',lengthMenu:'Tampilkan _MENU_ data',info:'Menampilkan _START_ - _END_ dari _TOTAL_ data',infoEmpty:'Belum ada data',zeroRecords:'Data tidak ditemukan',paginate:{next:'&raquo;',previous:'&laquo;'}};
function createSearchTable(selector,order,hasActionColumn=true){if(!$.fn.DataTable||!document.querySelector(selector))return null;const body=document.querySelector(selector+' tbody'),hasRows=body&&body.querySelector('tr')&&!body.querySelector('td[colspan]');if(!hasRows)return null;if($.fn.DataTable.isDataTable(selector))$(selector).DataTable().destroy();return $(selector).DataTable({pageLength:10,order:order||[],responsive:true,autoWidth:false,language:tableLanguage,columnDefs:hasActionColumn?[{targets:-1,orderable:false,searchable:false}]:[]})}
function initEmployeeSearch(scope){if(!$.fn.select2)return;$(scope||document).find('select[name="technician_id"],select[name="target_technician_id"],select[name="user_id"]').each(function(){const select=$(this);if(select.hasClass('select2-hidden-accessible'))return;const parent=select.closest('.modal');select.select2({width:'100%',placeholder:select.find('option:first').text()||'Cari karyawan',allowClear:true,dropdownParent:parent.length?parent:$(document.body)})})}
function renderScan(item){
let allocations=(item.allocations||[]).map(a=>{let acts=[];if(['quarantine','service_center'].includes(a.custodian_type)&&['inspection_required','repair_required','under_repair','damaged'].includes(a.condition_status))acts=['inspect'];else if(a.custodian_type==='technician')acts=['install','transfer','return'].concat(item.subject_type==='customer_equipment'?['return_customer']:[]);else if(a.custodian_type==='customer'&&a.usage_status==='installed')acts=['remove','replacement'];const buttons=acts.filter(can).map(x=>x==='replacement'?'<a class="btn btn-sm btn-outline-primary" href="'+base+'teknisi/replacement_workflow"><i class="bi bi-arrow-repeat"></i> '+actionLabels[x]+'</a>':(['transfer','return'].includes(x)?'<a class="btn btn-sm btn-outline-primary" href="'+base+'teknisi/bulk_workflow?action='+x+'&technician_id='+Number(a.custodian_id||0)+'&scan='+encodeURIComponent(item.barcode||a.barcode_snapshot)+'"><i class="bi bi-box-arrow-right"></i> '+actionLabels[x]+' (Batch)</a>':'<button class="btn btn-sm btn-outline-primary tech-action" data-action="'+x+'" data-allocation="'+a.id+'">'+actionLabels[x]+'</button>')).join('');return '<tr><td>'+esc(positionText(a))+'</td><td><span class="tech-status">'+esc(labels[a.usage_status]||a.usage_status)+'</span></td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td><td>'+qty(a.qty)+'</td><td><div class="tech-actions mt-0">'+buttons+'</div></td></tr>'}).join('');
let handover=item.subject_type==='company_barcode'&&Number(item.warehouse_available)>0&&can('handover')?'<a class="btn btn-primary" href="'+base+'teknisi/bulk_workflow?action=handover&scan='+encodeURIComponent(item.barcode)+'"><i class="bi bi-person-plus"></i> Tambahkan ke Penyerahan Batch</a>':'';
$('#techScanResult').removeClass('tech-empty').html('<div class="tech-identity"><div class="tech-kv"><small>Barang</small><strong>'+esc(item.display_name)+'</strong><br><span class="'+(item.ownership_type==='company'?'tech-owner-company':'tech-owner-customer')+'">'+esc(labels[item.ownership_type]||item.ownership_type)+'</span></div><div class="tech-kv"><small>Barcode</small>'+esc(item.barcode||'-')+'</div><div class="tech-kv"><small>Serial</small>'+esc(item.serial_number||'-')+'</div><div class="tech-kv"><small>Jenis</small>'+esc(item.tracking_type||'-')+'</div><div class="tech-kv"><small>Tersedia di Gudang</small>'+qty(item.warehouse_available||0)+'</div></div><div class="tech-actions">'+handover+'</div><div class="tech-table-wrap mt-3"><table class="table tech-table"><thead><tr><th>Posisi</th><th>Penggunaan</th><th>Kondisi</th><th>Qty</th><th>Tindakan Valid</th></tr></thead><tbody>'+(allocations||'<tr><td colspan="5" class="tech-empty">Tidak ada saldo di luar gudang.</td></tr>')+'</tbody></table></div>');
}
function loadScanned(code){const btn=$('#techScanForm button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_scan',{code:code}).done(r=>{if(!r.status)return toast(false,r.message||'Barcode tidak dapat diperiksa.');scanned=r.result||r.data&&r.data.result;if(!scanned)return toast(false,'Respons barcode tidak lengkap.');renderScan(scanned)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Barcode tidak dapat diperiksa.')).always(()=>btn.prop('disabled',false))}
function renderLookup(rows){$('#techScanResult').removeClass('tech-empty').html('<div class="tech-card-head mb-2"><div><strong>Hasil pencarian</strong><p>Pilih barcode yang akan diperiksa.</p></div><span class="tech-status">'+rows.length+' hasil</span></div><div class="tech-search-results">'+rows.map((row,index)=>'<button type="button" class="tech-search-option tech-lookup-select" data-code="'+esc(row.lookup_code)+'"><span><strong>'+esc(row.display_name)+'</strong><small>'+esc(row.barcode)+(row.serial_number?' · SN '+esc(row.serial_number):'')+'</small></span><span><b>'+qty(row.available_qty)+'</b><small>'+esc(row.tracking_type)+'</small></span></button>').join('')+'</div>')}
$('#techScanForm').on('submit',function(e){e.preventDefault();const query=$('#techScanCode').val().trim();if(!query)return toast(false,'Scan barcode atau isi nama barang.');const btn=$(this).find('button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_search',{q:query,mode:'all'}).done(r=>{const rows=r.data||[];if(!rows.length)return toast(false,'Barang tidak ditemukan.');const exact=rows.filter(row=>[row.barcode,row.serial_number].some(value=>String(value||'').toLowerCase()===query.toLowerCase()));if(exact.length===1)loadScanned(exact[0].lookup_code);else if(rows.length===1)loadScanned(rows[0].lookup_code);else renderLookup(rows)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Pencarian barang gagal.')).always(()=>btn.prop('disabled',false))});
$(document).on('click','.tech-lookup-select',function(){$('#techScanCode').val(this.dataset.code);loadScanned(this.dataset.code)});
function allocation(id){return scanned&&scanned.allocations?(scanned.allocations.find(x=>String(x.id)===String(id))||null):null}
function openAction(action,id){const a=id?allocation(id):null;const form=$('#techActionForm')[0];form.reset();form.elements.action.value=action;form.elements.subject_id.value=scanned?scanned.subject_id:'';form.elements.allocation_id.value=id||'';form.elements.idempotency_key.value=randomKey();form.elements.date.value=new Date().toISOString().slice(0,10);$('#techActionForm .modal-title').text(actionLabels[action]||'Catat Pergerakan');$('#techActionSubject').html('<strong>'+esc(scanned?scanned.display_name:(a?a.display_name:'Barang'))+'</strong><br><small>'+esc(scanned?(scanned.barcode||''):(a?a.barcode_snapshot:''))+(a?' · '+esc(positionText(a))+' · Saldo '+qty(a.qty):'')+'</small>');
const needs={qty:true,technician:['handover','remove'].includes(action),target_technician:action==='transfer',customer:['install','return_customer'].includes(action),warehouse:action==='return',condition:action==='remove',inspection:action==='inspect',location:action==='install',reason:['remove','return','transfer'].includes(action)};Object.keys(needs).forEach(k=>{$('[data-field="'+k+'"]').toggleClass('d-none',!needs[k]).find('input,select').prop('required',!!needs[k])});
if(action==='inspect'&&a){form.elements.allocation_id.value=a.id}if((scanned&&scanned.tracking_type==='UNIT')||(a&&a.tracking_type==='UNIT')){$('[data-field="qty"]').addClass('d-none');form.elements.qty.value=1}else form.elements.qty.value=a?a.qty:(scanned?scanned.warehouse_available:'');clearFileBox($('#techActionForm [data-filebox]')[0]);modal('techActionModal',true)}
$(document).on('click','.tech-action',function(){openAction(this.dataset.action,this.dataset.allocation)});
$('#techActionForm').on('submit',async function(e){e.preventDefault();const form=this,action=form.elements.action.value;modal('techActionModal',false);await new Promise(resolve=>setTimeout(resolve,180));if(!(await confirmPost(actionLabels[action]||'Posting pergerakan?'))){modal('techActionModal',true);return}const btn=$(form).find('[type=submit]').prop('disabled',true);const url=action==='inspect'?'teknisi/process_inspection':'teknisi/process_equipment';$.ajax({url:base+url,method:'POST',data:new FormData(form),processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status){toast(false,r.message);modal('techActionModal',true);return}toast(true,r.message);if($('#techScanCode').length)$('#techScanForm').trigger('submit');loadInspection();loadDocuments()}).fail(x=>{toast(false,(x.responseJSON&&x.responseJSON.message)||'Transaksi gagal diproses.');modal('techActionModal',true)}).always(()=>btn.prop('disabled',false))});
function loadDocuments(){const table=$('#techDocumentsTable tbody');if(!table.length)return;if(documentTable){documentTable.destroy();documentTable=null}$.getJSON(base+'teknisi/equipment_documents').done(r=>{const rows=(r.data||[]).slice(0,100);table.html(rows.length?rows.map(d=>'<tr><td><strong>'+esc(d.document_no)+'</strong><br><small>'+esc(d.status)+'</small></td><td>'+esc(d.document_date)+'</td><td>'+esc(actionLabels[d.document_type]||d.document_type)+'</td><td>'+esc(d.technician_name||'-')+'<br><small>'+esc(d.customer_name||'-')+'</small></td><td>'+qty(d.line_count)+'</td><td><div class="tech-doc-actions"><button class="btn btn-outline-primary btn-sm tech-doc-detail" data-id="'+d.id+'"><i class="bi bi-eye"></i> Detail</button>'+(perms.export?'<button class="btn btn-outline-danger btn-sm tech-doc-pdf" data-id="'+d.id+'"><i class="bi bi-file-pdf"></i> PDF</button>':'')+'</div></td></tr>').join(''):'<tr><td colspan="6" class="tech-empty">Belum ada dokumen operasional.</td></tr>');if(rows.length)documentTable=createSearchTable('#techDocumentsTable',[[1,'desc']])}).fail(()=>table.html('<tr><td colspan="6" class="tech-empty text-danger">Riwayat gagal dimuat.</td></tr>'))}
$(document).on('click','.tech-doc-detail',function(){const id=this.dataset.id;$.getJSON(base+'teknisi/equipment_document/'+id).done(r=>{const d=r.result;let lines=(d.lines||[]).map(l=>'<tr><td>'+esc(l.display_name)+'</td><td>'+esc(l.barcode_snapshot)+(l.serial_number?'<br><small>SN '+esc(l.serial_number)+'</small>':'')+'</td><td>'+esc(labels[l.ownership_type]||l.ownership_type)+'</td><td>'+esc(labels[l.from_custodian_type]||l.from_custodian_type)+' → '+esc(labels[l.to_custodian_type]||l.to_custodian_type)+'</td><td>'+qty(l.qty)+'</td><td>'+esc(l.notes||'-')+'</td></tr>').join('');let files=(perms.attachment?d.attachments||[]:[]).map(f=>'<button class="btn btn-outline-secondary btn-sm tech-attachment" data-url="'+base+'accountingattachments/preview/'+f.id+'" data-mime="'+esc(f.mime_type)+'"><i class="bi '+(f.mime_type==='application/pdf'?'bi-file-pdf':'bi-image')+'"></i> '+esc(f.original_name)+'</button>').join(' ');let correction=perms.correct&&d.status==='posted'&&d.document_type!=='correction'&&d.document_type!=='opening'?'<button class="btn btn-outline-warning tech-correct-doc mt-3" data-id="'+d.id+'" data-no="'+esc(d.document_no)+'"><i class="bi bi-arrow-counterclockwise"></i> Koreksi / Reversal</button>':'';$('#techDocumentBody').html('<div class="tech-modal-summary"><div><small>Nomor / Tanggal</small><strong>'+esc(d.document_no)+'</strong><br>'+esc(d.document_date)+'</div><div><small>Proses / Teknisi</small>'+esc(actionLabels[d.document_type]||d.document_type)+'<br>'+esc(d.technician_name||'-')+'</div><div><small>Customer / Gudang</small>'+esc(d.customer_name||'-')+'<br>'+esc(d.warehouse_name||'-')+'</div></div><p class="mt-3"><strong>Keterangan:</strong> '+esc(d.notes||'-')+'</p><div class="tech-table-wrap"><table class="table tech-table"><thead><tr><th>Barang</th><th>Barcode</th><th>Pemilik</th><th>Perpindahan</th><th>Qty</th><th>Keterangan</th></tr></thead><tbody>'+lines+'</tbody></table></div><div class="mt-3"><strong>Lampiran</strong><div class="tech-actions">'+(files||'<span class="text-muted">Tidak ada lampiran atau akses tidak diberikan.</span>')+'</div></div>'+correction);modal('techDocumentModal',true)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Detail tidak dapat dibuka.'))});
$(document).on('click','.tech-correct-doc',async function(){const id=this.dataset.id,no=this.dataset.no;modal('techDocumentModal',false);if(!window.Swal)return;const r=await Swal.fire({icon:'warning',title:'Koreksi '+no,html:'<label class="w-100 text-start">Tanggal koreksi<input id="techCorrectionDate" type="date" class="swal2-input mx-0 w-100" value="'+new Date().toISOString().slice(0,10)+'"></label><label class="w-100 text-start">Alasan wajib<textarea id="techCorrectionReason" class="swal2-textarea mx-0 w-100" placeholder="Jelaskan alasan koreksi"></textarea></label>',showCancelButton:true,confirmButtonText:'Buat Reversal',cancelButtonText:'Batal',allowOutsideClick:false,preConfirm:()=>{const date=document.getElementById('techCorrectionDate').value,reason=document.getElementById('techCorrectionReason').value.trim();if(!date||!reason){Swal.showValidationMessage('Tanggal dan alasan koreksi wajib diisi.');return false}return{date,reason}}});if(!r.isConfirmed)return;$.post(base+'teknisi/process_correction',{document_id:id,date:r.value.date,reason:r.value.reason,idempotency_key:randomKey()},res=>{toast(!!res.status,res.message);if(res.status)loadDocuments()},'json').fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Koreksi gagal diproses.'))});
$(document).on('click','.tech-doc-pdf',function(){preview(base+'teknisi/equipment_document_pdf/'+this.dataset.id,'application/pdf')});
$(document).on('click','.tech-attachment',function(){preview(this.dataset.url,this.dataset.mime)});
function preview(url,mime){$('#techPreviewBody').html(mime==='application/pdf'?'<iframe title="Preview PDF" src="'+esc(url)+'" style="width:100%;height:100%;border:0"></iframe>':'<div class="h-100 d-grid align-items-center text-center"><img src="'+esc(url)+'" alt="Preview" style="max-width:100%;max-height:100%;margin:auto;object-fit:contain"></div>');modal('techPreviewModal',true)}
function replacementCandidates(kind,query){const form=document.getElementById('techReplacementForm');if(!form)return[];const tech=Number(form.elements.technician_id.value||0),customer=Number(form.elements.customer_id.value||0),q=String(query||'').trim().toLowerCase();return replacementAllocations.filter(a=>{const eligible=kind==='carried'?a.custodian_type==='technician'&&Number(a.custodian_id)===tech&&['carried','removed','returned'].includes(a.usage_status):a.custodian_type==='customer'&&Number(a.customer_id)===customer&&a.usage_status==='installed';return eligible&&(!q||[a.display_name,a.barcode_snapshot,a.serial_number,a.external_serial].some(v=>String(v||'').toLowerCase().includes(q)))})}
function renderReplacementSelect(kind,query){const form=document.getElementById('techReplacementForm');if(!form)return;const select=form.querySelector('.tech-allocation-select[data-kind="'+kind+'"]'),current=select.value,rows=replacementCandidates(kind,query),needs=kind==='carried'?!form.elements.technician_id.value:!form.elements.customer_id.value;select.innerHTML='<option value="">'+(needs?(kind==='carried'?'Pilih teknisi terlebih dahulu':'Pilih customer terlebih dahulu'):(rows.length?'Pilih dari '+rows.length+' barang':'Tidak ada barang yang sesuai'))+'</option>'+rows.map(a=>'<option value="'+a.id+'">'+esc(a.display_name)+' · '+esc(a.barcode_snapshot)+(a.serial_number||a.external_serial?' · SN '+esc(a.serial_number||a.external_serial):'')+' · '+qty(a.qty)+' · '+esc(a.ownership_type==='company'?'Milik Perusahaan':'Milik Customer / Non-Persediaan')+'</option>').join('');if(rows.some(a=>String(a.id)===String(current)))select.value=current;else if(current)$(select).trigger('change');const hint=document.getElementById(kind==='carried'?'techOutgoingHint':'techIncomingHint');if(hint)hint.textContent=needs?(kind==='carried'?'Pilih teknisi terlebih dahulu.':'Pilih customer terlebih dahulu.'):(rows.length+' barang ditemukan'+(query?' untuk pencarian ini.':'.'))}
function loadAllocs(){if(!$('.tech-allocation-select').length)return;$.getJSON(base+'teknisi/equipment_allocations').done(r=>{replacementAllocations=r.data||[];renderReplacementSelect('carried','');renderReplacementSelect('installed','')}).fail(()=>toast(false,'Daftar posisi barang gagal dimuat.'))}
function selectReplacementBySearch(kind){const input=document.getElementById(kind==='carried'?'techOutgoingSearch':'techIncomingSearch'),query=input.value.trim();if(!query)return renderReplacementSelect(kind,'');const rows=replacementCandidates(kind,query),exact=rows.filter(a=>[a.barcode_snapshot,a.serial_number,a.external_serial].some(v=>String(v||'').toLowerCase()===query.toLowerCase()));renderReplacementSelect(kind,exact.length===1?'':query);const select=document.querySelector('.tech-allocation-select[data-kind="'+kind+'"]');if(exact.length===1){select.value=exact[0].id;$(select).trigger('change');input.value='';renderReplacementSelect(kind,'')}else if(rows.length===1){select.value=rows[0].id;$(select).trigger('change')}else if(!rows.length)toast(false,'Barang tidak ditemukan pada teknisi/customer yang dipilih.');else{select.focus();toast(true,rows.length+' barang sesuai pencarian. Pilih salah satu dari daftar.')}}
$(document).on('click','.tech-replacement-search',function(){selectReplacementBySearch(this.dataset.kind)});
$('#techOutgoingSearch,#techIncomingSearch').on('keydown',function(e){if(e.key==='Enter'){e.preventDefault();selectReplacementBySearch(this.id==='techOutgoingSearch'?'carried':'installed')}}).on('input',function(){renderReplacementSelect(this.id==='techOutgoingSearch'?'carried':'installed',this.value)});
$('#techReplacementForm [name=technician_id]').on('change',function(){$('#techOutgoingSearch').val('');renderReplacementSelect('carried','')});
$('#techReplacementForm [name=customer_id]').on('change',function(){$('#techIncomingSearch').val('');renderReplacementSelect('installed','')});
$(document).on('change','.tech-allocation-select',function(){const row=replacementAllocations.find(a=>String(a.id)===String(this.value)),kind=this.dataset.kind,info=document.getElementById(kind==='carried'?'techOutgoingInfo':'techIncomingInfo'),amount=document.querySelector('#techReplacementForm [name="'+(kind==='carried'?'outgoing_qty':'incoming_qty')+'"]');if(!row){if(info)info.textContent=kind==='carried'?'Belum ada barang dipilih.':'Bisa milik perusahaan maupun milik customer.';if(amount){amount.readOnly=false;if(kind==='carried')amount.value='';}return}if(info)info.innerHTML='<strong>'+esc(row.ownership_type==='company'?'Milik Perusahaan':'Milik Customer / Non-Persediaan')+'</strong> · '+esc(row.tracking_type)+' · Saldo '+qty(row.qty)+' · '+esc(positionText(row));if(amount){amount.value=String(row.tracking_type).toUpperCase()==='UNIT'?1:Number(row.qty);amount.readOnly=String(row.tracking_type).toUpperCase()==='UNIT';amount.max=row.qty}});
$('#techNewOldItem').on('change',function(){const manual=this.checked;$('#techOldItemFields').toggleClass('d-none',!manual);$('#techReplacementForm [name=incoming_allocation_id]').prop('disabled',manual).prop('required',!manual);$('#techIncomingSearch').prop('disabled',manual);$('#techOldItemFields [name=old_equipment_name],#techOldItemFields [name=old_barcode]').prop('required',manual)});
$('#techReplacementForm [name=incoming_allocation_id]').prop('required',true);
$('#techReplacementForm').on('submit',async function(e){e.preventDefault();if(!(await confirmPost('Posting pemasangan dan penggantian?')))return;const btn=$(this).find('[type=submit]').prop('disabled',true),fd=new FormData(this);fd.set('idempotency_key',randomKey());$.ajax({url:base+'teknisi/process_replacement',method:'POST',data:fd,processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status)return toast(false,r.message);toast(true,r.message);this.reset();$('#techNewOldItem').trigger('change');$('#techOutgoingSearch,#techIncomingSearch').val('');$('#techOutgoingInfo').text('Belum ada barang dipilih.');$('#techIncomingInfo').text('Bisa milik perusahaan maupun milik customer.');$('#techReplacementForm [name=outgoing_qty]').val('').prop('readonly',false).removeAttr('max');$('#techReplacementForm [name=incoming_qty]').val('1').prop('readonly',false).removeAttr('max');clearFileBox($(this).find('[data-filebox]')[0]);loadAllocs()}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Penggantian gagal diproses.')).always(()=>btn.prop('disabled',false))});
function loadInspection(){const body=$('#techInspectionTable tbody');if(!body.length)return;const query=$('#techInspectionFilter').serialize();$.getJSON(base+'teknisi/inspection_data'+(query?'?'+query:'')).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>{const waiting=Number(a.requires_return)===1;let position=waiting?'Masih dibawa teknisi':(a.custodian_type==='customer'?'Gudang customer: '+(a.customer_name||'-'):(a.custodian_type==='warehouse'?'Gudang perusahaan: '+(a.warehouse_name||'-'):(a.warehouse_name||'Karantina gudang')));let action='Menunggu dikembalikan';if(waiting&&perms.return)action='<a class="btn btn-warning btn-sm" href="'+base+'teknisi/bulk_workflow?action=return&technician_id='+encodeURIComponent(a.technician_id||'')+'&scan='+encodeURIComponent(a.barcode_snapshot||'')+'"><i class="bi bi-box-arrow-in-down"></i> Kembalikan</a>';else if(!waiting&&perms.inspect)action='<button class="btn btn-primary btn-sm tech-inspect-row" data-row="'+encodeURIComponent(JSON.stringify(a))+'">Periksa</button>';return'<tr><td><strong>'+esc(a.display_name)+'</strong><br><small>'+esc(a.document_date||'-')+'</small></td><td>'+esc(a.barcode_snapshot)+(a.serial_number?'<br><small>'+esc(a.serial_number)+'</small>':'')+'</td><td>'+esc(labels[a.ownership_type]||a.ownership_type)+'</td><td>'+esc(position)+'</td><td>'+esc(a.technician_name||'-')+'<br><small>'+esc(a.customer_name||'-')+'</small></td><td>'+qty(a.qty)+'</td><td>'+action+'</td></tr>'}).join(''):'<tr><td colspan="7" class="tech-empty">Tidak ada barang yang perlu dikembalikan atau diperiksa.</td></tr>')}).fail(()=>body.html('<tr><td colspan="7" class="tech-empty text-danger">Antrean gagal dimuat.</td></tr>'))}
$('#techInspectionFilter').on('submit',function(e){e.preventDefault();loadInspection()});
$(document).on('click','.tech-inspect-row',function(){const a=JSON.parse(decodeURIComponent(this.dataset.row));scanned={display_name:a.display_name,barcode:a.barcode_snapshot,tracking_type:a.tracking_type,allocations:[a]};openAction('inspect',a.id)});
function loadCustomers(){const body=$('#techCustomerTable tbody');if(!body.length)return;$.getJSON(base+'teknisi/customer_equipment_data').done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td><strong>'+esc(a.customer_name)+'</strong></td><td>'+qty(a.item_count)+'</td><td><strong>'+qty(a.total_qty)+'</strong></td><td>'+qty(a.company_qty)+'</td><td>'+qty(a.customer_qty)+'</td><td>'+qty(a.installed_qty)+'</td><td>'+qty(a.good_qty)+'</td><td><div class="tech-doc-actions"><button class="btn btn-outline-primary btn-sm tech-customer-group" data-id="'+Number(a.customer_id)+'" data-name="'+esc(a.customer_name)+'"><i class="bi bi-eye"></i> Detail</button><button class="btn btn-outline-secondary btn-sm tech-customer-notes" data-id="'+Number(a.customer_id)+'" data-name="'+esc(a.customer_name)+'"><i class="bi bi-card-text"></i> Keterangan</button></div></td></tr>').join(''):'<tr><td colspan="8" class="tech-empty">Belum ada customer yang mempunyai barang tercatat.</td></tr>')}).fail(()=>body.html('<tr><td colspan="8" class="tech-empty text-danger">Ringkasan barang customer gagal dimuat.</td></tr>'))}
let activeCustomerId=0,activeCustomerName='';
function customerPosition(a){if(a.custodian_type==='technician')return'Teknisi: '+(a.technician_name||'-');if(a.custodian_type==='customer')return(a.usage_status==='installed'?'Terpasang di ':'Tersimpan di ')+(a.customer_name||'Customer');if(a.custodian_type==='quarantine')return'Karantina: '+(a.warehouse_name||'-');if(a.custodian_type==='warehouse')return'Gudang perusahaan: '+(a.warehouse_name||'-');if(a.custodian_type==='service_center')return'Pusat perbaikan: '+(a.warehouse_name||'-');return a.custodian_type||'-'}
function loadCustomerGroup(){if(!activeCustomerId)return;const body=$('#techCustomerGroupBody'),status=$('#techCustomerStatus').val()||'all';body.html('<tr><td colspan="7" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat detail...</td></tr>');$.getJSON(base+'teknisi/customer_equipment_customer_data/'+activeCustomerId,{status:status}).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td><strong>'+esc(a.display_name)+'</strong><br><small>'+esc(a.tracking_type)+'</small></td><td>'+esc(a.barcode_snapshot)+'<br><small>'+esc(a.serial_number||'-')+'</small></td><td>'+esc(a.ownership_type==='company'?'Milik Perusahaan':a.ownership_type==='customer'?'Milik Customer':'Perusahaan / Non-Persediaan')+'</td><td>'+qty(a.qty)+'</td><td>'+esc(customerPosition(a))+'</td><td><span class="tech-status">'+esc(labels[a.usage_status]||a.usage_status)+'</span></td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td></tr>').join(''):'<tr><td colspan="7" class="tech-empty">Tidak ada barang sesuai status yang dipilih.</td></tr>')}).fail(x=>body.html('<tr><td colspan="7" class="tech-empty text-danger">'+esc((x.responseJSON&&x.responseJSON.message)||'Detail barang gagal dimuat.')+'</td></tr>'))}
$(document).on('click','.tech-customer-group',function(){activeCustomerId=Number(this.dataset.id);activeCustomerName=this.dataset.name||'Customer';$('#techCustomerGroupTitle').text(activeCustomerName);$('#techCustomerStatus').val('all');modal('techCustomerGroupModal',true);loadCustomerGroup()});
$('#techCustomerApplyStatus').on('click',loadCustomerGroup);$('#techCustomerStatus').on('change',loadCustomerGroup);
$('#techCustomerPdf').on('click',function(){if(!activeCustomerId)return;preview(base+'teknisi/customer_equipment_customer_pdf/'+activeCustomerId+'?status='+encodeURIComponent($('#techCustomerStatus').val()||'all'),'application/pdf')});
$(document).on('click','.tech-customer-notes',function(){const customerId=Number(this.dataset.id),customerName=this.dataset.name||'Customer',body=$('#techCustomerNotesTable tbody');if(!customerId)return;if(customerNotesTable){customerNotesTable.destroy();customerNotesTable=null}$('#techCustomerNotesTitle').text('Log Keterangan · '+customerName);$('#techCustomerNotesPdf').attr('href',base+'teknisi/customer_equipment_notes_pdf/'+customerId);body.html('<tr><td colspan="7" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat log keterangan...</td></tr>');modal('techCustomerNotesModal',true);$.getJSON(base+'teknisi/customer_equipment_notes_data/'+customerId).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td>'+esc(a.document_date)+'<br><small>'+esc(a.document_no)+'</small></td><td><strong>'+esc(a.display_name)+'</strong></td><td>'+esc(a.barcode_snapshot||'-')+'<br><small>SN '+esc(a.serial_number||'-')+'</small></td><td>'+esc(actionLabels[a.document_type]||a.document_type)+'</td><td>'+qty(a.qty)+'</td><td>'+esc(a.technician_name||a.actor_name||'-')+'</td><td class="text-wrap" style="min-width:240px;white-space:pre-wrap">'+esc(a.note_text||'-')+'</td></tr>').join(''):'<tr><td colspan="7" class="tech-empty">Belum ada log keterangan untuk customer ini.</td></tr>');if(rows.length)customerNotesTable=createSearchTable('#techCustomerNotesTable',[[0,'desc']],false)}).fail(x=>body.html('<tr><td colspan="7" class="tech-empty text-danger">'+esc((x.responseJSON&&x.responseJSON.message)||'Log keterangan gagal dimuat.')+'</td></tr>'))});
$('#techCustomerLookupForm').on('submit',function(e){e.preventDefault();const query=$('#techCustomerLookup').val().trim(),target=$('#techCustomerLookupResult');if(!query)return toast(false,'Scan barcode/SN atau isi nama barang customer.');const btn=$(this).find('button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_search',{q:query,mode:'customer'}).done(r=>{const rows=r.data||[];if(!rows.length){target.addClass('d-none').empty();$('#techCustomerItemForm [name=external_barcode]').val(query);document.getElementById('techCustomerItemForm').scrollIntoView({behavior:'smooth',block:'start'});toast(true,'Data lama tidak ditemukan. Barcode sudah dimasukkan ke form baru; lengkapi data wajib saja.');return}target.removeClass('d-none').html('<div class="tech-warning"><strong>Barang sudah pernah dicatat.</strong><br><small>Pilih data berikut; identitas tidak perlu dimasukkan ulang.</small></div><div class="tech-search-results">'+rows.map(row=>'<a class="tech-search-option" href="'+base+'teknisi?scan='+encodeURIComponent(row.lookup_code)+'#scanner"><span><strong>'+esc(row.display_name)+'</strong><small>'+esc(row.barcode)+(row.serial_number?' · SN '+esc(row.serial_number):'')+' · '+esc(row.customer_name||'-')+'</small></span><span><b>Buka Pergerakan</b><small>'+esc(labels[row.ownership_type]||row.ownership_type)+'</small></span></a>').join('')+'</div>')}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Pencarian barang customer gagal.')).always(()=>btn.prop('disabled',false))});
$(document).on('click','.tech-customer-detail',function(){$.getJSON(base+'teknisi/customer_equipment_detail/'+this.dataset.id).done(r=>{const d=r.result,positions=(d.allocations||[]).map(a=>'<tr><td>'+esc(positionText(a))+'</td><td>'+esc(labels[a.usage_status]||a.usage_status)+'</td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td><td>'+qty(a.qty)+'</td></tr>').join(''),files=(perms.attachment?d.attachments||[]:[]).map(f=>'<button class="btn btn-outline-secondary btn-sm tech-attachment" data-url="'+base+'accountingattachments/preview/'+f.id+'" data-mime="'+esc(f.mime_type)+'"><i class="bi '+(f.mime_type==='application/pdf'?'bi-file-pdf':'bi-image')+'"></i> '+esc(f.original_name)+'</button>').join(' ');$('#techDocumentBody').html('<div class="tech-modal-summary"><div><small>Barang</small><strong>'+esc(d.equipment_name)+'</strong><br>'+esc([d.brand,d.model].filter(Boolean).join(' ')||'-')+'</div><div><small>Barcode / Serial</small>'+esc(d.external_barcode)+'<br>'+esc(d.serial_number||'-')+'</div><div><small>Pemilik</small>'+esc(d.owner_name||'-')+'<br>'+esc(labels[d.ownership_type]||d.ownership_type)+'</div></div><p class="mt-3"><strong>Otorisasi penggunaan ulang:</strong> '+(Number(d.deployment_allowed)?'Diizinkan · '+esc(d.authorization_reference||'-'):'Tidak diizinkan')+'</p><div class="tech-table-wrap"><table class="table tech-table"><thead><tr><th>Posisi</th><th>Status</th><th>Kondisi</th><th>Qty</th></tr></thead><tbody>'+positions+'</tbody></table></div><div class="tech-actions"><strong class="w-100">Foto / Dokumen</strong>'+(files||'<span class="text-muted">Tidak ada lampiran atau akses tidak diberikan.</span>')+'</div>');modal('techDocumentModal',true)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Detail barang customer gagal dimuat.'))});
$('#techCustomerItemForm').on('submit',async function(e){e.preventDefault();if(!(await confirmPost('Simpan barang customer?')))return;const form=this,btn=$(form).find('[type=submit]').prop('disabled',true);$.ajax({url:base+'teknisi/register_customer_equipment',method:'POST',data:new FormData(form),processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status)return toast(false,r.message);toast(true,r.message);form.reset();clearFileBox($(form).find('[data-filebox]')[0]);loadCustomers()}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Register gagal disimpan.')).always(()=>btn.prop('disabled',false))});
const selections=new WeakMap();function setupFileBox(box){if(!box)return;const input=box.querySelector('input[type=file]');if(!input||input.dataset.ready)return;input.dataset.ready='1';selections.set(box,[]);input.addEventListener('change',()=>{const all=(selections.get(box)||[]).concat(Array.from(input.files));selections.set(box,all.slice(0,10));syncFiles(box)})}
function syncFiles(box){const input=box.querySelector('input[type=file]'),files=selections.get(box)||[],add=box.querySelector('.tech-add-file');box.querySelectorAll('.tech-thumb').forEach(x=>x.remove());files.forEach((f,i)=>{const el=document.createElement('div');el.className='tech-thumb';const isPdf=f.type==='application/pdf';if(isPdf)el.innerHTML='<div class="tech-pdf"><i class="bi bi-file-pdf fs-2 text-danger"></i></div>';else{const img=document.createElement('img');img.src=URL.createObjectURL(f);img.alt=f.name;el.appendChild(img)}const x=document.createElement('button');x.type='button';x.innerHTML='&times;';x.title='Hapus';x.addEventListener('click',()=>{const next=(selections.get(box)||[]).filter((_,n)=>n!==i);selections.set(box,next);syncFiles(box)});el.appendChild(x);el.addEventListener('click',e=>{if(e.target===x)return;const url=URL.createObjectURL(f);preview(url,f.type)});box.insertBefore(el,add)});const dt=new DataTransfer();files.forEach(f=>dt.items.add(f));input.files=dt.files}
function clearFileBox(box){if(!box)return;selections.set(box,[]);syncFiles(box)}
document.querySelectorAll('[data-filebox]').forEach(setupFileBox);
$(function(){if(Array.isArray(cfg.targetTechnicians)&&$('#techActionForm [name=target_technician_id]').length)$('#techActionForm [name=target_technician_id]').html('<option value="">Pilih karyawan</option>'+cfg.targetTechnicians.map(t=>'<option value="'+Number(t.id)+'">'+esc(t.label)+'</option>').join(''));initEmployeeSearch(document);if($('#techTechniciansTable tbody tr').length&&!$('#techTechniciansTable tbody td[colspan]').length)technicianTable=createSearchTable('#techTechniciansTable',[[0,'asc']]);if(!perms.condition)$('#techActionForm [name=result_status] option[value=damaged],#techActionForm [name=result_status] option[value=lost],#techActionForm [name=result_status] option[value=scrapped]').remove();loadDocuments();loadAllocs();loadInspection();loadCustomers();const initial=new URLSearchParams(location.search).get('scan');if(initial&&$('#techScanCode').length){$('#techScanCode').val(initial);$('#techScanForm').trigger('submit')}});
})(jQuery);