bep40 commited on
Commit
e72d71a
·
verified ·
1 Parent(s): 3080268

Restore order-store.js v19 from commit e5d3e00e

Browse files
Files changed (1) hide show
  1. order-store.js +346 -0
order-store.js ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO — Order Store v19
3
+ *
4
+ * CHANGES from v18:
5
+ * - ADD PRODUCT TO ORDER: Nút "➕ Thêm SP" trong modal sửa đơn
6
+ * → Hiện thanh tìm kiếm SP → Chọn SP → Tự động thêm vào đơn + lưu
7
+ */
8
+ (function(){
9
+ 'use strict';
10
+ var STORE_KEY='vai_orders';
11
+ var QUEUE_KEY='vai_sync_queue';
12
+ var KETOAN_API='https://bep40-vaistudio-ketoan-bot.hf.space';
13
+ var KH_LIST=[];var KH_LOADED=false;
14
+
15
+ // ===== SYNC FROM SERVER (NEW in v17) =====
16
+
17
+ function resolveOrderItemImage(it){
18
+ var img=it.image||it.img||'';if(img)return img;
19
+ 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(/[̀-ͯ]/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(/[̀-ͯ]/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||'';}}
20
+ catch(e){}
21
+ return '';
22
+ }
23
+ function syncFromServer(){
24
+ fetch(KETOAN_API+'/orders',{method:'GET'}).then(function(r){return r.json();}).then(function(d){
25
+ var serverOrders=d.data||[];
26
+ if(!serverOrders.length)return;
27
+ var localOrders=getOrders();
28
+ var localCodes={};
29
+ localOrders.forEach(function(o){if(o.code)localCodes[o.code]=true;});
30
+ var added=0;
31
+ serverOrders.forEach(function(so){
32
+ var code=so.ma_don||so.code||'';
33
+ if(!code)return;
34
+ var existingIdx=localOrders.findIndex(function(o){return o.code===code;});
35
+ if(existingIdx>=0 && !so.overwrite && !so.savedAt)return;
36
+ // Convert server format to local format
37
+ var order={
38
+ code:code,
39
+ customer:so.khach||so.customer||'',
40
+ phone:so.dien_thoai||so.phone||'',
41
+ email:so.email||'',
42
+ addr:so.dia_chi||so.addr||'',
43
+ date:so.date||so.ngay||'',
44
+ 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)};}),
45
+ fees:so.fees||[],
46
+ deposit:so.deposit||0,
47
+ discountPercent:so.discountPercent||0,
48
+ grandTotal:so.grandTotal||0,
49
+ remaining:so.remaining||0,
50
+ status:so.status||(so.confirmedAt?'confirmed':'pending'),
51
+ savedAt:so.savedAt||so.confirmedAt||'',
52
+ confirmedAt:so.confirmedAt||'',
53
+ ma_kh:so.ma_kh||''
54
+ };
55
+ if(existingIdx>=0)localOrders[existingIdx]=order;else localOrders.unshift(order);
56
+ localCodes[code]=true;
57
+ added++;
58
+ });
59
+ if(added>0){
60
+ saveOrders(localOrders);
61
+ console.log('[OS v19] ☁️ Synced '+added+' orders from server. Total local: '+localOrders.length);
62
+ }
63
+ }).catch(function(e){
64
+ console.log('[OS v19] Server sync skip (bot may be sleeping)');
65
+ });
66
+ }
67
+ // Sync from server 3s after page load (give bot time to wake up)
68
+ setTimeout(syncFromServer,3000);
69
+ // Also sync every 60s
70
+ setInterval(syncFromServer,60000);
71
+
72
+ // ===== RETRY QUEUE =====
73
+ function getQueue(){try{return JSON.parse(localStorage.getItem(QUEUE_KEY)||'[]');}catch(e){return[];}}
74
+ function saveQueue(q){localStorage.setItem(QUEUE_KEY,JSON.stringify(q));}
75
+ function addToQueue(endpoint,payload){var q=getQueue();q.push({endpoint:endpoint,payload:payload,attempts:0,addedAt:Date.now()});saveQueue(q);}
76
+ function processQueue(){
77
+ var q=getQueue();if(!q.length)return;
78
+ var item=q[0];
79
+ fetch(KETOAN_API+item.endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(item.payload)})
80
+ .then(function(r){if(r.ok){q.shift();saveQueue(q);console.log('[OS v19] Queue sync OK: '+item.endpoint+' ('+q.length+' remaining)');if(q.length)setTimeout(processQueue,2000);}else throw new Error('HTTP '+r.status);})
81
+ .catch(function(){item.attempts=(item.attempts||0)+1;if(item.attempts>50){q.shift();}saveQueue(q);});
82
+ }
83
+ setInterval(processQueue,30000);
84
+ setTimeout(processQueue,5000);
85
+
86
+ function syncOrderToBot(order){
87
+ 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()};
88
+ if(order.confirmedAt)payload.confirmedAt=order.confirmedAt;
89
+ fetch(KETOAN_API+'/order-saved',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
90
+ .then(function(r){if(!r.ok)throw new Error();return r.json();})
91
+ .catch(function(){addToQueue('/order-saved',payload);});
92
+ }
93
+
94
+ function sendToKetoanBot(order){
95
+ 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||''};
96
+ fetch(KETOAN_API+'/order-confirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
97
+ .then(function(r){if(!r.ok)throw new Error();return r.json();})
98
+ .then(function(d){if(d.ok&&typeof showToast==='function')showToast('📨 Đã gửi kế toán!');})
99
+ .catch(function(){addToQueue('/order-confirmed',payload);if(typeof showToast==='function')showToast('⏳ Bot đang khởi động, sẽ tự gửi lại...');});
100
+ }
101
+
102
+ 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;});}
103
+ setTimeout(loadKhachHang,1000);
104
+ 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);});}
105
+ 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;}
106
+
107
+ function getOrders(){try{return JSON.parse(localStorage.getItem(STORE_KEY)||'[]');}catch(e){return[];}}
108
+ function saveOrders(orders){localStorage.setItem(STORE_KEY,JSON.stringify(orders));}
109
+ 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;}
110
+ function deleteOrder(code){saveOrders(getOrders().filter(function(o){return o.code!==code;}));}
111
+ 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);});}
112
+ function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
113
+
114
+ function formatInfoVal(v){
115
+ if(v==null||v==='')return '';
116
+ if(typeof v==='string'){
117
+ var t=v.trim();
118
+ if(!t||t==='{}'||t==='[]')return '';
119
+ if((t[0]==='{'&&t[t.length-1]==='}')||(t[0]==='['&&t[t.length-1]===']')){try{return formatInfoVal(JSON.parse(t));}catch(e){return t;}}
120
+ return t;
121
+ }
122
+ if(Array.isArray(v))return v.map(formatInfoVal).filter(Boolean).join('; ');
123
+ if(typeof v==='object'){
124
+ 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));});
125
+ return out.join('; ');
126
+ }
127
+ return String(v);
128
+ }
129
+ function orderItemInfo(it){it=it||{};return formatInfoVal(it.info||it.thong_tin||it.dim_info||it.dimension_info||it.specs||it.summary||it.desc||'');}
130
+
131
+ function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');}
132
+ 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;});}
133
+ 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;}
134
+ function parseItemDiscountLine(line,map){var lo=(line||'').toString().normalize('NFD').replace(/[̀-ͯ]/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&&v<=100)map[compactKey(m[1])]=v2;}}
135
+ function esc(s){return(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
136
+ function parsePrompt(text){
137
+ var fees=[],deposit=0,discountPercent=0,note='',itemDiscounts={};
138
+ if(!text)return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
139
+ text.split(/[,;\n]+/).forEach(function(line){
140
+ line=line.trim();if(!line)return;
141
+ parseItemDiscountLine(line,itemDiscounts);
142
+ 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;
143
+ var lo=line.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d');
144
+ // Ghi chú
145
+ if(lo.match(/^(ghi chu|note|luu y)/)){note=line.replace(/^(ghi chú|ghi chu|note|lưu ý|luu y)[:\s]*/i,'').trim();return;}
146
+ // CK
147
+ var ck=lo.match(/^(ck|chiet khau|giam|discount)\s*([\d.,]+)\s*(%|k|tr)?/);
148
+ if(ck){var v=parseFloat(ck[2].replace(/\./g,'').replace(',','.'));if((!ck[3]||ck[3]===''||ck[3]==='%')&&v>0&&v<=100)discountPercent=v;return;}
149
+ // Cọc
150
+ 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;}
151
+ // Phụ phí: giao hàng, lắp đặt, vận chuyển, phí khác
152
+ 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)?/);
153
+ if(feeMatch){
154
+ var amt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.'));
155
+ var u=(feeMatch[3]||'').toLowerCase();
156
+ 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;
157
+ 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í'};
158
+ fees.push({label:labelMap[feeMatch[1]]||'Phụ phí',amount:amt});
159
+ return;
160
+ }
161
+ // Fallback: line with number
162
+ var m=lo.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
163
+ 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});}}
164
+ });
165
+ return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
166
+ }
167
+
168
+ 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);});}
169
+ 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;}
170
+
171
+ 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||{},notes:d.notes||[],grandTotal:d.grandTotal||0,remaining:d.remaining||0,promptText:window.VAI_QR.getPromptText?window.VAI_QR.getPromptText():'',status:'pending',savedAt:new Date().toISOString()};recalcOrder(order);addOrder(order);syncOrderToBot(order);if(typeof showToast==='function')showToast('✅ Đã lưu đơn '+code);}
172
+ function confirmOrder(order){order.status='confirmed';order.confirmedAt=new Date().toISOString();addOrder(order);sendToKetoanBot(order);}
173
+ 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(){});}
174
+
175
+ 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:order.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};}
176
+ 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);}}
177
+ 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));}}
178
+ 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));}}
179
+ 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));}}
180
+ 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));}}
181
+
182
+ 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;
183
+ // Refresh button — force sync from server
184
+ document.getElementById('vai-os-refresh').onclick=function(){
185
+ this.textContent='⏳';var self=this;
186
+ syncFromServer();
187
+ setTimeout(function(){self.textContent='🔄';ov.remove();openSearchModal();},2000);
188
+ };
189
+ 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();};});}
190
+
191
+ 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);
192
+ var _searchOpen=false;
193
+ function addProductToOrder(product){
194
+ var pn=product.priceNum||product.pn||0;
195
+ var item={name:product.name||product.n||'',model:product.sku||product.model||product.mod||'',brand:product.brand||'',image:product.image||product.img||product.i||'',specs:product.specs||'',info:product.info||product.thong_tin||'',dim_info:product.dim_info||product.dimension_info||'',qty:1,price:pn,discPrice:pn,total:pn,note:''};
196
+ order.items.push(item);
197
+ recalcOrder(order);
198
+ order.savedAt=new Date().toISOString();
199
+ addOrder(order);
200
+ syncOrderToBot(order);
201
+ if(typeof showToast==='function')showToast('✅ Đã thêm "'+((product.name||product.n||'').substring(0,25))+'"');
202
+ _searchOpen=false;
203
+ render();
204
+ }
205
+ function nrm(s){return(s||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d').toLowerCase();}
206
+ function searchProducts(q){
207
+ if(!q||q.length<1)return[];
208
+ var db=[];if(typeof D!=='undefined'&&Array.isArray(D))db=D;if(typeof VAI_URL_ADDED_PRODUCTS!=='undefined'&&Array.isArray(VAI_URL_ADDED_PRODUCTS))db=db.concat(VAI_URL_ADDED_PRODUCTS);
209
+ var nq=nrm(q);var terms=nq.split(/\s+/).filter(function(t){return t.length>1;});
210
+ var scored=[];
211
+ for(var i=0;i<db.length;i++){
212
+ var p=db[i];if(!p)continue;
213
+ var name=nrm(p.name||p.n||'');var sku=nrm(p.sku||p.model||p.mod||'');var brand=nrm(p.brand||'');
214
+ var score=0;
215
+ for(var j=0;j<terms.length;j++){
216
+ var t=terms[j];
217
+ if(name.indexOf(t)!==-1)score+=3;
218
+ if(sku.indexOf(t)!==-1)score+=5;
219
+ if(brand.indexOf(t)!==-1)score+=2;
220
+ if(name===t||sku===t)score+=10;
221
+ }
222
+ if(score>0)scored.push({p:p,s:score});
223
+ }
224
+ scored.sort(function(a,b){return b.s-a.s;});
225
+ return scored.slice(0,20).map(function(x){return x.p;});
226
+ }
227
+ function toggleSearchPanel(){
228
+ _searchOpen=!_searchOpen;
229
+ render();
230
+ if(_searchOpen){
231
+ setTimeout(function(){
232
+ var inp=document.getElementById('od-sp-search');
233
+ if(inp)inp.focus();
234
+ },50);
235
+ }
236
+ }
237
+ function renderSearchPanel(){
238
+ if(!_searchOpen)return '';
239
+ return '<div id="od-sp-panel" style="margin-top:8px;padding:10px;background:#f8fafc;border-radius:8px;border:2px solid #003f62">'
240
+ +'<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px">'
241
+ +'<span style="font-size:12px;font-weight:700;color:#003f62">🔍 Thêm sản phẩm</span>'
242
+ +'<button id="od-sp-close" style="margin-left:auto;background:none;border:none;color:#64748b;font-size:16px;cursor:pointer;padding:0 4px">✕</button></div>'
243
+ +'<input id="od-sp-search" placeholder="Tìm tên SP, mã SP, thương hiệu..." style="width:100%;padding:8px 10px;border:2px solid #003f62;border-radius:8px;font-size:12px;box-sizing:border-box;outline:none" onfocus="this.style.borderColor=\'#005a8c\'" onblur="this.style.borderColor=\'#003f62\'">'
244
+ +'<div id="od-sp-results" style="max-height:260px;overflow-y:auto;margin-top:6px"></div></div>';
245
+ }
246
+ function renderSearchResults(){
247
+ if(!_searchOpen)return;
248
+ var q=document.getElementById('od-sp-search');
249
+ if(!q)return;
250
+ var results=searchProducts(q.value);
251
+ var el=document.getElementById('od-sp-results');
252
+ if(!el)return;
253
+ if(!q.value.trim()){el.innerHTML='<div style="text-align:center;padding:16px;color:#94a3b8;font-size:11px">Nhập từ khóa để tìm sản phẩm</div>';return;}
254
+ if(!results.length){el.innerHTML='<div style="text-align:center;padding:16px;color:#94a3b8;font-size:11px">Không tìm thấy sản phẩm</div>';return;}
255
+ el.innerHTML=results.map(function(p){
256
+ var nm=p.name||p.n||'';
257
+ var sku=p.sku||p.model||p.mod||'';
258
+ var br=p.brand||'';
259
+ var img=p.image||p.img||p.i||'';
260
+ var price=p.priceNum||p.pn||0;
261
+ return '<div class="od-sp-item" style="display:flex;align-items:center;gap:8px;padding:6px;border:1px solid #e2e8f0;border-radius:6px;margin-bottom:4px;cursor:pointer;background:#fff" onmouseover="this.style.background=\'#eff6ff\';this.style.borderColor=\'#93c5fd\'" onmouseout="this.style.background=\'#fff\';this.style.borderColor=\'#e2e8f0\'">'
262
+ +'<div style="width:44px;height:44px;flex-shrink:0;background:#f1f5f9;border-radius:4px;display:flex;align-items:center;justify-content:center;overflow:hidden">'+(img?'<img src="'+img+'" style="max-width:40px;max-height:40px;object-fit:contain" referrerpolicy="no-referrer" onerror="this.style.display=\'none\'">':'<span style="font-size:16px;color:#94a3b8">📦</span>')+'</div>'
263
+ +'<div style="flex:1;min-width:0"><div style="font-size:11px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(nm)+'</div><div style="font-size:9px;color:#64748b">'+esc(sku)+(br?' • '+esc(br):'')+'</div></div>'
264
+ +'<div style="font-size:11px;font-weight:700;color:#003f62;white-space:nowrap">'+fmt(price)+'</div>'
265
+ +'<button class="od-sp-add" data-name="'+esc(nm)+'" style="padding:4px 8px;background:#003f62;color:#fff;border:none;border-radius:5px;font-size:10px;font-weight:700;cursor:pointer;white-space:nowrap">+ Thêm</button></div>';
266
+ }).join('');
267
+ // Bind click events
268
+ el.querySelectorAll('.od-sp-item').forEach(function(item){
269
+ item.onclick=function(e){
270
+ if(e.target.closest('.od-sp-add'))return;
271
+ var nm=this.querySelector('.od-sp-add').dataset.name;
272
+ var idx=results.findIndex(function(p){return(p.name||p.n||'')===nm;});
273
+ if(idx>=0)addProductToOrder(results[idx]);
274
+ };
275
+ });
276
+ el.querySelectorAll('.od-sp-add').forEach(function(btn){
277
+ btn.onclick=function(e){
278
+ e.stopPropagation();
279
+ var nm=this.dataset.name;
280
+ var idx=results.findIndex(function(p){return(p.name||p.n||'')===nm;});
281
+ if(idx>=0)addProductToOrder(results[idx]);
282
+ };
283
+ });
284
+ }
285
+ function render(){recalcOrder(order);var isC=order.status==='confirmed';
286
+ 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">'
287
+ +'<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>'
288
+ +'<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>'
289
+ +'<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>';
290
+ (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>';});
291
+ h+='</tbody></table>';
292
+ 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>';}
293
+ 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>';
294
+ 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>';
295
+ 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>'
296
+ +'<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>'
297
+ +'<div style="display:flex;gap:5px;margin-top:8px;flex-wrap:wrap"><button id="od-add-sp" style="padding:7px 12px;background:#0891b2;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">➕ Thêm SP</button><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>';
298
+ 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>';
299
+ 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>';
300
+ h+='</div>';
301
+ h+=renderSearchPanel();
302
+ h+='</div>';modal.innerHTML=h;
303
+ document.getElementById('xc').onclick=function(){ov.remove();};
304
+ modal.querySelectorAll('.xd').forEach(function(b){b.onclick=function(e){e.stopPropagation();order.items.splice(parseInt(this.dataset.i),1);render();};});
305
+ // Add SP button
306
+ var addSpBtn=document.getElementById('od-add-sp');
307
+ if(addSpBtn){addSpBtn.onclick=toggleSearchPanel;}
308
+ // Close search panel
309
+ var closeSpBtn=document.getElementById('od-sp-close');
310
+ if(closeSpBtn){closeSpBtn.onclick=function(){_searchOpen=false;render();};}
311
+ // Search input
312
+ var searchInput=document.getElementById('od-sp-search');
313
+ if(searchInput){searchInput.oninput=function(){renderSearchResults();};searchInput.onkeydown=function(e){if(e.key==='Escape'){_searchOpen=false;render();}};}
314
+ // Render search results if panel is open
315
+ if(_searchOpen){renderSearchResults();}
316
+ function cc(){order.customer=document.getElementById('od-name').value;order.phone=document.getElementById('od-phone').value;order.addr=document.getElementById('od-addr').value;}
317
+ var mI=document.getElementById('od-makh'),sB=document.getElementById('od-makh-suggest'),mInfo=document.getElementById('od-makh-info');
318
+ 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);};}
319
+ 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();}
320
+ 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||{};order.notes=ps.notes||[];recalcOrder(order);render();};
321
+ document.getElementById('od-save').onclick=function(){cc();var p=document.getElementById('od-prompt').value;if(p&&p!==order.promptText){order.promptText=p;var ps=parsePrompt(p);order.fees=ps.fees.length?ps.fees:[];order.deposit=ps.deposit||0;if(ps.discountPercent)order.discountPercent=ps.discountPercent;order.itemDiscounts=ps.itemDiscounts||{};order.notes=ps.notes||[];recalcOrder(order);}else{recalcOrder(order);}order.savedAt=new Date().toISOString();addOrder(order);syncOrderToBot(order);alert('✅ Lưu!');};
322
+ document.getElementById('od-pdf').onclick=function(){cc();recalcOrder(order);exportPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
323
+ document.getElementById('od-xl').onclick=function(){cc();recalcOrder(order);exportExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
324
+ document.getElementById('od-gh').onclick=function(){cc();recalcOrder(order);exportDeliveryPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
325
+ document.getElementById('od-gh-xl').onclick=function(){cc();recalcOrder(order);exportDeliveryExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
326
+ 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();};
327
+ var uB=document.getElementById('od-unconfirm');if(uB)uB.onclick=function(){if(!confirm('Bỏ chốt?'))return;unconfirmOrder(order);render();};
328
+ }render();
329
+ }
330
+
331
+ 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;
332
+ 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';
333
+ 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>';
334
+ pI.parentElement.insertBefore(sec,pI);
335
+ var input=document.getElementById('vai-makh-input'),dd=document.getElementById('vai-makh-dropdown'),badge=document.getElementById('vai-makh-badge');
336
+ 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);}};});});};
337
+ input.onblur=function(){setTimeout(function(){dd.style.display='none';},200);};
338
+ }
339
+ 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}));}
340
+
341
+ var _mO=new MutationObserver(function(){setTimeout(injectMaKHToQuote,500);});_mO.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','style']});setInterval(injectMaKHToQuote,3000);
342
+
343
+ 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};
344
+ var qLen=getQueue().length;
345
+ console.log('[OS v19] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled | ➕ Add product to order enabled');
346
+ })();