Pilih booking untuk mengisi detail perjalanan secara otomatis.
Preview invoice Pilih booking dan isi jumlah tagihan.
Invoice Tersimpan
No. Invoice
Booking
Agent / Tamu
Tanggal
Jatuh Tempo
Total
Status
Aksi
(function(){
var BOOKING_KEY=’enos-tour-medan-booking-list-v1′,GUEST_KEY=’enos-tour-medan-guest-list-v1′,INVOICE_KEY=’enos-tour-medan-invoice-list-v1′;
var samples=[{id:’b1′,no:1,agent:’Nusantara Holiday’,guestName:’Budi Santoso’,package:’Danau Toba & Samosir’,start:’2026-09-18′,end:’2026-09-20′,adult:4,kid:1,infant:0,partnerItems:[{partner:’Grand Mercure Medan — Hotel’}]},{id:’b2′,no:2,agent:’Wonderful Travel’,guestName:’Siti Nur Aisyah’,package:’Medan – Berastagi – Danau Toba’,start:’2026-09-25′,end:’2026-09-28′,adult:8,kid:2,infant:1,partnerItems:[{partner:’Grand Mercure Medan — Hotel’},{partner:’Restoran Tip Top — Restaurant’}]}];
var $=function(id){return document.getElementById(id)},form=$(‘invoiceForm’),select=$(‘bookingSelect’),lineBody=$(‘lineItems’),bookings=[],invoices=[],editingIndex=-1,editingBooking=null;
function esc(v){return String(v==null?”:v).replace(/[&”‘]/g,function(c){return {‘&’:’&’,”:’>’,'”‘:’"’,”‘”:’'’}[c]})}
function arr(key,fallback){try{var x=JSON.parse(localStorage.getItem(key));return Array.isArray(x)?x:fallback}catch(e){return fallback}}
function persist(){localStorage.setItem(INVOICE_KEY,JSON.stringify(invoices))}
function today(){var d=new Date();return d.getFullYear()+’-‘+String(d.getMonth()+1).padStart(2,’0′)+’-‘+String(d.getDate()).padStart(2,’0′)}
function plusDays(v,n){var d=new Date(v+’T00:00:00′);d.setDate(d.getDate()+n);return d.getFullYear()+’-‘+String(d.getMonth()+1).padStart(2,’0′)+’-‘+String(d.getDate()).padStart(2,’0’)}
var lang=localStorage.getItem(‘mvt-invoice-language’)===’en’?’en’:’id’;
var translations={‘INVOICE MANAGEMENT’:’Manajemen Invoice’,’Daftar Agent’:’Agent List’,’Daftar Booking’:’Booking List’,’Daftar Partner’:’Partner List’,’Voucher’:’Voucher’,’TAGIHAN PERJALANAN’:’TRAVEL BILLING’,’Pilih booking untuk mengisi detail perjalanan secara otomatis.’:’Select a booking to fill in trip details automatically.’,’Buat Invoice’:’Create Invoice’,’Edit Invoice’:’Edit Invoice’,’Pilih Booking *’:’Select Booking *’,’Pilih booking’:’Select booking’,’Pilih booking untuk mengambil data agent, tamu, paket, periode, dan peserta.’:’Select a booking to load agent, guest, package, travel dates, and passenger details.’,’Nomor Invoice *’:’Invoice Number *’,’Tanggal Invoice *’:’Invoice Date *’,’Jatuh Tempo *’:’Due Date *’,’Mata Uang’:’Currency’,’Rincian Tagihan’:’Billing Details’,’Deskripsi’:’Description’,’Pax’:’Pax’,’Price’:’Price’,’Amount’:’Amount’,’+ Tambah Baris’:’+ Add Row’,’Total:’:’Total:’,’Status Pembayaran’:’Payment Status’,’Belum dibayar’:’Unpaid’,’DP dibayar’:’Deposit Paid’,’Lunas’:’Paid in Full’,’Terlambat’:’Overdue’,’Dibatalkan’:’Cancelled’,’Catatan / Rincian’:’Notes / Details’,’Instruksi pembayaran atau rincian tambahan’:’Payment instructions or additional details’,’Simpan Invoice’:’Save Invoice’,’Simpan Perubahan’:’Save Changes’,’Reset’:’Reset’,’Batal Edit’:’Cancel Edit’,’Preview invoice’:’Invoice preview’,’Pilih booking dan isi jumlah tagihan.’:’Select a booking and enter the billing amount.’,’Invoice Tersimpan’:’Saved Invoices’,’No. Invoice’:’Invoice No.’,’Booking’:’Booking’,’Agent / Tamu’:’Agent / Guest’,’Tanggal’:’Date’,’Total’:’Total’,’Status’:’Status’,’Aksi’:’Actions’,’Belum ada invoice tersimpan.’:’No invoices saved yet.’,’Lihat / Cetak’:’View / Print’,’Edit’:’Edit’,’Hapus’:’Delete’,’DESCRIPTION’:’DESCRIPTION’,’PRICE’:’PRICE’,’AMOUNT’:’AMOUNT’,’DITAGIHKAN KEPADA’:’BILLED TO’,’STATUS PEMBAYARAN’:’PAYMENT STATUS’,’KODE BOOKING’:’BOOKING CODE’,’PAKET WISATA’:’TOUR PACKAGE’,’PERIODE PERJALANAN’:’TRAVEL DATES’,’JUMLAH PESERTA’:’PASSENGER COUNT’,’Jatuh Tempo’:’Due Date’,’TOTAL’:’TOTAL’,’TERBILANG’:’IN WORDS’,’Invoice perjalanan wisata’:’Travel Invoice’,’Nomor invoice’:’Invoice number’,’PIC Agent:’:’Agent Contact:’,’Phone:’:’Phone:’,’Email:’:’Email:’,’Address:’:’Address:’,’Adult ‘:’Adult ‘,’ · Kid ‘:’ · Child ‘,’ · Infant ‘:’ · Infant ‘,’PAYMENT TO’:’PAYMENT TO’,’Catatan:’:’Notes:’,’Terima kasih telah memilih PT ENOS TOUR MEDAN.’:’Thank you for choosing PT ENOS TOUR MEDAN.’,’Cantumkan nomor invoice saat pembayaran.’:’Please include the invoice number with your payment.’,’ peserta’:’ passengers’,’Nomor invoice sudah digunakan.’:’This invoice number is already in use.’,’Invoice tersimpan. Gunakan Lihat / Cetak untuk mencetak atau menyimpan sebagai PDF.’:’Invoice saved. Use View / Print to print or save as PDF.’,’Hapus invoice ‘:’Delete invoice ‘};
function tr(s){var keys=Object.keys(translations);if(lang===’id’){keys.forEach(function(k){s=s.split(translations[k]).join(k)});return s}keys.forEach(function(k){s=s.split(k).join(translations[k])});return s}
function localize(root){if(!root)return;var walker=document.createTreeWalker(root,NodeFilter.SHOW_TEXT),node;while(node=walker.nextNode())node.nodeValue=tr(node.nodeValue);Array.from(root.querySelectorAll(‘[placeholder],[aria-label]’)).forEach(function(el){[‘placeholder’,’aria-label’].forEach(function(a){if(el.hasAttribute(a))el.setAttribute(a,tr(el.getAttribute(a)))})})}
function applyLanguage(){lang=$(‘languageSelect’).value===’en’?’en’:’id’;localStorage.setItem(‘mvt-invoice-language’,lang);document.documentElement.lang=lang;localize(document.querySelector(‘main’));localize(document.querySelector(‘.topbar’));localize(lineBody);localize($(‘invoiceRows’));localize($(‘previewWrap’));var statuses=[‘Belum dibayar’,’DP dibayar’,’Lunas’,’Terlambat’,’Dibatalkan’];Array.from($(‘paymentStatus’).options).forEach(function(o,i){o.value=statuses[i]});Array.from($(‘invoiceRows’).querySelectorAll(‘select[data-status]’)).forEach(function(sel){var i=Number(sel.dataset.status);Array.from(sel.options).forEach(function(o,j){o.value=statuses[j];o.selected=statuses[j]===invoices[i].status})})}
function date(v){return v?new Intl.DateTimeFormat(lang===’en’?’en-MY’:’id-ID’,{day:’2-digit’,month:’long’,year:’numeric’}).format(new Date(v+’T00:00:00′)):’—’}
function currency(){return $(‘currency’).value||’MYR’}
function money(v,c){return new Intl.NumberFormat(lang===’en’?’en-MY’:’id-ID’,{style:’currency’,currency:c||currency(),maximumFractionDigits:0}).format(Number(v||0))}
function words(n){var a=[‘nol’,’satu’,’dua’,’tiga’,’empat’,’lima’,’enam’,’tujuh’,’delapan’,’sembilan’,’sepuluh’,’sebelas’];n=Math.floor(Math.abs(Number(n)||0));if(n<12)return a[n];if(n<20)return words(n-10)+' belas';if(n<100)return words(Math.floor(n/10))+' puluh'+(n%10?' '+words(n%10):'');if(n<200)return 'seratus'+(n-100?' '+words(n-100):'');if(n<1000)return words(Math.floor(n/100))+' ratus'+(n%100?' '+words(n%100):'');if(n<2000)return 'seribu'+(n-1000?' '+words(n-1000):'');var scales=[[1e12,' triliun'],[1e9,' miliar'],[1e6,' juta'],[1e3,' ribu']];for(var i=0;i=unit){var q=Math.floor(n/unit),r=n%unit;return words(q)+scales[i][1]+(r?’ ‘+words(r):”)}}return String(n)}
function wordsEn(n){var a=[‘zero’,’one’,’two’,’three’,’four’,’five’,’six’,’seven’,’eight’,’nine’,’ten’,’eleven’,’twelve’,’thirteen’,’fourteen’,’fifteen’,’sixteen’,’seventeen’,’eighteen’,’nineteen’],t=[”,”,’twenty’,’thirty’,’forty’,’fifty’,’sixty’,’seventy’,’eighty’,’ninety’];n=Math.floor(Math.abs(Number(n)||0));if(n<20)return a[n];if(n<100)return t[Math.floor(n/10)]+(n%10?' '+a[n%10]:'');if(n<1000)return a[Math.floor(n/100)]+' hundred'+(n%100?' '+wordsEn(n%100):'');var s=[[1e9,' billion'],[1e6,' million'],[1e3,' thousand']];for(var i=0;i=s[i][0])return wordsEn(Math.floor(n/s[i][0]))+s[i][1]+(n%s[i][0]?’ ‘+wordsEn(n%s[i][0]):”);return String(n)}
function inWords(amount,c){return (lang===’en’?wordsEn(amount)+(c===’IDR’?’ rupiah’:’ Malaysian ringgit’):words(amount)+’ ‘+(c===’IDR’?’rupiah’:’Ringgit Malaysia’))}
function booking(){return bookings.find(function(b){return String(b.id)===select.value})||(editingBooking&&String(editingBooking.id)===select.value?editingBooking:null)} function agentContact(name){return arr(GUEST_KEY,[]).find(function(g){return String(g.agent||”).trim().toLowerCase()===String(name||”).trim().toLowerCase()})||{}}
function partners(b){var p=Array.isArray(b.partnerItems)?b.partnerItems:[];return p.map(function(x){return x.partner}).filter(Boolean).join(‘, ‘)||b.partner||’—’}
function readLines(){return Array.from(lineBody.querySelectorAll(‘tr’)).map(function(row){var desc=row.querySelector(‘[data-desc]’).value.trim(),pax=Number(row.querySelector(‘[data-pax]’).value||0),price=Number(row.querySelector(‘[data-price]’).value||0);return {description:desc,pax:pax,price:price,amount:pax*price}}).filter(function(x){return x.description||x.pax||x.price})}
function total(lines){return lines.reduce(function(sum,x){return sum+Number(x.amount||0)},0)}
function addLine(item){var row=document.createElement(‘tr’);row.innerHTML=’
‘+money(0)+’
‘;if(item){row.querySelector(‘[data-desc]’).value=item.description||”;row.querySelector(‘[data-pax]’).value=item.pax==null?1:item.pax;row.querySelector(‘[data-price]’).value=item.price==null?0:item.price}lineBody.appendChild(row);localize(row)}
function updateLineAmounts(){Array.from(lineBody.querySelectorAll(‘tr’)).forEach(function(row){var pax=Number(row.querySelector(‘[data-pax]’).value||0),price=Number(row.querySelector(‘[data-price]’).value||0);row.querySelector(‘[data-amount]’).textContent=money(pax*price)});$(‘draftTotal’).textContent=money(total(readLines()));preview()}
function refreshBookings(){bookings=arr(BOOKING_KEY,samples);var old=select.value;select.innerHTML=’Pilih booking’+bookings.map(function(b){return ”+String(b.no||”).padStart(3,’0′)+’ — ‘+esc(b.agent)+(b.guestName?’ / ‘+esc(b.guestName):”)+’ — ‘+esc(b.package)+”}).join(”);select.value=old;localize(select);info();preview()}
function info(){var b=booking();$(‘bookingInfo’).innerHTML=b?’‘+esc(b.agent)+’‘+(b.guestName?’ · ‘+esc(b.guestName):”)+’ ‘+esc(b.package)+’ ‘+date(b.start)+’ – ‘+date(b.end)+’ · ‘+(Number(b.adult||0)+Number(b.kid||0)+Number(b.infant||0))+’ peserta’:’Pilih booking untuk mengisi detail perjalanan.’;localize($(‘bookingInfo’))}
function draft(){var b=booking();if(!b)return null;var contact=agentContact(b.agent),lines=readLines();return {number:$(‘invoiceNumber’).value.trim(),bookingId:b.id,bookingNo:b.no,agent:b.agent,guestName:b.guestName||”,agentPic:contact.pic||”,agentPhone:contact.phone||”,agentEmail:contact.email||”,agentAddress:contact.country||contact.address||”,package:b.package,start:b.start,end:b.end,adult:Number(b.adult||0),kid:Number(b.kid||0),infant:Number(b.infant||0),date:$(‘invoiceDate’).value,due:$(‘dueDate’).value,currency:currency(),lineItems:lines,amount:total(lines),status:$(‘paymentStatus’).value,notes:$(‘notes’).value.trim()}}
function show(data){if(!data)return’
Preview invoice Pilih booking dan isi rincian tagihan.
‘:”)+’‘}
function preview(data){$(‘previewWrap’).innerHTML=show(data===undefined?draft():data);localize($(‘previewWrap’))}
function rows(){var el=$(‘invoiceRows’);if(!invoices.length){el.innerHTML=’