Spaces:
Running
Running
Restore order modal info column and formatted product info exports v18
Browse files- order-store.js +28 -11
order-store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
/**
|
| 2 |
-
* V.AI STUDIO — Order Store
|
| 3 |
*
|
| 4 |
* CHANGES from v16:
|
| 5 |
* - SYNC FROM SERVER: Khi page load → fetch /orders từ bot → merge vào localStorage
|
|
@@ -42,7 +42,7 @@ function syncFromServer(){
|
|
| 42 |
email:so.email||'',
|
| 43 |
addr:so.dia_chi||so.addr||'',
|
| 44 |
date:so.date||so.ngay||'',
|
| 45 |
-
items:(so.items||[]).map(function(it){return{name:it.ten||it.name||'',model:it.ma||it.model||'',brand:it.brand||'',image:resolveOrderItemImage(it),specs:it
|
| 46 |
fees:so.fees||[],
|
| 47 |
deposit:so.deposit||0,
|
| 48 |
discountPercent:so.discountPercent||0,
|
|
@@ -59,10 +59,10 @@ function syncFromServer(){
|
|
| 59 |
});
|
| 60 |
if(added>0){
|
| 61 |
saveOrders(localOrders);
|
| 62 |
-
console.log('[OS
|
| 63 |
}
|
| 64 |
}).catch(function(e){
|
| 65 |
-
console.log('[OS
|
| 66 |
});
|
| 67 |
}
|
| 68 |
// Sync from server 3s after page load (give bot time to wake up)
|
|
@@ -78,14 +78,14 @@ function processQueue(){
|
|
| 78 |
var q=getQueue();if(!q.length)return;
|
| 79 |
var item=q[0];
|
| 80 |
fetch(KETOAN_API+item.endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(item.payload)})
|
| 81 |
-
.then(function(r){if(r.ok){q.shift();saveQueue(q);console.log('[OS
|
| 82 |
.catch(function(){item.attempts=(item.attempts||0)+1;if(item.attempts>50){q.shift();}saveQueue(q);});
|
| 83 |
}
|
| 84 |
setInterval(processQueue,30000);
|
| 85 |
setTimeout(processQueue,5000);
|
| 86 |
|
| 87 |
function syncOrderToBot(order){
|
| 88 |
-
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||'',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()};
|
| 89 |
if(order.confirmedAt)payload.confirmedAt=order.confirmedAt;
|
| 90 |
fetch(KETOAN_API+'/order-saved',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 91 |
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
|
@@ -93,7 +93,7 @@ function syncOrderToBot(order){
|
|
| 93 |
}
|
| 94 |
|
| 95 |
function sendToKetoanBot(order){
|
| 96 |
-
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||'',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||''};
|
| 97 |
fetch(KETOAN_API+'/order-confirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 98 |
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
| 99 |
.then(function(d){if(d.ok&&typeof showToast==='function')showToast('📨 Đã gửi kế toán!');})
|
|
@@ -112,6 +112,23 @@ function deleteOrder(code){saveOrders(getOrders().filter(function(o){return o.co
|
|
| 112 |
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);});}
|
| 113 |
function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 116 |
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;});}
|
| 117 |
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;}
|
|
@@ -156,7 +173,7 @@ function saveCurrentOrder(){if(!window.VAI_QR||!window.VAI_QR.getData)return;var
|
|
| 156 |
function confirmOrder(order){order.status='confirmed';order.confirmedAt=new Date().toISOString();addOrder(order);sendToKetoanBot(order);}
|
| 157 |
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(){});}
|
| 158 |
|
| 159 |
-
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:it
|
| 160 |
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);}}
|
| 161 |
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));}}
|
| 162 |
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));}}
|
|
@@ -178,8 +195,8 @@ function openOrderDetail(order){var old=document.getElementById('vai-order-detai
|
|
| 178 |
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">'
|
| 179 |
+'<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>'
|
| 180 |
+'<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>'
|
| 181 |
-
+'<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">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>';
|
| 182 |
-
(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;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>';});
|
| 183 |
h+='</tbody></table>';
|
| 184 |
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>';}
|
| 185 |
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>';
|
|
@@ -221,5 +238,5 @@ var _mO=new MutationObserver(function(){setTimeout(injectMaKHToQuote,500);});_mO
|
|
| 221 |
|
| 222 |
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};
|
| 223 |
var qLen=getQueue().length;
|
| 224 |
-
console.log('[OS
|
| 225 |
})();
|
|
|
|
| 1 |
/**
|
| 2 |
+
* V.AI STUDIO — Order Store v18
|
| 3 |
*
|
| 4 |
* CHANGES from v16:
|
| 5 |
* - SYNC FROM SERVER: Khi page load → fetch /orders từ bot → merge vào localStorage
|
|
|
|
| 42 |
email:so.email||'',
|
| 43 |
addr:so.dia_chi||so.addr||'',
|
| 44 |
date:so.date||so.ngay||'',
|
| 45 |
+
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)};}),
|
| 46 |
fees:so.fees||[],
|
| 47 |
deposit:so.deposit||0,
|
| 48 |
discountPercent:so.discountPercent||0,
|
|
|
|
| 59 |
});
|
| 60 |
if(added>0){
|
| 61 |
saveOrders(localOrders);
|
| 62 |
+
console.log('[OS v18] ☁️ Synced '+added+' orders from server. Total local: '+localOrders.length);
|
| 63 |
}
|
| 64 |
}).catch(function(e){
|
| 65 |
+
console.log('[OS v18] Server sync skip (bot may be sleeping)');
|
| 66 |
});
|
| 67 |
}
|
| 68 |
// Sync from server 3s after page load (give bot time to wake up)
|
|
|
|
| 78 |
var q=getQueue();if(!q.length)return;
|
| 79 |
var item=q[0];
|
| 80 |
fetch(KETOAN_API+item.endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(item.payload)})
|
| 81 |
+
.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);})
|
| 82 |
.catch(function(){item.attempts=(item.attempts||0)+1;if(item.attempts>50){q.shift();}saveQueue(q);});
|
| 83 |
}
|
| 84 |
setInterval(processQueue,30000);
|
| 85 |
setTimeout(processQueue,5000);
|
| 86 |
|
| 87 |
function syncOrderToBot(order){
|
| 88 |
+
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()};
|
| 89 |
if(order.confirmedAt)payload.confirmedAt=order.confirmedAt;
|
| 90 |
fetch(KETOAN_API+'/order-saved',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 91 |
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
function sendToKetoanBot(order){
|
| 96 |
+
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||''};
|
| 97 |
fetch(KETOAN_API+'/order-confirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 98 |
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
| 99 |
.then(function(d){if(d.ok&&typeof showToast==='function')showToast('📨 Đã gửi kế toán!');})
|
|
|
|
| 112 |
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);});}
|
| 113 |
function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
|
| 114 |
|
| 115 |
+
function formatInfoVal(v){
|
| 116 |
+
if(v==null||v==='')return '';
|
| 117 |
+
if(typeof v==='string'){
|
| 118 |
+
var t=v.trim();
|
| 119 |
+
if(!t||t==='{}'||t==='[]')return '';
|
| 120 |
+
if((t[0]==='{'&&t[t.length-1]==='}')||(t[0]==='['&&t[t.length-1]===']')){try{return formatInfoVal(JSON.parse(t));}catch(e){return t;}}
|
| 121 |
+
return t;
|
| 122 |
+
}
|
| 123 |
+
if(Array.isArray(v))return v.map(formatInfoVal).filter(Boolean).join('; ');
|
| 124 |
+
if(typeof v==='object'){
|
| 125 |
+
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));});
|
| 126 |
+
return out.join('; ');
|
| 127 |
+
}
|
| 128 |
+
return String(v);
|
| 129 |
+
}
|
| 130 |
+
function orderItemInfo(it){it=it||{};return formatInfoVal(it.info||it.thong_tin||it.dim_info||it.dimension_info||it.specs||it.summary||it.desc||'');}
|
| 131 |
+
|
| 132 |
function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 133 |
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;});}
|
| 134 |
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;}
|
|
|
|
| 173 |
function confirmOrder(order){order.status='confirmed';order.confirmedAt=new Date().toISOString();addOrder(order);sendToKetoanBot(order);}
|
| 174 |
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(){});}
|
| 175 |
|
| 176 |
+
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};}
|
| 177 |
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);}}
|
| 178 |
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));}}
|
| 179 |
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));}}
|
|
|
|
| 195 |
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">'
|
| 196 |
+'<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>'
|
| 197 |
+'<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>'
|
| 198 |
+
+'<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>';
|
| 199 |
+
(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>';});
|
| 200 |
h+='</tbody></table>';
|
| 201 |
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>';}
|
| 202 |
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>';
|
|
|
|
| 238 |
|
| 239 |
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};
|
| 240 |
var qLen=getQueue().length;
|
| 241 |
+
console.log('[OS v18] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled');
|
| 242 |
})();
|