V.AISTUDIO / order-store.js
bep40's picture
Restore order modal info column and formatted product info exports v18
7d3d38f verified
/**
* V.AI STUDIO — Order Store v18
*
* CHANGES from v16:
* - SYNC FROM SERVER: Khi page load → fetch /orders từ bot → merge vào localStorage
* → Đơn từ thiết bị/user khác sẽ xuất hiện trong danh sách!
* - Retry queue vẫn hoạt động (push to server)
*/
(function(){
'use strict';
var STORE_KEY='vai_orders';
var QUEUE_KEY='vai_sync_queue';
var KETOAN_API='https://bep40-vaistudio-ketoan-bot.hf.space';
var KH_LIST=[];var KH_LOADED=false;
// ===== SYNC FROM SERVER (NEW in v17) =====
function resolveOrderItemImage(it){
var img=it.image||it.img||'';if(img)return img;
try{var data=(typeof D!=='undefined'&&D)||window._vaiGetD&&window._vaiGetD()||[];var code=(it.ma||it.model||'').toString().toLowerCase().replace(/[^a-z0-9]/g,'');var name=(it.ten||it.name||'').toString().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9]/g,'');for(var i=0;i<data.length;i++){var p=data[i]||{};var pc=((p.sku||p.mod||p.model||'')+' '+(p.slug||'')).toLowerCase().replace(/[^a-z0-9]/g,'');var pn=(p.name||p.n||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9]/g,'');if((code&&pc.indexOf(code)!==-1)||(name&&pn.indexOf(name.slice(0,20))!==-1))return p.image||p.img||p.i||'';}}
catch(e){}
return '';
}
function syncFromServer(){
fetch(KETOAN_API+'/orders',{method:'GET'}).then(function(r){return r.json();}).then(function(d){
var serverOrders=d.data||[];
if(!serverOrders.length)return;
var localOrders=getOrders();
var localCodes={};
localOrders.forEach(function(o){if(o.code)localCodes[o.code]=true;});
var added=0;
serverOrders.forEach(function(so){
var code=so.ma_don||so.code||'';
if(!code)return;
var existingIdx=localOrders.findIndex(function(o){return o.code===code;});
if(existingIdx>=0 && !so.overwrite && !so.savedAt)return;
// Convert server format to local format
var order={
code:code,
customer:so.khach||so.customer||'',
phone:so.dien_thoai||so.phone||'',
email:so.email||'',
addr:so.dia_chi||so.addr||'',
date:so.date||so.ngay||'',
items:(so.items||[]).map(function(it){return{name:it.ten||it.name||'',model:it.ma||it.model||'',brand:it.brand||'',image:resolveOrderItemImage(it),specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),note:it.note||'',qty:it.sl||it.qty||1,price:it.listPrice||it.originalPrice||it.price||it.gia_goc||it.gia_niem_yet||it.gia_ban||0,discPrice:it.discPrice||it.gia_ban||it.price||0,total:(it.gia_ban||it.price||0)*(it.sl||it.qty||1)};}),
fees:so.fees||[],
deposit:so.deposit||0,
discountPercent:so.discountPercent||0,
grandTotal:so.grandTotal||0,
remaining:so.remaining||0,
status:so.status||(so.confirmedAt?'confirmed':'pending'),
savedAt:so.savedAt||so.confirmedAt||'',
confirmedAt:so.confirmedAt||'',
ma_kh:so.ma_kh||''
};
if(existingIdx>=0)localOrders[existingIdx]=order;else localOrders.unshift(order);
localCodes[code]=true;
added++;
});
if(added>0){
saveOrders(localOrders);
console.log('[OS v18] ☁️ Synced '+added+' orders from server. Total local: '+localOrders.length);
}
}).catch(function(e){
console.log('[OS v18] Server sync skip (bot may be sleeping)');
});
}
// Sync from server 3s after page load (give bot time to wake up)
setTimeout(syncFromServer,3000);
// Also sync every 60s
setInterval(syncFromServer,60000);
// ===== RETRY QUEUE =====
function getQueue(){try{return JSON.parse(localStorage.getItem(QUEUE_KEY)||'[]');}catch(e){return[];}}
function saveQueue(q){localStorage.setItem(QUEUE_KEY,JSON.stringify(q));}
function addToQueue(endpoint,payload){var q=getQueue();q.push({endpoint:endpoint,payload:payload,attempts:0,addedAt:Date.now()});saveQueue(q);}
function processQueue(){
var q=getQueue();if(!q.length)return;
var item=q[0];
fetch(KETOAN_API+item.endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(item.payload)})
.then(function(r){if(r.ok){q.shift();saveQueue(q);console.log('[OS v18] Queue sync OK: '+item.endpoint+' ('+q.length+' remaining)');if(q.length)setTimeout(processQueue,2000);}else throw new Error('HTTP '+r.status);})
.catch(function(){item.attempts=(item.attempts||0)+1;if(item.attempts>50){q.shift();}saveQueue(q);});
}
setInterval(processQueue,30000);
setTimeout(processQueue,5000);
function syncOrderToBot(order){
var payload={ma_don:order.code,khach:order.customer||'',dien_thoai:order.phone||'',dia_chi:order.addr||'',items:(order.items||[]).map(function(it){return{ma:it.model||'',model:it.model||'',ten:it.name||'',name:it.name||'',brand:it.brand||'',image:resolveOrderItemImage(it),img:it.image||it.img||'',specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),sl:it.qty||1,qty:it.qty||1,gia_ban:it.discPrice||it.price||0,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0};}),fees:order.fees||[],grandTotal:order.grandTotal||0,deposit:order.deposit||0,remaining:order.remaining||0,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},email:order.email||'',date:order.date||'',status:order.status||'pending',savedAt:order.savedAt||new Date().toISOString()};
if(order.confirmedAt)payload.confirmedAt=order.confirmedAt;
fetch(KETOAN_API+'/order-saved',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
.then(function(r){if(!r.ok)throw new Error();return r.json();})
.catch(function(){addToQueue('/order-saved',payload);});
}
function sendToKetoanBot(order){
var payload={ma_don:order.code,khach:order.customer||'',dien_thoai:order.phone||'',dia_chi:order.addr||'',items:(order.items||[]).map(function(it){return{ma:it.model||'',model:it.model||'',ten:it.name||'',name:it.name||'',brand:it.brand||'',image:resolveOrderItemImage(it),img:it.image||it.img||'',specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),sl:it.qty||1,qty:it.qty||1,gia_ban:it.discPrice||it.price||0,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0};}),fees:order.fees||[],grandTotal:order.grandTotal||0,deposit:order.deposit||0,remaining:order.remaining||0,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},confirmedAt:order.confirmedAt||new Date().toISOString(),email:order.email||'',date:order.date||''};
fetch(KETOAN_API+'/order-confirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
.then(function(r){if(!r.ok)throw new Error();return r.json();})
.then(function(d){if(d.ok&&typeof showToast==='function')showToast('📨 Đã gửi kế toán!');})
.catch(function(){addToQueue('/order-confirmed',payload);if(typeof showToast==='function')showToast('⏳ Bot đang khởi động, sẽ tự gửi lại...');});
}
function loadKhachHang(){if(KH_LOADED)return Promise.resolve(KH_LIST);return fetch(KETOAN_API+'/khachhang',{method:'GET'}).then(function(r){return r.json();}).then(function(d){KH_LIST=d.data||[];KH_LOADED=true;return KH_LIST;}).catch(function(){KH_LOADED=true;return KH_LIST;});}
setTimeout(loadKhachHang,1000);
function findKH(query){if(!query||query.length<1)return[];var q=query.toLowerCase();return KH_LIST.filter(function(kh){return(kh.ma_kh||'').toLowerCase().includes(q)||(kh.ten||'').toLowerCase().includes(q)||(kh.sdt||'').includes(q);});}
function getKHCKRate(kh,brand){if(!kh||!brand)return null;var b=brand.toLowerCase().replace(/\s/g,'');var codes=kh.ck_codes||[];for(var i=0;i<codes.length;i++){var c=codes[i].toLowerCase().replace(/\s/g,'');if(c.includes(b)){var m=c.match(/ck(\d+)/);if(m)return parseInt(m[1]);}}return null;}
function getOrders(){try{return JSON.parse(localStorage.getItem(STORE_KEY)||'[]');}catch(e){return[];}}
function saveOrders(orders){localStorage.setItem(STORE_KEY,JSON.stringify(orders));}
function addOrder(order){var orders=getOrders();var idx=orders.findIndex(function(o){return o.code===order.code;});if(idx>=0)orders[idx]=order;else orders.unshift(order);saveOrders(orders);return orders;}
function deleteOrder(code){saveOrders(getOrders().filter(function(o){return o.code!==code;}));}
function searchOrders(q,statusFilter){var all=getOrders();if(statusFilter&&statusFilter!=='all')all=all.filter(function(o){return(o.status||'pending')===statusFilter;});if(!q||!q.trim())return all;var s=q.toLowerCase().trim();return all.filter(function(o){return(o.code||'').toLowerCase().includes(s)||(o.customer||'').toLowerCase().includes(s)||(o.phone||'').includes(s);});}
function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
function formatInfoVal(v){
if(v==null||v==='')return '';
if(typeof v==='string'){
var t=v.trim();
if(!t||t==='{}'||t==='[]')return '';
if((t[0]==='{'&&t[t.length-1]==='}')||(t[0]==='['&&t[t.length-1]===']')){try{return formatInfoVal(JSON.parse(t));}catch(e){return t;}}
return t;
}
if(Array.isArray(v))return v.map(formatInfoVal).filter(Boolean).join('; ');
if(typeof v==='object'){
var out=[];Object.keys(v).forEach(function(k){var val=v[k];if(val==null||val===''||(typeof val==='object'&&!Array.isArray(val)&&!Object.keys(val).length))return;out.push(k+': '+formatInfoVal(val));});
return out.join('; ');
}
return String(v);
}
function orderItemInfo(it){it=it||{};return formatInfoVal(it.info||it.thong_tin||it.dim_info||it.dimension_info||it.specs||it.summary||it.desc||'');}
function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');}
function itemKeys(it){var arr=[];['model','sku','code','ma','name'].forEach(function(k){if(it&&it[k])arr.push(compactKey(it[k]));});if(it&&it.name){String(it.name).split('|').forEach(function(x){arr.push(compactKey(x));});}return arr.filter(function(x){return x&&x.length>=3;});}
function matchItemDiscount(it,itemDiscounts){itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);for(var i=0;i<keys.length;i++){if(itemDiscounts[keys[i]])return itemDiscounts[keys[i]];}for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var k=keys[j];if(code.length>=3&&k.length>=3&&(k.indexOf(code)!==-1||code.indexOf(k)!==-1))return itemDiscounts[code];}}return 0;}
function parseItemDiscountLine(line,map){var lo=(line||'').toString().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d').toLowerCase();var re1=/\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*(?:[:=\-]|\s+)?\s*(?:ck|chiet\s*khau|giam|discount)\s*([\d.,]+)\s*%?/gi;var re2=/(?:ck|chiet\s*khau|giam|discount)\s*\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*([\d.,]+)\s*%?/gi;var m;while((m=re1.exec(lo))!==null){var v=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v>0&&v<=100)map[compactKey(m[1])]=v;}while((m=re2.exec(lo))!==null){var v2=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v2>0&&v2<=100)map[compactKey(m[1])]=v2;}}
function esc(s){return(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function parsePrompt(text){
var fees=[],deposit=0,discountPercent=0,note='',itemDiscounts={};
if(!text)return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
text.split(/[,;\n]+/).forEach(function(line){
line=line.trim();if(!line)return;
parseItemDiscountLine(line,itemDiscounts);
if(Object.keys(itemDiscounts).length&&line.match(/\b[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d[A-Za-z0-9._\-\/]*\b/i)&&line.match(/\b(ck|chiết khấu|chiet khau|giảm|giam|discount)\b/i))return;
var lo=line.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d');
// Ghi chú
if(lo.match(/^(ghi chu|note|luu y)/)){note=line.replace(/^(ghi chú|ghi chu|note|lưu ý|luu y)[:\s]*/i,'').trim();return;}
// CK
var ck=lo.match(/^(ck|chiet khau|giam|discount)\s*([\d.,]+)\s*(%|k|tr)?/);
if(ck){var v=parseFloat(ck[2].replace(/\./g,'').replace(',','.'));if((!ck[3]||ck[3]===''||ck[3]==='%')&&v>0&&v<=100)discountPercent=v;return;}
// Cọc
if(lo.match(/coc|dat coc|deposit/)){var dm=lo.match(/([\d.,]+)\s*(k|tr|trieu|%)?/);if(dm){var a=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var u2=(dm[2]||'').toLowerCase();if(u2==='%'){deposit=a;/* percent handled separately */}else{if(u2==='k')a*=1000;if(u2==='tr'||u2==='trieu')a*=1000000;deposit=a;}}return;}
// Phụ phí: giao hàng, lắp đặt, vận chuyển, phí khác
var feeMatch=lo.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi giao|phi lap|phi khac|phu phi|phi)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
if(feeMatch){
var amt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.'));
var u=(feeMatch[3]||'').toLowerCase();
if(u==='k'||u==='nghin')amt*=1000;else if(u==='tr'||u==='trieu'||u==='m')amt*=1000000;else if(amt>0&&amt<500)amt*=1000;
var labelMap={'giao hang':'Phí giao hàng','giao':'Phí giao hàng','lap dat':'Phí lắp đặt','lap':'Phí lắp đặt','van chuyen':'Phí vận chuyển','ship':'Phí vận chuyển','phi giao':'Phí giao hàng','phi lap':'Phí lắp đặt','phi khac':'Phí khác','phu phi':'Phụ phí','phi':'Phụ phí'};
fees.push({label:labelMap[feeMatch[1]]||'Phụ phí',amount:amt});
return;
}
// Fallback: line with number
var m=lo.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
if(m){var amt2=parseFloat(m[1].replace(/\./g,'').replace(',','.'));var u3=(m[2]||'').toLowerCase();if(u3==='k'||u3==='nghin')amt2*=1000;if(u3==='tr'||u3==='trieu'||u3==='m')amt2*=1000000;if(amt2>0){var lb=line.replace(m[0],'').trim();if(!lb||lb.length<2)lb='Phụ phí';fees.push({label:lb,amount:amt2});}}
});
return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
}
function applyDiscount(order){var pct=order.discountPercent||0;var itemDiscounts=order.itemDiscounts||{};(order.items||[]).forEach(function(it){var base=it.price||0;var ip=matchItemDiscount(it,itemDiscounts);if(ip>0)it.discPrice=Math.round(base*(1-ip/100));else if(pct>0)it.discPrice=Math.round(base*(1-pct/100));if(!it.discPrice)it.discPrice=base;it.total=(it.discPrice||base)*(it.qty||1);});}
function recalcOrder(order){applyDiscount(order);var pt=0;(order.items||[]).forEach(function(it){pt+=it.total||0;});var sc=0;(order.fees||[]).forEach(function(f){sc+=(f.amount||0);});order.grandTotal=pt+sc;order.remaining=order.grandTotal-(order.deposit||0);if(order.remaining<0)order.remaining=0;}
function saveCurrentOrder(){if(!window.VAI_QR||!window.VAI_QR.getData)return;var d=window.VAI_QR.getData();var qd=d.qd;var code=window.VAI_QR.getEffectiveOrderCode();var order={code:code,customer:qd.customer.name||'',phone:qd.customer.phone||'',email:qd.customer.email||'',addr:qd.customer.addr||'',date:qd.customer.date||new Date().toLocaleDateString('vi-VN'),items:qd.items||[],fees:d.fees||[],deposit:d.deposit||0,discountPercent:d.discountPercent||0,itemDiscounts:d.itemDiscounts||{},grandTotal:d.grandTotal||0,remaining:d.remaining||0,promptText:window.VAI_QR.getPromptText?window.VAI_QR.getPromptText():'',status:'pending',savedAt:new Date().toISOString()};addOrder(order);syncOrderToBot(order);if(typeof showToast==='function')showToast('✅ Đã lưu đơn '+code);}
function confirmOrder(order){order.status='confirmed';order.confirmedAt=new Date().toISOString();addOrder(order);sendToKetoanBot(order);}
function unconfirmOrder(order){order.status='pending';delete order.confirmedAt;addOrder(order);fetch(KETOAN_API+'/order-unconfirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ma_don:order.code})}).catch(function(){});}
function orderToVAIData(order){recalcOrder(order);return{qd:{customer:{name:order.customer||'',phone:order.phone||'',email:order.email||'',addr:order.addr||'',date:order.date||''},items:(order.items||[]).map(function(it,i){return{stt:i+1,image:it.image||'',name:it.name||'',model:it.model||'',specs:orderItemInfo(it),qty:it.qty||1,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0,total:it.total||0,note:it.note||''};}),grandTotal:order.grandTotal||0},fees:order.fees||[],notes:[],discount:null,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},deposit:order.deposit||0,productTotal:order.grandTotal-(order.fees||[]).reduce(function(s,f){return s+(f.amount||0);},0),grandTotal:order.grandTotal||0,remaining:order.remaining||0};}
function _orderBW(opt){try{return !!(opt&&opt.bw)||!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)||!!(document.getElementById('vai-bw-export')&&document.getElementById('vai-bw-export').checked);}catch(e){return !!(opt&&opt.bw);}}
async function exportPDF(order,opt){if(window.VAI_QR&&window.VAI_QR.exportPDF){var d=orderToVAIData(order);await window.VAI_QR.exportPDF(d,order.code,_orderBW(opt));}}
async function exportExcel(order,opt){if(typeof ExcelJS==='undefined')return;if(window.VAI_QR&&window.VAI_QR.exportExcel){var d=orderToVAIData(order),qd=d.qd,qrUrl=window.VAI_QR.getQRUrl(order.deposit>0?order.remaining:order.grandTotal,order.code);await window.VAI_QR.exportExcel(d,qd,order.code,qrUrl,_orderBW(opt));}}
async function exportDeliveryPDF(order,opt){if(window.VAI_QR&&window.VAI_QR.exportDeliveryPDF){var qd=orderToVAIData(order).qd;await window.VAI_QR.exportDeliveryPDF(qd,order.code,_orderBW(opt));}}
async function exportDeliveryExcel(order,opt){if(typeof ExcelJS==='undefined')return;if(window.VAI_QR&&window.VAI_QR.exportDeliveryExcel){var qd=orderToVAIData(order).qd;await window.VAI_QR.exportDeliveryExcel(qd,order.code,_orderBW(opt));}}
function openSearchModal(){var old=document.getElementById('vai-order-search-modal');if(old)old.remove();var orders=getOrders();var pC=orders.filter(function(o){return(o.status||'pending')==='pending';}).length;var cC=orders.filter(function(o){return o.status==='confirmed';}).length;var cf='all';var ov=document.createElement('div');ov.id='vai-order-search-modal';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;display:flex;align-items:flex-start;justify-content:center;padding:40px 16px;overflow-y:auto';ov.onclick=function(e){if(e.target===ov)ov.remove();};ov.innerHTML='<div style="background:#fff;border-radius:16px;max-width:750px;width:100%;max-height:85vh;overflow:hidden;display:flex;flex-direction:column"><div style="padding:14px 18px;background:#003f62;display:flex;align-items:center"><div style="font-size:16px;font-weight:800;color:#fff;flex:1">📋 Đơn hàng ('+orders.length+')</div><button id="vai-os-refresh" style="background:rgba(255,255,255,.2);border:none;color:#fff;padding:4px 10px;border-radius:6px;cursor:pointer;font-size:11px;margin-right:8px">🔄</button><button id="vai-os-close" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer">✕</button></div><div style="padding:10px 18px;border-bottom:1px solid #e2e8f0"><div style="display:flex;gap:6px;margin-bottom:8px"><button class="os-filter" data-f="all" style="padding:5px 12px;border-radius:20px;border:2px solid #003f62;background:#003f62;color:#fff;font-size:11px;font-weight:700;cursor:pointer">Tất cả ('+orders.length+')</button><button class="os-filter" data-f="pending" style="padding:5px 12px;border-radius:20px;border:2px solid #f59e0b;background:#fffbeb;color:#92400e;font-size:11px;font-weight:700;cursor:pointer">⏳ ('+pC+')</button><button class="os-filter" data-f="confirmed" style="padding:5px 12px;border-radius:20px;border:2px solid #16a34a;background:#f0fdf4;color:#166534;font-size:11px;font-weight:700;cursor:pointer">✅ ('+cC+')</button></div><input id="vai-os-q" placeholder="Tìm mã đơn, tên KH, SĐT..." style="width:100%;padding:9px;border:2px solid #e2e8f0;border-radius:8px;font-size:12px;box-sizing:border-box"></div><div id="vai-os-r" style="flex:1;overflow-y:auto;padding:10px 18px"></div></div>';document.body.appendChild(ov);var re=document.getElementById('vai-os-r');function rl(ls){if(!ls.length){re.innerHTML='<div style="text-align:center;padding:30px;color:#94a3b8">Trống</div>';return;}re.innerHTML=ls.map(function(o,i){var isC=o.status==='confirmed';return'<div class="oi" data-i="'+i+'" style="padding:10px;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:6px;cursor:pointer;border-left:3px solid '+(isC?'#16a34a':'#f59e0b')+'"><div style="display:flex;justify-content:space-between"><div><b style="color:#003f62">'+o.code+'</b> '+(isC?'✅':'⏳')+'</div><button class="od" data-c="'+esc(o.code)+'" style="background:#fee2e2;border:none;color:#dc2626;padding:2px 6px;border-radius:4px;cursor:pointer;font-size:10px">🗑</button></div><div style="font-size:11px;color:#64748b;margin-top:3px">'+esc(o.customer)+' • '+fmt(o.grandTotal)+'</div></div>';}).join('');re.onclick=function(e){var d=e.target.closest('.od');if(d){e.stopPropagation();if(confirm('Xóa?')){deleteOrder(d.dataset.c);df();}return;}var it=e.target.closest('.oi');if(it){ov.remove();openOrderDetail(ls[parseInt(it.dataset.i)]);}};}function df(){rl(searchOrders(document.getElementById('vai-os-q').value,cf));}rl(orders);document.getElementById('vai-os-close').onclick=function(){ov.remove();};document.getElementById('vai-os-q').oninput=df;
// Refresh button — force sync from server
document.getElementById('vai-os-refresh').onclick=function(){
this.textContent='⏳';var self=this;
syncFromServer();
setTimeout(function(){self.textContent='🔄';ov.remove();openSearchModal();},2000);
};
ov.querySelectorAll('.os-filter').forEach(function(b){b.onclick=function(e){e.stopPropagation();cf=this.dataset.f;ov.querySelectorAll('.os-filter').forEach(function(x){x.style.background='#fff';x.style.color='#003f62';});this.style.background='#003f62';this.style.color='#fff';df();};});}
function openOrderDetail(order){var old=document.getElementById('vai-order-detail-modal');if(old)old.remove();order=JSON.parse(JSON.stringify(order));var ov=document.createElement('div');ov.id='vai-order-detail-modal';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;display:flex;align-items:flex-start;justify-content:center;padding:16px;overflow-y:auto';ov.onclick=function(e){if(e.target===ov)ov.remove();};document.body.appendChild(ov);var modal=document.createElement('div');modal.style.cssText='background:#fff;border-radius:16px;max-width:750px;width:100%;max-height:92vh;overflow-y:auto';ov.appendChild(modal);
function render(){recalcOrder(order);var isC=order.status==='confirmed';
var h='<div style="padding:12px 18px;background:#003f62;border-radius:16px 16px 0 0;display:flex;justify-content:space-between;align-items:center"><div><b style="color:#fff">📄 '+esc(order.code)+'</b> '+(isC?'<span style="background:#dcfce7;color:#16a34a;padding:2px 8px;border-radius:6px;font-size:10px;margin-left:8px">✅ Đã chốt</span>':'<span style="background:#fef9c3;color:#92400e;padding:2px 8px;border-radius:6px;font-size:10px;margin-left:8px">⏳</span>')+'</div><button id="xc" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer">✕</button></div><div style="padding:14px 18px">'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:11px;margin-bottom:10px;padding:10px;background:#f8fafc;border-radius:8px"><div><b>KH:</b> <input id="od-name" value="'+esc(order.customer||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:55%;font-size:11px"></div><div><b>SĐT:</b> <input id="od-phone" value="'+esc(order.phone||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:50%;font-size:11px"></div><div style="grid-column:1/-1"><b>ĐC:</b> <input id="od-addr" value="'+esc(order.addr||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:85%;font-size:11px"></div></div>'
+'<div style="margin-bottom:10px;padding:8px 10px;background:#eff6ff;border-radius:8px;border:1px solid #bfdbfe;position:relative"><div style="font-size:10px;font-weight:700;color:#1e40af;margin-bottom:4px">👤 Mã KH</div><input id="od-makh" value="'+esc(order.ma_kh||'')+'" placeholder="Mã KH hoặc tên..." style="width:100%;padding:6px 10px;border:1px solid #93c5fd;border-radius:6px;font-size:11px;box-sizing:border-box"><div id="od-makh-suggest" style="position:absolute;left:10px;right:10px;top:58px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;max-height:100px;overflow-y:auto;display:none;z-index:10;box-shadow:0 4px 12px rgba(0,0,0,.1)"></div><div id="od-makh-info" style="font-size:9px;margin-top:3px;color:#64748b">'+(order.ma_kh?'✅ '+order.ma_kh:'')+'</div></div>'
+'<table style="width:100%;border-collapse:collapse;font-size:11px;margin-bottom:8px"><thead><tr style="background:#003f62;color:#fff"><th style="padding:5px">#</th><th style="padding:5px">Ảnh</th><th style="padding:5px;text-align:left">SP</th><th style="padding:5px;text-align:left">Thông tin</th><th style="padding:5px">SL</th><th style="padding:5px;text-align:right">Giá niêm yết</th><th style="padding:5px;text-align:right">Giá CK</th><th style="padding:5px;text-align:right">TT</th><th></th></tr></thead><tbody>';
(order.items||[]).forEach(function(it,i){var ck=it.discPrice&&it.price&&it.discPrice<it.price;h+='<tr style="border-bottom:1px solid #f1f5f9"><td style="padding:3px;text-align:center">'+(i+1)+'</td><td style="padding:3px">'+(it.image?'<img src="'+it.image+'" style="width:40px;height:40px;object-fit:contain;border-radius:4px" referrerpolicy="no-referrer" onerror="this.style.display=\'none\'">':'')+'</td><td style="padding:3px"><b style="font-size:10px">'+esc((it.name||'').substring(0,28))+'</b><br><span style="font-size:9px;color:#64748b">'+esc(it.model||'')+'</span></td><td style="padding:3px;font-size:9px;color:#64748b;max-width:150px;white-space:normal">'+esc(orderItemInfo(it)).substring(0,260)+'</td><td style="padding:3px;text-align:center">'+(it.qty||1)+'</td><td style="padding:3px;text-align:right">'+fmt(it.price)+'</td><td style="padding:3px;text-align:right;'+(ck?'color:#dc3545;font-weight:700':'')+'">'+fmt(it.discPrice||it.price)+'</td><td style="padding:3px;text-align:right;font-weight:700">'+fmt(it.total)+'</td><td style="padding:3px"><button class="xd" data-i="'+i+'" style="background:#fee2e2;border:none;color:#dc2626;padding:1px 4px;border-radius:3px;cursor:pointer;font-size:9px">✕</button></td></tr>';});
h+='</tbody></table>';
if(order.fees&&order.fees.length){h+='<div style="padding:4px 10px;background:#fffbeb;border-radius:6px;margin-bottom:6px;font-size:10px">';order.fees.forEach(function(f){h+='<div style="display:flex;justify-content:space-between"><span style="color:#92400e">'+esc(f.label)+'</span><b>'+fmt(f.amount)+'</b></div>';});h+='</div>';}
h+='<div style="padding:8px 12px;background:#003f62;border-radius:6px;display:flex;justify-content:space-between;margin-bottom:6px"><span style="color:#fff;font-weight:800">TỔNG</span><span style="color:#f0b840;font-weight:900;font-size:15px">'+fmt(order.grandTotal)+'</span></div>';
if(order.deposit>0)h+='<div style="padding:4px 10px;background:#f0fdf4;border-radius:6px;font-size:10px;margin-bottom:6px"><span>Cọc: <b>'+fmt(order.deposit)+'</b></span> | <span>Còn: <b style="color:#dc2626">'+fmt(order.remaining)+'</b></span></div>';
h+='<div style="margin:8px 0;padding:8px;background:#f0f9ff;border-radius:8px;border:1px solid #bae6fd"><input id="od-prompt" value="'+esc(order.promptText||'')+'" placeholder="mh70btc ck 30%, CK 10, giao 200k, lắp 300k, cọc 5tr" style="width:100%;padding:6px;border:1px solid #e2e8f0;border-radius:6px;font-size:11px;box-sizing:border-box"><button id="od-apply" style="margin-top:4px;padding:4px 10px;background:#0369a1;color:#fff;border:none;border-radius:5px;font-weight:700;cursor:pointer;font-size:10px">⚡ Áp dụng</button></div>'
+'<label style="display:inline-flex;align-items:center;gap:5px;margin-top:8px;padding:6px 9px;background:#fff;color:#111;border:1px solid #111;border-radius:8px;font-weight:700;font-size:10px"><input id="od-bw" type="checkbox"> Trắng đen</label>'
+'<div style="display:flex;gap:5px;margin-top:8px;flex-wrap:wrap"><button id="od-save" style="padding:7px 12px;background:#7c3aed;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">💾 Lưu</button><button id="od-pdf" style="padding:7px 10px;background:#db9815;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">PDF</button><button id="od-xl" style="padding:7px 10px;background:#b45309;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">Excel</button><button id="od-gh" style="padding:7px 10px;background:#0d9488;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">GH PDF</button><button id="od-gh-xl" style="padding:7px 10px;background:#059669;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">GH Excel</button>';
if(!isC)h+='<button id="od-confirm" style="padding:7px 14px;background:#16a34a;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:11px;margin-left:auto">✅ CHỐT</button>';
else h+='<button id="od-unconfirm" style="padding:7px 10px;background:#fef2f2;color:#dc2626;border:2px solid #fca5a5;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px;margin-left:auto">↩️ Bỏ chốt</button>';
h+='</div></div>';modal.innerHTML=h;
document.getElementById('xc').onclick=function(){ov.remove();};
modal.querySelectorAll('.xd').forEach(function(b){b.onclick=function(e){e.stopPropagation();order.items.splice(parseInt(this.dataset.i),1);render();};});
function cc(){order.customer=document.getElementById('od-name').value;order.phone=document.getElementById('od-phone').value;order.addr=document.getElementById('od-addr').value;}
var mI=document.getElementById('od-makh'),sB=document.getElementById('od-makh-suggest'),mInfo=document.getElementById('od-makh-info');
if(mI){mI.oninput=function(){var q=this.value.trim();if(q.length<1){sB.style.display='none';mInfo.textContent='';order.ma_kh='';return;}var ms=findKH(q);if(!ms.length){sB.style.display='none';mInfo.textContent='⚠️ Không tìm';order.ma_kh='';return;}var ex=ms.find(function(k){return k.ma_kh.toLowerCase()===q.toLowerCase();});if(ex){applyKH(ex);sB.style.display='none';return;}sB.style.display='block';sB.innerHTML=ms.slice(0,4).map(function(k){return'<div class="kh-opt" data-ma="'+esc(k.ma_kh)+'" style="padding:5px 8px;cursor:pointer;border-bottom:1px solid #f1f5f9;font-size:10px"><b>'+esc(k.ma_kh)+'</b> '+esc(k.ten)+'</div>';}).join('');sB.querySelectorAll('.kh-opt').forEach(function(o){o.onmousedown=function(e){e.preventDefault();var k=KH_LIST.find(function(x){return x.ma_kh===this.dataset.ma;}.bind(this));if(k){mI.value=k.ma_kh;applyKH(k);sB.style.display='none';}};});};mI.onblur=function(){setTimeout(function(){sB.style.display='none';},200);};}
function applyKH(kh){order.ma_kh=kh.ma_kh;var ap=[];(order.items||[]).forEach(function(it){var br=it.brand||(it.name||'').match(/(malloca|eurogold|grob|garis)/i);if(br){var bn=typeof br==='string'?br:br[1];var rate=getKHCKRate(kh,bn);if(rate!==null){it.discPrice=Math.round((it.price||0)*(1-rate/100));it.total=it.discPrice*(it.qty||1);ap.push(bn+' '+rate+'%');}}});order.discountPercent=0;recalcOrder(order);mInfo.innerHTML='✅ '+esc(kh.ma_kh)+' — '+esc(kh.ten)+(ap.length?' | '+ap.join(', '):'');render();}
document.getElementById('od-apply').onclick=function(){cc();var p=document.getElementById('od-prompt').value;order.promptText=p;var ps=parsePrompt(p);order.fees=ps.fees.length?ps.fees:(p.trim()?order.fees:[]);order.deposit=ps.deposit||0;if(ps.discountPercent)order.discountPercent=ps.discountPercent;order.itemDiscounts=ps.itemDiscounts||{};recalcOrder(order);render();};
document.getElementById('od-save').onclick=function(){cc();recalcOrder(order);order.savedAt=new Date().toISOString();addOrder(order);syncOrderToBot(order);alert('✅ Lưu!');};
document.getElementById('od-pdf').onclick=function(){cc();recalcOrder(order);exportPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
document.getElementById('od-xl').onclick=function(){cc();recalcOrder(order);exportExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
document.getElementById('od-gh').onclick=function(){cc();recalcOrder(order);exportDeliveryPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
document.getElementById('od-gh-xl').onclick=function(){cc();recalcOrder(order);exportDeliveryExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
var cB=document.getElementById('od-confirm');if(cB)cB.onclick=function(){cc();recalcOrder(order);if(!confirm('Chốt đơn '+order.code+'?'))return;confirmOrder(order);if(typeof showToast==='function')showToast('✅ Đã chốt '+order.code);render();};
var uB=document.getElementById('od-unconfirm');if(uB)uB.onclick=function(){if(!confirm('Bỏ chốt?'))return;unconfirmOrder(order);render();};
}render();
}
function injectMaKHToQuote(){var modal=document.querySelector('.quote-overlay.open,.quote-modal.open,.quote-overlay[style*="block"],.quote-modal[style*="block"],[class*="quote"][class*="open"],[class*="quote"][style*="block"]');if(!modal)return;if(modal.querySelector('#vai-makh-section'))return;var pI=null;modal.querySelectorAll('input,textarea').forEach(function(el){var ph=(el.placeholder||'').toLowerCase();if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('chiet khau'))pI=el;});if(!pI)return;
var sec=document.createElement('div');sec.id='vai-makh-section';sec.style.cssText='margin:8px 16px;padding:8px 10px;background:#eff6ff;border-radius:8px;border:1px solid #bfdbfe;position:relative';
sec.innerHTML='<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px"><span style="font-size:10px;font-weight:700;color:#1e40af">👤 Mã KH</span><span id="vai-makh-badge" style="font-size:9px;color:#64748b"></span></div><input id="vai-makh-input" placeholder="Mã KH hoặc tên..." style="width:100%;padding:6px 10px;border:1px solid #93c5fd;border-radius:6px;font-size:11px;box-sizing:border-box"><div id="vai-makh-dropdown" style="position:absolute;left:10px;right:10px;top:54px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;max-height:100px;overflow-y:auto;display:none;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,.1)"></div>';
pI.parentElement.insertBefore(sec,pI);
var input=document.getElementById('vai-makh-input'),dd=document.getElementById('vai-makh-dropdown'),badge=document.getElementById('vai-makh-badge');
input.oninput=function(){var q=this.value.trim();if(q.length<1){dd.style.display='none';badge.textContent='';return;}loadKhachHang().then(function(){var ms=findKH(q);if(!ms.length){dd.style.display='none';badge.textContent='⚠️';badge.style.color='#dc2626';return;}var ex=ms.find(function(k){return k.ma_kh.toLowerCase()===q.toLowerCase();});if(ex){dd.style.display='none';badge.innerHTML='✅ '+esc(ex.ten);badge.style.color='#16a34a';applyKHToQuoteModal(ex);return;}dd.style.display='block';dd.innerHTML=ms.slice(0,4).map(function(k){return'<div class="vai-kh-opt" data-ma="'+esc(k.ma_kh)+'" style="padding:5px 8px;cursor:pointer;border-bottom:1px solid #f1f5f9;font-size:10px"><b>'+esc(k.ma_kh)+'</b> '+esc(k.ten)+'</div>';}).join('');dd.querySelectorAll('.vai-kh-opt').forEach(function(o){o.onmousedown=function(e){e.preventDefault();var k=KH_LIST.find(function(x){return x.ma_kh===this.dataset.ma;}.bind(this));if(k){input.value=k.ma_kh;dd.style.display='none';badge.innerHTML='✅ '+esc(k.ten);badge.style.color='#16a34a';applyKHToQuoteModal(k);}};});});};
input.onblur=function(){setTimeout(function(){dd.style.display='none';},200);};
}
function applyKHToQuoteModal(kh){var codes=kh.ck_codes||[];if(!codes.length)return;var pI=null;document.querySelectorAll('input,textarea').forEach(function(el){var ph=(el.placeholder||'').toLowerCase();if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('chiet khau'))pI=el;});if(!pI)return;var ckVal=null;for(var i=0;i<codes.length;i++){var m=codes[i].match(/ck(\d+)/i);if(m){ckVal=parseInt(m[1]);break;}}if(!ckVal)return;var cur=pI.value||'';cur=cur.replace(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)\s*[\d.,]+\s*%?\s*[,;]?\s*/gim,'').trim();pI.value=('CK '+ckVal+(cur?', '+cur:'')).trim();pI.dispatchEvent(new Event('input',{bubbles:true}));}
var _mO=new MutationObserver(function(){setTimeout(injectMaKHToQuote,500);});_mO.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','style']});setInterval(injectMaKHToQuote,3000);
window.VAI_ORDERS={save:saveCurrentOrder,search:searchOrders,openSearch:openSearchModal,openDetail:openOrderDetail,getAll:getOrders,getByCode:function(c){return getOrders().find(function(o){return o.code===c;});},add:addOrder,delete:deleteOrder,confirm:confirmOrder,unconfirm:unconfirmOrder,toVAIData:orderToVAIData,loadKH:loadKhachHang,findKH:findKH,processQueue:processQueue,syncFromServer:syncFromServer};
var qLen=getQueue().length;
console.log('[OS v18] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled');
})();