Update besar apk finance
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
var nativeFetch = window.fetch.bind(window);
|
||||
|
||||
function toast(text) {
|
||||
var old = document.querySelector('.ajax-error-toast');
|
||||
if (old) old.remove();
|
||||
var element = document.createElement('div');
|
||||
element.className = 'ajax-error-toast';
|
||||
element.setAttribute('role', 'alert');
|
||||
element.textContent = text;
|
||||
document.body.appendChild(element);
|
||||
window.setTimeout(function () { element.remove(); }, 7000);
|
||||
}
|
||||
|
||||
function addCsrf(formData) {
|
||||
var token = window.APP_CSRF;
|
||||
if (token && token.name && !formData.has(token.name)) {
|
||||
formData.append(token.name, token.value);
|
||||
}
|
||||
return formData;
|
||||
}
|
||||
|
||||
function secureFetch(url, options) {
|
||||
options = Object.assign({}, options || {});
|
||||
var method = String(options.method || 'GET').toUpperCase();
|
||||
var token = window.APP_CSRF;
|
||||
var target;
|
||||
try {
|
||||
target = new URL(url, window.location.href);
|
||||
} catch (_error) {
|
||||
target = null;
|
||||
}
|
||||
if (target && target.origin === window.location.origin && !/^(GET|HEAD|OPTIONS)$/.test(method) && token && token.name) {
|
||||
if (options.body instanceof FormData) {
|
||||
addCsrf(options.body);
|
||||
} else if (options.body instanceof URLSearchParams) {
|
||||
options.body = new URLSearchParams(options.body);
|
||||
if (!options.body.has(token.name)) options.body.append(token.name, token.value);
|
||||
}
|
||||
}
|
||||
return nativeFetch(url, options);
|
||||
}
|
||||
|
||||
window.fetch = secureFetch;
|
||||
|
||||
async function request(url, options) {
|
||||
options = options || {};
|
||||
options.headers = Object.assign({
|
||||
Accept: 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}, options.headers || {});
|
||||
if (options.body instanceof FormData) addCsrf(options.body);
|
||||
|
||||
var response;
|
||||
try {
|
||||
response = await window.fetch(url, options);
|
||||
} catch (error) {
|
||||
throw new Error('Tidak dapat terhubung ke server. Periksa jaringan atau Apache.');
|
||||
}
|
||||
|
||||
var contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.indexOf('application/json') === -1) {
|
||||
var raw = await response.text();
|
||||
if (response.redirected || /<(?:html|!doctype)/i.test(raw)) {
|
||||
throw new Error('Sesi berakhir atau server mengirim halaman error. Silakan login ulang.');
|
||||
}
|
||||
throw new Error(raw.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 240) || 'Respons server tidak valid.');
|
||||
}
|
||||
|
||||
var payload = await response.json();
|
||||
if (!response.ok) throw new Error(payload.message || ('Permintaan gagal (HTTP ' + response.status + ').'));
|
||||
return payload;
|
||||
}
|
||||
|
||||
window.App = Object.assign(window.App || {}, {
|
||||
request: request,
|
||||
addCsrf: addCsrf,
|
||||
showError: toast
|
||||
});
|
||||
|
||||
if (window.jQuery) {
|
||||
window.jQuery.ajaxSetup({headers: {'X-Requested-With': 'XMLHttpRequest'}});
|
||||
window.jQuery.ajaxPrefilter(function (options, originalOptions) {
|
||||
var method = String(options.type || options.method || 'GET').toUpperCase();
|
||||
var token = window.APP_CSRF;
|
||||
if (/^(GET|HEAD|OPTIONS)$/.test(method) || !token || !token.name) return;
|
||||
|
||||
if (options.data instanceof FormData) {
|
||||
if (!options.data.has(token.name)) options.data.append(token.name, token.value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof options.data === 'string') {
|
||||
var encodedName = encodeURIComponent(token.name);
|
||||
if (options.data.indexOf(encodedName + '=') === -1) {
|
||||
options.data += (options.data ? '&' : '') + encodedName + '=' + encodeURIComponent(token.value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
options.data = window.jQuery.extend({}, originalOptions.data || options.data || {});
|
||||
if (typeof options.data[token.name] === 'undefined') options.data[token.name] = token.value;
|
||||
});
|
||||
window.jQuery(document).ajaxError(function (_event, xhr, _settings, error) {
|
||||
var message = 'Pemanggilan data gagal';
|
||||
if (xhr.status === 401 || xhr.status === 403) message = 'Sesi atau hak akses tidak valid. Silakan login ulang.';
|
||||
else if (xhr.status) message += ' (HTTP ' + xhr.status + ').';
|
||||
else message += '. Periksa koneksi atau Apache.';
|
||||
toast(error && error !== 'error' ? error : message);
|
||||
});
|
||||
}
|
||||
}(window, document));
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
(function(){'use strict';var d=document,q=function(s,c){return Array.prototype.slice.call((c||d).querySelectorAll(s));};
|
||||
window.cleanNumber=function(v){if(v===null||v===undefined)return'';var s=String(v).trim().replace(/\s|Rp/gi,'');if(!s)return'';var neg=s.charAt(0)==='-';s=s.replace(/-/g,'');var comma=s.lastIndexOf(','),dot=s.lastIndexOf('.');if(comma>=0&&dot>=0){if(comma>dot)s=s.replace(/\./g,'').replace(',','.');else s=s.replace(/,/g,'');}else if(comma>=0){var ca=s.split(',');s=(ca.length===2&&ca[1].length>0&&ca[1].length<=2)?ca[0].replace(/,/g,'')+'.'+ca[1]:s.replace(/,/g,'');}else if(dot>=0){var da=s.split('.');s=(da.length===2&&da[1].length>0&&da[1].length<=2)?s:s.replace(/\./g,'');}s=s.replace(/[^0-9.]/g,'');return(neg?'-':'')+s;};
|
||||
window.parseMoney=function(v){var n=Number(window.cleanNumber(v));return Number.isFinite(n)?n:0;};
|
||||
window.formatNumber=function(v){if(v===null||v===undefined)return'';var raw=String(v).trim().replace(/\s|Rp/gi,''),neg=raw.charAt(0)==='-';raw=raw.replace(/-/g,'').replace(/[^0-9.,]/g,'');if(raw==='')return'';var comma=raw.lastIndexOf(','),dot=raw.lastIndexOf('.'),integer=raw,decimal='',hasDecimal=false;if(comma>=0&&dot>=0){var sep=comma>dot?',':'.',at=Math.max(comma,dot);integer=raw.substring(0,at).replace(/[.,]/g,'');decimal=raw.substring(at+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else if(comma>=0){var ctail=raw.length-comma-1;if(ctail<=2){integer=raw.substring(0,comma).replace(/\D/g,'');decimal=raw.substring(comma+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else integer=raw.replace(/,/g,'');}else if(dot>=0){var dtail=raw.length-dot-1;if(raw.split('.').length===2&&dtail>0&&dtail<=2){integer=raw.substring(0,dot).replace(/\D/g,'');decimal=raw.substring(dot+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else integer=raw.replace(/\./g,'');}integer=(integer||'0').replace(/^0+(?=\d)/,'').replace(/\B(?=(\d{3})+(?!\d))/g,'.');return(neg?'-':'')+integer+(hasDecimal?','+decimal:'');};
|
||||
window.formatRupiah=function(v){return'Rp '+new Intl.NumberFormat('id-ID').format(Number(v)||0);};
|
||||
window.formatTglIndo=function(v){if(!v)return'-';var x=String(v).split(' ')[0].split('-');if(x.length!==3)return v;var b=['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];return Number(x[2])+' '+b[Number(x[1])-1]+' '+x[0];};
|
||||
function filter(v){v=(v||'').toLowerCase();q('.app-menu-item').forEach(function(x){x.hidden=!!v&&x.dataset.menuLabel.indexOf(v)<0;if(v&&x.tagName==='DETAILS'&&!x.hidden)x.open=true;});var box=d.querySelector('.search-menu-results');if(!box)return;box.innerHTML='';q('.app-sidebar .app-menu-item:not([hidden]) a[href]').slice(0,16).forEach(function(a){var n=a.cloneNode(true);n.className='d-block p-2 border-bottom text-decoration-none';box.appendChild(n);});if(!box.children.length){var empty=d.createElement('div');empty.className='empty-state';empty.textContent='Menu tidak ditemukan';box.appendChild(empty);}}
|
||||
q('.menu-filter').forEach(function(x){x.addEventListener('input',function(){filter(x.value);});});
|
||||
function moneyFields(scope){q('input[name],input.format-rupiah',scope||d).forEach(function(x){var n=String(x.name||x.id||'').replace(/\[[^\]]*\]/g,'').toLowerCase();var money=/^(amount|debit|kredit|nominal|jumlah|harga|price|unit_price|estimated_price|discount_amount|opening_balance|statement_balance|outstanding_receipts|outstanding_payments|charge|credit_limit|nilai|nilai_perolehan|nilai_residu|biaya|budget|basic_salary|salary|fixed_amount|installment_amount|principal|residual_value|proceeds)$/.test(n)||/(^|_)(amount|nominal|harga|salary|balance|cost|budget|price|proceeds)$/.test(n);if(!money)return;x.classList.add('format-rupiah');if(String(x.type).toLowerCase()==='number')x.type='text';x.setAttribute('inputmode','decimal');if(x.value)x.value=window.formatNumber(x.value);});}
|
||||
moneyFields(d);q('input[type=number],.format-rupiah').forEach(function(x){x.setAttribute('inputmode',x.step&&String(x.step).indexOf('.')>=0?'decimal':'numeric');});
|
||||
q('img:not([loading])').forEach(function(x){x.loading='lazy';x.decoding='async';});
|
||||
q('.table-responsive table').forEach(function(t){t.classList.add('table-hover','responsive-detail-table');var heads=q('thead th',t).map(function(h){return h.textContent.trim();});q('tbody tr',t).forEach(function(r){q('td',r).forEach(function(c,i){if(!c.dataset.label)c.dataset.label=heads[i]||'';});});});
|
||||
q('form').forEach(function(f){f.addEventListener('submit',function(e){if(e.defaultPrevented||f.dataset.ajax==='true')return;if(f.dataset.submitting==='1'){e.preventDefault();return;}if(!navigator.onLine&&String(f.method).toLowerCase()==='post'){e.preventDefault();alert('Posting transaksi offline tidak diizinkan. Sambungkan internet lalu coba kembali.');return;}f.dataset.submitting='1';q('button[type=submit],input[type=submit]',f).forEach(function(b){b.disabled=true;b.setAttribute('aria-busy','true');});var l=d.getElementById('globalLoading');if(l)l.hidden=false;});});
|
||||
q('input[type=file][accept*="image"]:not([multiple])').forEach(function(input){input.addEventListener('change',function(){var file=input.files&&input.files[0];if(!file||file.size<350000)return;var img=new Image(),url=URL.createObjectURL(file);img.onload=function(){var scale=Math.min(1,1600/Math.max(img.width,img.height)),canvas=d.createElement('canvas');canvas.width=Math.round(img.width*scale);canvas.height=Math.round(img.height*scale);canvas.getContext('2d').drawImage(img,0,0,canvas.width,canvas.height);canvas.toBlob(function(blob){if(blob&&blob.size<file.size&&window.DataTransfer){var dt=new DataTransfer();dt.items.add(new File([blob],file.name.replace(/\.[^.]+$/,'.jpg'),{type:'image/jpeg'}));input.files=dt.files;}URL.revokeObjectURL(url);},'image/jpeg',.82);};img.src=url;});});
|
||||
if(window.jQuery){jQuery(d).on('input','.format-rupiah',function(){this.value=window.formatNumber(this.value);}).on('submit','form',function(){jQuery(this).find('.format-rupiah').each(function(){this.value=window.cleanNumber(this.value);});});var initSelect=function(scope){if(!jQuery.fn.select2)return;jQuery(scope||d).find('select.select-search').each(function(){var x=jQuery(this);if(x.hasClass('select2-hidden-accessible'))return;var p=x.closest('.modal'),lookup=x.data('lookup'),options={width:'100%',placeholder:x.data('placeholder')||x.attr('placeholder')||'-- Pilih --',allowClear:true,dropdownParent:p.length?p:jQuery(document.body)};if(lookup)options.ajax={url:(window.APP_BASE_URL||'/')+'lookups/search/'+lookup,dataType:'json',delay:300,cache:true,data:function(params){return{q:params.term||'',page:params.page||1,limit:20}},processResults:function(data){return data}};x.select2(options);});};initSelect(d);if(window.MutationObserver)new MutationObserver(function(ms){ms.forEach(function(m){Array.prototype.forEach.call(m.addedNodes,function(n){if(n.nodeType===1){moneyFields(n);initSelect(n);}});});}).observe(d.body,{childList:true,subtree:true});}
|
||||
window.addEventListener('pageshow',function(){var l=d.getElementById('globalLoading');if(l)l.hidden=true;q('form').forEach(function(f){delete f.dataset.submitting;q('[aria-busy]',f).forEach(function(b){b.disabled=false;b.removeAttribute('aria-busy');});});});
|
||||
if('serviceWorker'in navigator)window.addEventListener('load',function(){navigator.serviceWorker.register((window.APP_BASE_URL||'/')+'service-worker.js');});}());
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
(function($){
|
||||
'use strict';
|
||||
var cfg=window.RECEIVABLE_PAGE||{},invoices=[],files=[],sourceIndex=0;
|
||||
function esc(v){return $('<div>').text(v==null?'':v).html()}
|
||||
function parseMoney(v){v=String(v==null?'':v).replace(/Rp|\s/g,'');if(v.indexOf(',')>=0)v=v.replace(/\./g,'').replace(',','.');else if(/^\d{1,3}(\.\d{3})+$/.test(v))v=v.replace(/\./g,'');return Number(v)||0}
|
||||
function money(v){return'Rp '+new Intl.NumberFormat('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2}).format(Number(v)||0)}
|
||||
function fmt(v){return new Intl.NumberFormat('id-ID',{minimumFractionDigits:0,maximumFractionDigits:2}).format(Number(v)||0)}
|
||||
function toast(message,icon){if(window.AppAlert)return AppAlert.toast(message,icon||'success');return Swal.fire({toast:true,position:'top-end',icon:icon||'success',title:message,showConfirmButton:false,timer:3200})}
|
||||
function fail(xhr){var m='Permintaan tidak dapat diproses.';try{var r=xhr.responseJSON||JSON.parse(xhr.responseText);m=r.message||m}catch(e){}toast(m,'error')}
|
||||
function select2(selector,url,placeholder){$(selector).select2({width:'100%',placeholder:placeholder,allowClear:true,ajax:{url:cfg.base+url,dataType:'json',delay:300,data:function(p){return{q:p.term||'',page:p.page||1}},processResults:function(r){return{results:r.results||[],pagination:r.pagination||{more:false}}},cache:true}})}
|
||||
function addSource(){sourceIndex++;var row=$('<div class="payment-source-row"><select class="form-select source-account"></select><input class="form-control source-amount" inputmode="decimal" placeholder="Nominal"><button type="button" class="remove-payment-source" aria-label="Hapus sumber"><i class="bi bi-x-lg"></i></button></div>');$('#paymentSources').append(row);select2(row.find('.source-account'),'receivables/lookup_cash_accounts','Pilih akun Aktiva / Passiva');row.find('.source-amount').on('input blur',function(e){if(e.type==='blur')this.value=fmt(parseMoney(this.value));calculate()})}
|
||||
function renderInvoices(rows){invoices=Array.isArray(rows)?rows:[];if(!invoices.length){$('#invoiceAllocations').html('<div class="ar-empty"><i class="bi bi-receipt-cutoff"></i><span>Customer ini tidak memiliki invoice terbuka.</span></div>');$('#invoiceSelectionHint').text('Tidak ada invoice terbuka.');calculate();return}var today=new Date().toISOString().slice(0,10);$('#invoiceAllocations').html(invoices.map(function(i){var overdue=i.jatuh_tempo<today;return'<article class="invoice-allocation '+(overdue?'is-overdue':'')+'"><div class="invoice-allocation-head"><div><b>'+esc(i.no_invoice)+'</b><small>'+esc(i.invoice_type==='running'?'Berjalan / Akumulasi':'Sekali transaksi')+'</small></div><span class="badge '+(overdue?'bg-danger':'bg-warning text-dark')+'">'+(overdue?'Jatuh tempo':'Belum jatuh tempo')+'</span></div><div class="invoice-allocation-meta"><span>Total '+money(i.total)+'</span><span>Terbayar '+money(i.total_bayar)+'</span></div><div class="invoice-allocation-input"><b>Sisa '+money(i.sisa_piutang)+'</b><input class="form-control allocation-amount" data-id="'+Number(i.id)+'" data-balance="'+Number(i.sisa_piutang)+'" inputmode="decimal" value="0" aria-label="Alokasi '+esc(i.no_invoice)+'"></div></article>'}).join(''));$('#invoiceSelectionHint').text(invoices.length+' invoice belum lunas.');$('.allocation-amount').on('input blur',function(e){var value=Math.min(parseMoney(this.value),Number($(this).data('balance')));if(e.type==='blur')this.value=fmt(value);calculate()});if(cfg.focus&&Number(cfg.focus.customer_id)===Number($('#payCustomer').val())){$('.allocation-amount[data-id="'+Number(cfg.focus.id)+'"]').val(fmt(cfg.focus.sisa_piutang));cfg.focus=null}calculate()}
|
||||
function loadCustomer(id){$('#paymentCustomerId').val(id||'');if(!id){renderInvoices([]);return}$('#invoiceAllocations').html('<div class="ar-empty"><span class="spinner-border spinner-border-sm"></span><span>Memuat invoice...</span></div>');$.getJSON(cfg.base+'receivables/open_invoices/'+id).done(function(r){if(!r.status)return toast(r.message,'error');renderInvoices(r.invoices)}).fail(fail);$.getJSON(cfg.base+'receivables/payment_history/'+id).done(function(r){renderHistory(r.payments||[])}).fail(fail)}
|
||||
function renderHistory(rows){if(!rows.length){$('#paymentHistory').html('<div class="ar-empty"><i class="bi bi-clock-history"></i><span>Belum ada riwayat pembayaran customer ini.</span></div>');return}$('#paymentHistory').html('<div class="payment-history-list">'+rows.map(function(p){return'<div class="payment-history-row"><div><b>'+esc(p.payment_no)+'</b><small>'+esc(p.tanggal)+'</small></div><div>'+esc((p.allocations||'Uang muka').replace(/\|\|/g,', '))+'<small>Referensi '+esc(p.reference_no||'-')+' · '+esc(p.creator_name||'-')+'</small></div><b>'+money(p.jumlah)+'</b><button class="btn btn-sm btn-outline-danger preview-receipt" data-url="'+cfg.base+'sales/receipt_pdf/'+p.id+'"><i class="bi bi-file-pdf"></i> Bukti</button></div>'}).join('')+'</div>')}
|
||||
function calculate(){var allocated=0;$('.allocation-amount').each(function(){allocated+=parseMoney(this.value)});var source=0;$('.source-amount').each(function(){source+=parseMoney(this.value)});var diff=source-allocated;$('#allocatedTotal').text(money(allocated));$('#sourceTotal').text(money(source));$('#paymentDifference').text(money(diff)).toggleClass('is-negative',diff<-.001).toggleClass('is-positive',diff>.001);return{allocated:allocated,source:source,difference:diff}}
|
||||
function syncFileInput(){var dt=new DataTransfer();files.forEach(function(f){dt.items.add(f)});document.getElementById('paymentAttachments').files=dt.files;renderFiles()}
|
||||
function renderFiles(){$('#paymentAttachmentGrid .payment-file-tile').remove();var add=$('#addPaymentAttachment');files.forEach(function(f,i){var url=URL.createObjectURL(f),pdf=f.type==='application/pdf';var tile=$('<div class="payment-file-tile" data-index="'+i+'">'+(pdf?'<div class="pdf-icon"><i class="bi bi-file-earmark-pdf"></i></div>':'<img alt="'+esc(f.name)+'" src="'+url+'">')+'<button type="button" class="payment-file-open" aria-label="Pratinjau"></button><small>'+esc(f.name)+'</small><button type="button" class="file-remove" aria-label="Hapus">×</button></div>');tile.data('url',url).data('type',f.type);add.before(tile)})}
|
||||
function preview(url,type){var modal=$('#paymentPreviewModal'),frame=modal.find('iframe'),img=modal.find('img');frame.hide().attr('src','about:blank');img.hide().attr('src','');if(type==='application/pdf')frame.show().attr('src',url);else img.show().attr('src',url);bootstrap.Modal.getOrCreateInstance(modal[0]).show()}
|
||||
$(function(){select2('#payCustomer','receivables/lookup_customers','Cari customer');addSource();if(cfg.focus){var option=new Option(cfg.focus.customer_name,cfg.focus.customer_id,true,true);$('#payCustomer').append(option).trigger('change')}else $('#invoiceSelectionHint').text('Pilih customer terlebih dahulu.')});
|
||||
$('#payCustomer').on('change',function(){loadCustomer(this.value)});$('#addPaymentSource').on('click',addSource);$(document).on('click','.remove-payment-source',function(){if($('.payment-source-row').length===1){$(this).closest('.payment-source-row').find('select').val(null).trigger('change');$(this).closest('.payment-source-row').find('input').val('');calculate();return}$(this).closest('.payment-source-row').remove();calculate()});$('#allocateAll').on('click',function(){$('.allocation-amount').each(function(){this.value=fmt($(this).data('balance'))});calculate()});$('#clearAllocations').on('click',function(){$('.allocation-amount').val('0');calculate()});$('#addPaymentAttachment').on('click',function(){$('#paymentAttachments').trigger('click')});$('#paymentAttachments').on('change',function(){files=files.concat(Array.from(this.files));if(files.length>10){files=files.slice(0,10);toast('Maksimal 10 bukti pembayaran.','warning')}syncFileInput()});$(document).on('click','.file-remove',function(e){e.stopPropagation();files.splice(Number($(this).closest('.payment-file-tile').data('index')),1);syncFileInput()});$(document).on('click','.payment-file-open',function(){var tile=$(this).closest('.payment-file-tile');preview(tile.data('url'),tile.data('type'))});$(document).on('click','.preview-receipt',function(){preview($(this).data('url'),'application/pdf')});
|
||||
$('#paymentPreviewModal').on('hidden.bs.modal',function(){$(this).find('iframe').attr('src','about:blank');$(this).find('img').attr('src','')});
|
||||
$('#paymentForm').on('submit',function(e){e.preventDefault();var customer=$('#payCustomer').val(),allocations={},sources=[];$('.allocation-amount').each(function(){var value=parseMoney(this.value);if(value>0)allocations[$(this).data('id')]=value});$('.payment-source-row').each(function(){var account=$(this).find('.source-account').val(),amount=parseMoney($(this).find('.source-amount').val());if(account&&amount>0)sources.push({account_id:Number(account),amount:amount})});var totals=calculate();if(!customer)return toast('Pilih customer.','warning');if(!sources.length)return toast('Tambahkan minimal satu sumber Kas/Bank dan nominal.','warning');if(!files.length)return toast('Bukti pembayaran wajib ditambahkan.','warning');if(totals.difference<-.001)return toast('Sumber penerimaan lebih kecil dari alokasi invoice.','warning');if(totals.difference>.001&&!$('#allowAdvance').is(':checked'))return toast('Terdapat kelebihan. Centang uang muka customer atau sesuaikan nominal.','warning');var form=this,button=$('#postPayment');var confirm=window.AppAlert?AppAlert.confirm({icon:'question',title:'Post pembayaran?',html:'Penerimaan <b>'+money(totals.source)+'</b> akan dialokasikan sebesar <b>'+money(totals.allocated)+'</b>.',confirmButtonText:'Ya, Post'}):Swal.fire({icon:'question',title:'Post pembayaran?',showCancelButton:true,confirmButtonText:'Ya, Post'});confirm.then(function(r){if(!r.isConfirmed)return;var fd=new FormData(form);fd.set('customer_id',customer);fd.set('sources',JSON.stringify(sources));fd.set('allocations_json',JSON.stringify(allocations));button.prop('disabled',true).html('<span class="spinner-border spinner-border-sm"></span> Memproses...');$.ajax({url:form.action,type:'POST',data:fd,dataType:'json',processData:false,contentType:false}).done(function(x){if(!x.status)return toast(x.message,'error');toast(x.message,'success');if(x.result&&x.result.payment_id)preview(cfg.base+'sales/receipt_pdf/'+x.result.payment_id,'application/pdf');loadCustomer(customer);files=[];syncFileInput();$('.source-amount').val('');calculate()}).fail(fail).always(function(){button.prop('disabled',false).html('<i class="bi bi-check-circle"></i> Post Pembayaran')})})});
|
||||
})(jQuery);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,336 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
const cfg = window.TECH_EQUIPMENT_BULK || {};
|
||||
const base = cfg.base || '/';
|
||||
const form = document.getElementById('techBulkForm');
|
||||
if (!form) return;
|
||||
|
||||
let action = '';
|
||||
let selected = [];
|
||||
let searchResults = [];
|
||||
let technicianHoldings = [];
|
||||
let visibleHoldings = [];
|
||||
let holdingsRequest = null;
|
||||
const fileSelections = new WeakMap();
|
||||
|
||||
const esc = value => $('<div>').text(value == null ? '' : String(value)).html();
|
||||
const number = value => {
|
||||
const n = Number(value || 0);
|
||||
return Number.isInteger(n) ? n.toLocaleString('id-ID') : n.toLocaleString('id-ID', {maximumFractionDigits: 4});
|
||||
};
|
||||
const toast = (ok, message) => {
|
||||
if (window.Swal) Swal.fire({toast: true, position: 'top-end', icon: ok ? 'success' : 'error', title: message, showConfirmButton: false, timer: ok ? 2600 : 4500, timerProgressBar: true});
|
||||
else alert(message);
|
||||
};
|
||||
const key = () => 'TEQ-BATCH-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
||||
|
||||
function modal(id, show) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) return;
|
||||
const instance = bootstrap.Modal.getOrCreateInstance(element, {backdrop: 'static', keyboard: false});
|
||||
show ? instance.show() : instance.hide();
|
||||
}
|
||||
|
||||
function initEmployeeSearch() {
|
||||
if (!$.fn.select2) return;
|
||||
$(form).find('select[name="technician_id"],select[name="target_technician_id"]').each(function () {
|
||||
const select = $(this);
|
||||
if (select.hasClass('select2-hidden-accessible')) return;
|
||||
select.select2({width: '100%', placeholder: select.find('option:first').text() || 'Cari karyawan', allowClear: true});
|
||||
});
|
||||
}
|
||||
|
||||
function setAction(next, resetItems) {
|
||||
action = next;
|
||||
form.elements.action.value = action;
|
||||
form.elements.idempotency_key.value = key();
|
||||
document.querySelectorAll('.tech-bulk-action').forEach(button => button.classList.toggle('active', button.dataset.action === action));
|
||||
const isTransfer = action === 'transfer';
|
||||
const isReturn = action === 'return';
|
||||
$('[data-bulk-field="target"]').toggleClass('d-none', !isTransfer).find('select').prop('required', isTransfer);
|
||||
$('[data-bulk-field="warehouse"]').toggleClass('d-none', !isReturn).find('select').prop('required', false);
|
||||
$('[data-bulk-field="proof"]').toggleClass('d-none', !isReturn);
|
||||
$('[data-tech-label]').text(action === 'handover' ? 'Karyawan Tujuan' : 'Karyawan Asal');
|
||||
form.elements.technician_id.required = true;
|
||||
if (resetItems) { clearItems(); clearFileBox(form.querySelector('[data-filebox]')); }
|
||||
$('#techBulkSearch').attr('placeholder', action === 'handover' ? 'Scan barang gudang atau cari nama barang' : 'Scan barcode/SN atau cari dari daftar barang teknisi');
|
||||
$('#techTechnicianStock').toggleClass('d-none', !(isTransfer || isReturn));
|
||||
if (isTransfer || isReturn) {
|
||||
if (form.elements.technician_id.value) loadTechnicianHoldings();
|
||||
else renderTechnicianHoldings();
|
||||
} else {
|
||||
technicianHoldings = [];
|
||||
visibleHoldings = [];
|
||||
}
|
||||
}
|
||||
|
||||
function clearItems() {
|
||||
selected = [];
|
||||
renderItems();
|
||||
renderTechnicianHoldings();
|
||||
$('#techBulkSearchResults').addClass('d-none').empty();
|
||||
}
|
||||
|
||||
function itemKey(item) {
|
||||
return action === 'handover' ? 'S' + item.subject_id : 'A' + item.allocation_id;
|
||||
}
|
||||
|
||||
function addItem(item, silent) {
|
||||
if (selected.some(row => itemKey(row) === itemKey(item))) {
|
||||
if (!silent) toast(false, 'Barang tersebut sudah ada dalam daftar. Ubah jumlah pada baris yang tersedia.');
|
||||
return false;
|
||||
}
|
||||
if (selected.length >= 200) {
|
||||
if (!silent) toast(false, 'Maksimal 200 baris dalam satu dokumen batch.');
|
||||
return false;
|
||||
}
|
||||
item.qty = String(item.tracking_type).toUpperCase() === 'UNIT' ? 1 : Number(item.available_qty);
|
||||
item.notes = '';
|
||||
selected.push(item);
|
||||
renderItems();
|
||||
$('#techBulkSearch').val('').trigger('focus');
|
||||
renderTechnicianHoldings('');
|
||||
$('#techBulkSearchResults').addClass('d-none').empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderItems() {
|
||||
const body = $('#techBulkItems tbody');
|
||||
if (!selected.length) {
|
||||
body.html('<tr class="tech-bulk-empty"><td colspan="7" class="tech-empty">Belum ada barang yang dipilih.</td></tr>');
|
||||
} else {
|
||||
body.html(selected.map((item, index) => {
|
||||
const unit = String(item.tracking_type).toUpperCase() === 'UNIT';
|
||||
return '<tr data-index="' + index + '">' +
|
||||
'<td><strong>' + esc(item.display_name) + '</strong><br><small>' + esc(item.ownership_type === 'company' ? 'Milik Perusahaan' : 'Barang Customer / Non-Persediaan') + '</small></td>' +
|
||||
'<td>' + esc(item.barcode) + '<br><small>' + esc(item.serial_number || '-') + '</small></td>' +
|
||||
'<td><span class="tech-status">' + esc(item.tracking_type) + '</span></td>' +
|
||||
'<td>' + number(item.available_qty) + '</td>' +
|
||||
'<td>' + (unit ? '<strong>1</strong>' : '<input class="form-control tech-bulk-qty" inputmode="decimal" value="' + esc(item.qty) + '" min="0.0001" max="' + esc(item.available_qty) + '">') + '</td>' +
|
||||
'<td><input class="form-control tech-bulk-note" value="' + esc(item.notes) + '" placeholder="Opsional"></td>' +
|
||||
'<td><button type="button" class="btn btn-outline-danger tech-bulk-remove" title="Hapus baris"><i class="bi bi-x-lg"></i></button></td>' +
|
||||
'</tr>';
|
||||
}).join(''));
|
||||
}
|
||||
$('#techBulkCount').text(selected.length);
|
||||
$('#techBulkSummary').text(selected.length ? selected.length + ' barang siap diposting dalam satu dokumen' : 'Belum ada barang dipilih');
|
||||
$(form).find('[type=submit]').prop('disabled', !selected.length);
|
||||
const needsCompanyWarehouse = action === 'return' && selected.some(item => item.subject_type === 'company_barcode' || item.ownership_type === 'transferred_non_inventory');
|
||||
$('[data-bulk-field="warehouse"]').find('select').prop('required', needsCompanyWarehouse);
|
||||
$('#techWarehouseHint').text(needsCompanyWarehouse ? 'Wajib karena terdapat barang milik perusahaan.' : 'Tidak diperlukan jika seluruh barang adalah milik customer.');
|
||||
}
|
||||
|
||||
const holdingStatus = value => ({carried: 'Sedang Dibawa', removed: 'Hasil Pelepasan', returned: 'Dikembalikan'}[value] || value || '-');
|
||||
|
||||
function renderTechnicianHoldings(query) {
|
||||
const panel = $('#techTechnicianStock');
|
||||
if (!panel.length || action === 'handover') return;
|
||||
panel.removeClass('d-none');
|
||||
const normalized = String(query == null ? $('#techBulkSearch').val() : query).trim().toLowerCase();
|
||||
visibleHoldings = technicianHoldings.filter(item => !normalized || [item.display_name, item.barcode, item.serial_number, item.customer_name, item.usage_status].some(value => String(value || '').toLowerCase().includes(normalized)));
|
||||
const body = $('#techTechnicianStockBody');
|
||||
if (!form.elements.technician_id.value) body.html('<tr><td colspan="6" class="tech-empty">Pilih teknisi asal untuk menampilkan barang.</td></tr>');
|
||||
else if (!technicianHoldings.length) body.html('<tr><td colspan="6" class="tech-empty">Teknisi ini tidak mempunyai barang dalam penguasaan.</td></tr>');
|
||||
else if (!visibleHoldings.length) body.html('<tr><td colspan="6" class="tech-empty">Tidak ada barang yang cocok dengan pencarian.</td></tr>');
|
||||
else body.html(visibleHoldings.map(item => {
|
||||
const checked = selected.some(row => Number(row.allocation_id) === Number(item.allocation_id));
|
||||
return '<tr class="' + (checked ? 'tech-stock-row-selected' : '') + '"><td><input type="checkbox" class="form-check-input tech-stock-check" data-id="' + Number(item.allocation_id) + '" ' + (checked ? 'checked' : '') + '></td><td><strong>' + esc(item.display_name) + '</strong><br><small>' + esc(item.ownership_type === 'company' ? 'Milik Perusahaan' : 'Barang Customer / Non-Persediaan') + (item.customer_name ? ' · ' + esc(item.customer_name) : '') + '</small></td><td>' + esc(item.barcode) + '<br><small>' + esc(item.serial_number || '-') + '</small></td><td><span class="tech-status">' + esc(holdingStatus(item.usage_status)) + '</span><br><small>' + esc(item.condition_status || '-') + '</small></td><td>' + esc(item.tracking_type) + '</td><td><strong>' + number(item.available_qty) + '</strong></td></tr>';
|
||||
}).join(''));
|
||||
const selectedVisible = visibleHoldings.filter(item => selected.some(row => Number(row.allocation_id) === Number(item.allocation_id))).length;
|
||||
const toggle = document.getElementById('techToggleVisible');
|
||||
if (toggle) { toggle.checked = visibleHoldings.length > 0 && selectedVisible === visibleHoldings.length; toggle.indeterminate = selectedVisible > 0 && selectedVisible < visibleHoldings.length; }
|
||||
$('#techTechnicianStockInfo').text((normalized ? visibleHoldings.length + ' dari ' : '') + technicianHoldings.length + ' barang · ' + selected.length + ' dipilih');
|
||||
}
|
||||
|
||||
function loadTechnicianHoldings() {
|
||||
if (action === 'handover') return;
|
||||
const technicianId = Number(form.elements.technician_id.value || 0);
|
||||
technicianHoldings = [];
|
||||
visibleHoldings = [];
|
||||
$('#techTechnicianStock').removeClass('d-none');
|
||||
$('#techTechnicianStockBody').html('<tr><td colspan="6" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat barang teknisi...</td></tr>');
|
||||
$('#techTechnicianStockInfo').text('Memuat daftar...');
|
||||
if (!technicianId) return renderTechnicianHoldings();
|
||||
if (holdingsRequest && holdingsRequest.readyState !== 4) holdingsRequest.abort();
|
||||
holdingsRequest = $.getJSON(base + 'teknisi/equipment_technician_items', {technician_id: technicianId})
|
||||
.done(response => {
|
||||
if (Number(form.elements.technician_id.value || 0) !== technicianId) return;
|
||||
technicianHoldings = response.data || [];
|
||||
renderTechnicianHoldings();
|
||||
const initialQuery = $('#techBulkSearch').val().trim();
|
||||
if (initialQuery) performSearch();
|
||||
})
|
||||
.fail(xhr => {
|
||||
if (xhr.statusText === 'abort') return;
|
||||
$('#techTechnicianStockBody').html('<tr><td colspan="6" class="tech-empty text-danger">Daftar barang teknisi gagal dimuat.</td></tr>');
|
||||
toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Daftar barang teknisi gagal dimuat.');
|
||||
});
|
||||
}
|
||||
|
||||
function renderSearch(rows) {
|
||||
const target = $('#techBulkSearchResults');
|
||||
if (!rows.length) {
|
||||
target.removeClass('d-none').html('<div class="tech-empty py-3">Barang tidak ditemukan atau tidak tersedia pada posisi yang dipilih.</div>');
|
||||
return;
|
||||
}
|
||||
target.removeClass('d-none').html(rows.map((item, index) => '<button type="button" class="tech-search-option" data-index="' + index + '"><span><strong>' + esc(item.display_name) + '</strong><small>' + esc(item.barcode) + (item.serial_number ? ' · SN ' + esc(item.serial_number) : '') + (item.technician_name ? ' · ' + esc(item.technician_name) : '') + '</small></span><span><b>' + number(item.available_qty) + '</b><small>' + esc(item.tracking_type) + '</small></span></button>').join(''));
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
const query = $('#techBulkSearch').val().trim();
|
||||
const technicianId = Number(form.elements.technician_id.value || 0);
|
||||
if ((action === 'transfer' || action === 'return') && !technicianId) return toast(false, 'Pilih teknisi asal terlebih dahulu.');
|
||||
if (action === 'transfer' || action === 'return') {
|
||||
if (!query) { renderTechnicianHoldings(''); return; }
|
||||
const exact = technicianHoldings.filter(item => [item.barcode, item.serial_number].some(value => String(value || '').toLowerCase() === query.toLowerCase()));
|
||||
if (exact.length === 1) addItem(exact[0]);
|
||||
else renderTechnicianHoldings(query);
|
||||
return;
|
||||
}
|
||||
if (!query) return toast(false, 'Scan barcode atau isi kata pencarian.');
|
||||
const button = $('#techBulkSearchButton').prop('disabled', true);
|
||||
$.getJSON(base + 'teknisi/equipment_search', {q: query, mode: action, technician_id: technicianId})
|
||||
.done(response => {
|
||||
searchResults = response.data || [];
|
||||
const exact = searchResults.filter(item => [item.barcode, item.serial_number].some(value => String(value || '').toLowerCase() === query.toLowerCase()));
|
||||
if (exact.length === 1) addItem(exact[0]);
|
||||
else if (searchResults.length === 1) addItem(searchResults[0]);
|
||||
else renderSearch(searchResults);
|
||||
})
|
||||
.fail(xhr => toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Pencarian barang gagal.'))
|
||||
.always(() => button.prop('disabled', false));
|
||||
}
|
||||
|
||||
$('.tech-bulk-action').on('click', function () {
|
||||
if (this.dataset.action === action) return;
|
||||
setAction(this.dataset.action, true);
|
||||
});
|
||||
$('#techBulkSearchButton').on('click', performSearch);
|
||||
$('#techBulkSearch').on('keydown', function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
$('#techBulkSearch').on('input', function () {
|
||||
if (action === 'transfer' || action === 'return') renderTechnicianHoldings(this.value);
|
||||
});
|
||||
$(document).on('click', '.tech-search-option', function () { addItem(searchResults[Number(this.dataset.index)]); });
|
||||
$(document).on('change', '.tech-stock-check', function () {
|
||||
const allocationId = Number(this.dataset.id);
|
||||
const item = technicianHoldings.find(row => Number(row.allocation_id) === allocationId);
|
||||
if (!item) return;
|
||||
if (this.checked) { if (!addItem(item, true)) { this.checked = false; toast(false, 'Maksimal 200 baris dalam satu dokumen batch.'); renderTechnicianHoldings(); } }
|
||||
else { selected = selected.filter(row => Number(row.allocation_id) !== allocationId); renderItems(); renderTechnicianHoldings(); }
|
||||
});
|
||||
$('#techSelectVisible').on('click', function () {
|
||||
let added = 0;
|
||||
for (const item of visibleHoldings) {
|
||||
if (selected.length >= 200) break;
|
||||
if (!selected.some(row => Number(row.allocation_id) === Number(item.allocation_id))) { item.qty = String(item.tracking_type).toUpperCase() === 'UNIT' ? 1 : Number(item.available_qty); item.notes = ''; selected.push(item); added++; }
|
||||
}
|
||||
renderItems();renderTechnicianHoldings();
|
||||
if (visibleHoldings.length > 200 || selected.length >= 200 && visibleHoldings.some(item => !selected.some(row => Number(row.allocation_id) === Number(item.allocation_id)))) toast(false, 'Maksimal 200 baris per dokumen. Sisa barang dapat diproses pada batch berikutnya.');
|
||||
else if (added) toast(true, added + ' barang ditambahkan.');
|
||||
});
|
||||
$('#techToggleVisible').on('change', function () { if (this.checked) $('#techSelectVisible').trigger('click'); else { const visibleIds = new Set(visibleHoldings.map(item => Number(item.allocation_id))); selected = selected.filter(item => !visibleIds.has(Number(item.allocation_id))); renderItems();renderTechnicianHoldings(); } });
|
||||
$('#techClearSelection').on('click', function () { selected = [];renderItems();renderTechnicianHoldings(); });
|
||||
$(document).on('click', '.tech-bulk-remove', function () {
|
||||
selected.splice(Number($(this).closest('tr').data('index')), 1);
|
||||
renderItems();
|
||||
renderTechnicianHoldings();
|
||||
});
|
||||
$(document).on('input', '.tech-bulk-qty', function () { selected[Number($(this).closest('tr').data('index'))].qty = this.value; });
|
||||
$(document).on('input', '.tech-bulk-note', function () { selected[Number($(this).closest('tr').data('index'))].notes = this.value; });
|
||||
$(form.elements.technician_id).on('change', function () {
|
||||
if (action !== 'transfer' && action !== 'return') return;
|
||||
const hadSelection = selected.length > 0;
|
||||
selected = [];renderItems();
|
||||
$('#techBulkSearch').val('');
|
||||
loadTechnicianHoldings();
|
||||
if (hadSelection) toast(true, 'Pilihan barang dikosongkan karena teknisi asal berubah.');
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async function (event) {
|
||||
event.preventDefault();
|
||||
if (!form.reportValidity()) return;
|
||||
if (!selected.length) return toast(false, 'Tambahkan minimal satu barang.');
|
||||
for (const item of selected) {
|
||||
const amount = Number(item.qty);
|
||||
if (!(amount > 0) || amount > Number(item.available_qty) + 0.0001) return toast(false, 'Jumlah untuk ' + item.display_name + ' tidak valid.');
|
||||
}
|
||||
if (action === 'return' && !form.querySelector('input[type=file]').files.length) return toast(false, 'Bukti pengembalian wajib dilampirkan satu kali untuk transaksi batch.');
|
||||
const confirmed = !window.Swal || (await Swal.fire({icon: 'question', title: 'Posting ' + selected.length + ' barang?', text: 'Seluruh barang akan diproses dalam satu dokumen.', showCancelButton: true, confirmButtonText: 'Ya, posting', cancelButtonText: 'Periksa lagi', allowOutsideClick: false})).isConfirmed;
|
||||
if (!confirmed) return;
|
||||
const submit = $(form).find('[type=submit]').prop('disabled', true);
|
||||
const payload = new FormData(form);
|
||||
payload.set('idempotency_key', form.elements.idempotency_key.value || key());
|
||||
payload.set('items_json', JSON.stringify(selected.map(item => ({subject_id: item.subject_id || null, allocation_id: item.allocation_id || null, qty: Number(item.qty), notes: item.notes || ''}))));
|
||||
$.ajax({url: base + 'teknisi/process_equipment_batch', method: 'POST', data: payload, processData: false, contentType: false, dataType: 'json'})
|
||||
.done(response => {
|
||||
if (!response.status) return toast(false, response.message || 'Transaksi batch gagal.');
|
||||
toast(true, response.message);
|
||||
clearItems();
|
||||
form.elements.idempotency_key.value = key();
|
||||
clearFileBox(form.querySelector('[data-filebox]'));
|
||||
if (action === 'transfer' || action === 'return') loadTechnicianHoldings();
|
||||
})
|
||||
.fail(xhr => toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Transaksi batch gagal diproses.'))
|
||||
.always(() => submit.prop('disabled', !selected.length));
|
||||
});
|
||||
|
||||
function setupFileBox(box) {
|
||||
if (!box) return;
|
||||
const input = box.querySelector('input[type=file]');
|
||||
fileSelections.set(box, []);
|
||||
input.addEventListener('change', () => {
|
||||
const files = (fileSelections.get(box) || []).concat(Array.from(input.files)).slice(0, 10);
|
||||
fileSelections.set(box, files);
|
||||
syncFiles(box);
|
||||
});
|
||||
}
|
||||
function syncFiles(box) {
|
||||
if (!box) return;
|
||||
const input = box.querySelector('input[type=file]');
|
||||
const add = box.querySelector('.tech-add-file');
|
||||
const files = fileSelections.get(box) || [];
|
||||
box.querySelectorAll('.tech-thumb').forEach(element => element.remove());
|
||||
files.forEach((file, index) => {
|
||||
const thumb = document.createElement('div');
|
||||
thumb.className = 'tech-thumb';
|
||||
if (file.type === 'application/pdf') thumb.innerHTML = '<div class="tech-pdf"><i class="bi bi-file-pdf fs-2 text-danger"></i></div>';
|
||||
else { const image = document.createElement('img'); image.src = URL.createObjectURL(file); image.alt = file.name; thumb.appendChild(image); }
|
||||
const remove = document.createElement('button');
|
||||
remove.type = 'button'; remove.innerHTML = '×'; remove.title = 'Hapus';
|
||||
remove.addEventListener('click', event => { event.stopPropagation(); fileSelections.set(box, (fileSelections.get(box) || []).filter((_, itemIndex) => itemIndex !== index)); syncFiles(box); });
|
||||
thumb.appendChild(remove);
|
||||
thumb.addEventListener('click', event => { if (event.target === remove) return; preview(URL.createObjectURL(file), file.type); });
|
||||
box.insertBefore(thumb, add);
|
||||
});
|
||||
const transfer = new DataTransfer();
|
||||
files.forEach(file => transfer.items.add(file));
|
||||
input.files = transfer.files;
|
||||
}
|
||||
function clearFileBox(box) { if (!box) return; fileSelections.set(box, []); syncFiles(box); }
|
||||
function preview(url, mime) {
|
||||
const body = document.querySelector('#techBulkPreviewModal [data-preview-body]');
|
||||
body.innerHTML = 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('techBulkPreviewModal', true);
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-filebox]').forEach(setupFileBox);
|
||||
const firstAction = document.querySelector('.tech-bulk-action');
|
||||
const initial = new URLSearchParams(location.search);
|
||||
const requestedAction = initial.get('action');
|
||||
const requestedButton = requestedAction ? document.querySelector('.tech-bulk-action[data-action="' + requestedAction + '"]') : null;
|
||||
if (requestedButton || firstAction) setAction((requestedButton || firstAction).dataset.action, false);
|
||||
if (initial.get('technician_id')) form.elements.technician_id.value = initial.get('technician_id');
|
||||
else if (form.elements.technician_id.options.length === 2) form.elements.technician_id.selectedIndex = 1;
|
||||
initEmployeeSearch();
|
||||
$(form).find('select[name="technician_id"],select[name="target_technician_id"]').trigger('change.select2');
|
||||
if (initial.get('scan')) $('#techBulkSearch').val(initial.get('scan'));
|
||||
if ((action === 'transfer' || action === 'return') && form.elements.technician_id.value) loadTechnicianHoldings();
|
||||
else if (initial.get('scan')) performSearch();
|
||||
renderItems();
|
||||
})(jQuery);
|
||||
@@ -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:'»',previous:'«'}};
|
||||
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='×';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);
|
||||
Reference in New Issue
Block a user