Khôi phục order-store.js về commit a75919ca
Browse files- order-store.js +124 -44
order-store.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
/**
|
| 2 |
-
* V.AI STUDIO — Order Store v19
|
| 3 |
-
*
|
| 4 |
-
*
|
| 5 |
-
*
|
| 6 |
-
*
|
|
|
|
| 7 |
*/
|
| 8 |
(function(){
|
| 9 |
'use strict';
|
|
@@ -58,10 +59,10 @@ function syncFromServer(){
|
|
| 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)
|
|
@@ -77,14 +78,14 @@ 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();})
|
|
@@ -92,7 +93,7 @@ function syncOrderToBot(order){
|
|
| 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!');})
|
|
@@ -131,26 +132,33 @@ function orderItemInfo(it){it=it||{};return formatInfoVal(it.info||it.thong_tin|
|
|
| 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=/
|
| 135 |
function esc(s){return(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
| 136 |
function parsePrompt(text){
|
| 137 |
-
var fees=[],deposit=0,discountPercent=0,
|
| 138 |
-
if(!text)return{fees:fees,deposit:deposit,discountPercent:discountPercent,
|
| 139 |
-
text.split(/[,;
|
| 140 |
-
]+/).forEach(function(line){
|
| 141 |
line=line.trim();if(!line)return;
|
| 142 |
parseItemDiscountLine(line,itemDiscounts);
|
| 143 |
-
if(Object.keys(itemDiscounts).length&&line.match(/
|
| 144 |
var lo=line.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d');
|
| 145 |
-
// Ghi chú
|
| 146 |
-
if(lo.match(/^(ghi chu|note|luu y)/)){
|
| 147 |
-
// CK
|
| 148 |
var ck=lo.match(/^(ck|chiet khau|giam|discount)\s*([\d.,]+)\s*(%|k|tr)?/);
|
| 149 |
if(ck){var v=parseFloat(ck[2].replace(/\./g,'').replace(',','.'));if((!ck[3]||ck[3]===''||ck[3]==='%')&&v>0&&v<=100)discountPercent=v;return;}
|
|
|
|
|
|
|
| 150 |
// Cọc
|
| 151 |
-
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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
// Phụ phí: giao hàng, lắp đặt, vận chuyển, phí khác
|
| 153 |
-
var feeMatch=lo.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi giao|phi
|
| 154 |
if(feeMatch){
|
| 155 |
var amt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.'));
|
| 156 |
var u=(feeMatch[3]||'').toLowerCase();
|
|
@@ -159,21 +167,83 @@ function parsePrompt(text){
|
|
| 159 |
fees.push({label:labelMap[feeMatch[1]]||'Phụ phí',amount:amt});
|
| 160 |
return;
|
| 161 |
}
|
| 162 |
-
// Fallback: line with number
|
| 163 |
var m=lo.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 164 |
-
if(m
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
});
|
| 166 |
-
return{fees:fees,deposit:deposit,discountPercent:discountPercent,
|
| 167 |
}
|
| 168 |
|
| 169 |
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);});}
|
| 170 |
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;}
|
| 171 |
|
| 172 |
-
|
| 173 |
-
function
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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: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};}
|
| 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));}}
|
|
@@ -181,15 +251,20 @@ async function exportDeliveryPDF(order,opt){if(window.VAI_QR&&window.VAI_QR.expo
|
|
| 181 |
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));}}
|
| 182 |
|
| 183 |
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;
|
| 184 |
-
// Refresh button — force sync from server
|
| 185 |
document.getElementById('vai-os-refresh').onclick=function(){
|
| 186 |
this.textContent='⏳';var self=this;
|
| 187 |
syncFromServer();
|
| 188 |
setTimeout(function(){self.textContent='🔄';ov.remove();openSearchModal();},2000);
|
| 189 |
};
|
| 190 |
-
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();};});
|
|
|
|
| 191 |
|
| 192 |
-
function openOrderDetail(order){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
var _searchOpen=false;
|
| 194 |
function addProductToOrder(product){
|
| 195 |
var pn=product.priceNum||product.pn||0;
|
|
@@ -265,7 +340,6 @@ function openOrderDetail(order){var old=document.getElementById('vai-order-detai
|
|
| 265 |
+'<div style="font-size:11px;font-weight:700;color:#003f62;white-space:nowrap">'+fmt(price)+'</div>'
|
| 266 |
+'<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>';
|
| 267 |
}).join('');
|
| 268 |
-
// Bind click events
|
| 269 |
el.querySelectorAll('.od-sp-item').forEach(function(item){
|
| 270 |
item.onclick=function(e){
|
| 271 |
if(e.target.closest('.od-sp-add'))return;
|
|
@@ -283,16 +357,19 @@ function openOrderDetail(order){var old=document.getElementById('vai-order-detai
|
|
| 283 |
};
|
| 284 |
});
|
| 285 |
}
|
| 286 |
-
function render(){
|
|
|
|
| 287 |
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">'
|
| 288 |
-
+'<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>'
|
| 289 |
+'<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>'
|
| 290 |
-
+'<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>';
|
| 291 |
-
(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>';});
|
| 292 |
h+='</tbody></table>';
|
| 293 |
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>';}
|
| 294 |
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>';
|
| 295 |
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>';
|
|
|
|
|
|
|
| 296 |
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>'
|
| 297 |
+'<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>'
|
| 298 |
+'<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>';
|
|
@@ -303,23 +380,26 @@ function openOrderDetail(order){var old=document.getElementById('vai-order-detai
|
|
| 303 |
h+='</div>';modal.innerHTML=h;
|
| 304 |
document.getElementById('xc').onclick=function(){ov.remove();};
|
| 305 |
modal.querySelectorAll('.xd').forEach(function(b){b.onclick=function(e){e.stopPropagation();order.items.splice(parseInt(this.dataset.i),1);render();};});
|
| 306 |
-
// Add SP button
|
| 307 |
var addSpBtn=document.getElementById('od-add-sp');
|
| 308 |
if(addSpBtn){addSpBtn.onclick=toggleSearchPanel;}
|
| 309 |
-
// Close search panel
|
| 310 |
var closeSpBtn=document.getElementById('od-sp-close');
|
| 311 |
if(closeSpBtn){closeSpBtn.onclick=function(){_searchOpen=false;render();};}
|
| 312 |
-
// Search input
|
| 313 |
var searchInput=document.getElementById('od-sp-search');
|
| 314 |
if(searchInput){searchInput.oninput=function(){renderSearchResults();};searchInput.onkeydown=function(e){if(e.key==='Escape'){_searchOpen=false;render();}};}
|
| 315 |
-
// Render search results if panel is open
|
| 316 |
if(_searchOpen){renderSearchResults();}
|
| 317 |
-
function cc(){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
var mI=document.getElementById('od-makh'),sB=document.getElementById('od-makh-suggest'),mInfo=document.getElementById('od-makh-info');
|
| 319 |
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);};}
|
| 320 |
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();}
|
| 321 |
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();};
|
| 322 |
-
|
| 323 |
document.getElementById('od-pdf').onclick=function(){cc();recalcOrder(order);exportPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 324 |
document.getElementById('od-xl').onclick=function(){cc();recalcOrder(order);exportExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 325 |
document.getElementById('od-gh').onclick=function(){cc();recalcOrder(order);exportDeliveryPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
|
@@ -343,5 +423,5 @@ var _mO=new MutationObserver(function(){setTimeout(injectMaKHToQuote,500);});_mO
|
|
| 343 |
|
| 344 |
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};
|
| 345 |
var qLen=getQueue().length;
|
| 346 |
-
console.log('[OS v19] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled | ➕ Add product to order enabled');
|
| 347 |
-
})();
|
|
|
|
| 1 |
/**
|
| 2 |
+
* V.AI STUDIO — Order Store v19.2
|
| 3 |
+
* FIX TRIỆT ĐỂ (v19.2):
|
| 4 |
+
* - saveCurrentOrder: lưu date từ input khách hàng (không để trống ngày đơn)
|
| 5 |
+
* - orderToVAIData: productTotal = tổng tiền SP (KHÔNG trừ phí); lấy notes từ order.notes (ưu tiên) rồi qd.notes
|
| 6 |
+
* - openOrderDetail: thêm cột Ghi chú vào bảng; lấy date từ input; luôn recalc trước export
|
| 7 |
+
* - parsePrompt: 'Malloca ck 35%' detected as global discount; 'ghi chú: giao thứ 7' = note
|
| 8 |
*/
|
| 9 |
(function(){
|
| 10 |
'use strict';
|
|
|
|
| 59 |
});
|
| 60 |
if(added>0){
|
| 61 |
saveOrders(localOrders);
|
| 62 |
+
console.log('[OS v19.2] ☁️ Synced '+added+' orders from server. Total local: '+localOrders.length);
|
| 63 |
}
|
| 64 |
}).catch(function(e){
|
| 65 |
+
console.log('[OS v19.2] 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 v19.2] 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||'',notes:order.notes||[],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||'',notes:order.notes||[],};
|
| 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!');})
|
|
|
|
| 132 |
function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[̀-ͯ]/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;}
|
| 135 |
+
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;}}
|
| 136 |
function esc(s){return(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
| 137 |
function parsePrompt(text){
|
| 138 |
+
var fees=[],deposit=0,discountPercent=0,notes=[],itemDiscounts={};
|
| 139 |
+
if(!text)return{fees:fees,deposit:deposit,discountPercent:discountPercent,notes:notes,itemDiscounts:itemDiscounts};
|
| 140 |
+
text.split(/[,;\n]+/).forEach(function(line){
|
|
|
|
| 141 |
line=line.trim();if(!line)return;
|
| 142 |
parseItemDiscountLine(line,itemDiscounts);
|
| 143 |
+
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;
|
| 144 |
var lo=line.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d');
|
| 145 |
+
// Ghi chú (FIX: always extract text after prefix, never parse numbers)
|
| 146 |
+
if(lo.match(/^(ghi chu|note|luu y)/)){notes.push(line.replace(/^(ghi chú|ghi chu|note|lưu ý|luu y)[:\s]*/i,'').trim());return;}
|
| 147 |
+
// CK (FIX: also match 'WORD ck X%')
|
| 148 |
var ck=lo.match(/^(ck|chiet khau|giam|discount)\s*([\d.,]+)\s*(%|k|tr)?/);
|
| 149 |
if(ck){var v=parseFloat(ck[2].replace(/\./g,'').replace(',','.'));if((!ck[3]||ck[3]===''||ck[3]==='%')&&v>0&&v<=100)discountPercent=v;return;}
|
| 150 |
+
var ck2=lo.match(/\bck\s*([\d.,]+)\s*(%)?/);
|
| 151 |
+
if(ck2&&!lo.match(/\b(ck|chiet khau|giam|discount)\s*[a-z]/)){var v2=parseFloat(ck2[1].replace(/\./g,'').replace(',','.'));if(v2>0&&v2<=100){discountPercent=v2;return;}}
|
| 152 |
// Cọc
|
| 153 |
+
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;}else{if(u2==='k')a*=1000;if(u2==='tr'||u2==='trieu')a*=1000000;deposit=a;}}return;}
|
| 154 |
+
// FIX: "giao thứ X" / "lắp thứ X" = note about delivery day
|
| 155 |
+
if(lo.match(/(giao|lap)\s*thu\s*\d/i)){
|
| 156 |
+
var dayMatch = lo.match(/(giao|lap)\s*thu\s*(\d+)/i);
|
| 157 |
+
if(dayMatch) {notes.push('Giao thứ ' + dayMatch[2]);}
|
| 158 |
+
return;
|
| 159 |
+
}
|
| 160 |
// Phụ phí: giao hàng, lắp đặt, vận chuyển, phí khác
|
| 161 |
+
var feeMatch=lo.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi giao|phi khac|phu phi|phi)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 162 |
if(feeMatch){
|
| 163 |
var amt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.'));
|
| 164 |
var u=(feeMatch[3]||'').toLowerCase();
|
|
|
|
| 167 |
fees.push({label:labelMap[feeMatch[1]]||'Phụ phí',amount:amt});
|
| 168 |
return;
|
| 169 |
}
|
| 170 |
+
// Fallback: line with number (skip if looks like day note)
|
| 171 |
var m=lo.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 172 |
+
if(m && !lo.match(/thu\s*(\d|$)/i)){
|
| 173 |
+
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){
|
| 174 |
+
var lb=line.replace(m[0],'').trim();
|
| 175 |
+
if(!lb||lb.length<2)lb='Phụ phí';fees.push({label:lb,amount:amt2});
|
| 176 |
+
}}
|
| 177 |
});
|
| 178 |
+
return{fees:fees,deposit:deposit,discountPercent:discountPercent,notes:notes,itemDiscounts:itemDiscounts};
|
| 179 |
}
|
| 180 |
|
| 181 |
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);});}
|
| 182 |
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;}
|
| 183 |
|
| 184 |
+
// FIX TRIỆT ĐỂ: lấy date từ input khách hàng nếu có
|
| 185 |
+
function _getCustomerDateFromInput(){
|
| 186 |
+
var sels=['#quoteDate','#quoteNgay','#checkoutDate','input[placeholder*="ngày"]','input[placeholder*="date"]'];
|
| 187 |
+
for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.value&&el.value.trim())return el.value.trim();}
|
| 188 |
+
return '';
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
function saveCurrentOrder(){
|
| 192 |
+
if(!window.VAI_QR||!window.VAI_QR.getData)return;
|
| 193 |
+
var d=window.VAI_QR.getData();
|
| 194 |
+
var qd=d.qd;
|
| 195 |
+
var code=window.VAI_QR.getEffectiveOrderCode();
|
| 196 |
+
// FIX: lấy date từ input khách hàng ưu tiên, fallback ngày hiện tại
|
| 197 |
+
var inputDate=_getCustomerDateFromInput();
|
| 198 |
+
var orderDate=inputDate||qd.customer.date||new Date().toLocaleDateString('vi-VN');
|
| 199 |
+
var order={
|
| 200 |
+
code:code,
|
| 201 |
+
customer:qd.customer.name||'',
|
| 202 |
+
phone:qd.customer.phone||'',
|
| 203 |
+
email:qd.customer.email||'',
|
| 204 |
+
addr:qd.customer.addr||'',
|
| 205 |
+
date:orderDate,
|
| 206 |
+
items:qd.items||[],
|
| 207 |
+
fees:d.fees||[],
|
| 208 |
+
deposit:d.deposit||0,
|
| 209 |
+
discountPercent:d.discountPercent||0,
|
| 210 |
+
itemDiscounts:d.itemDiscounts||{},
|
| 211 |
+
notes:d.notes||[],
|
| 212 |
+
grandTotal:d.grandTotal||0,
|
| 213 |
+
remaining:d.remaining||0,
|
| 214 |
+
promptText:window.VAI_QR.getPromptText?window.VAI_QR.getPromptText():'',
|
| 215 |
+
status:'pending',
|
| 216 |
+
savedAt:new Date().toISOString()
|
| 217 |
+
};
|
| 218 |
+
recalcOrder(order);
|
| 219 |
+
addOrder(order);
|
| 220 |
+
syncOrderToBot(order);
|
| 221 |
+
if(typeof showToast==='function')showToast('✅ Đã lưu đơn '+code);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// FIX TRIỆT ĐỂ: productTotal = tổng tiền SP (KHÔNG trừ phí); notes ưu tiên order.notes
|
| 225 |
+
function orderToVAIData(order){
|
| 226 |
+
recalcOrder(order);
|
| 227 |
+
var pt=0;(order.items||[]).forEach(function(it){pt+=it.total||0;});
|
| 228 |
+
var notes=order.notes&&order.notes.length?order.notes:[];
|
| 229 |
+
return{
|
| 230 |
+
qd:{
|
| 231 |
+
customer:{name:order.customer||'',phone:order.phone||'',email:order.email||'',addr:order.addr||'',date:order.date||''},
|
| 232 |
+
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||''};}),
|
| 233 |
+
grandTotal:order.grandTotal||0
|
| 234 |
+
},
|
| 235 |
+
fees:order.fees||[],
|
| 236 |
+
notes:notes,
|
| 237 |
+
discount:null,
|
| 238 |
+
discountPercent:order.discountPercent||0,
|
| 239 |
+
itemDiscounts:order.itemDiscounts||{},
|
| 240 |
+
deposit:order.deposit||0,
|
| 241 |
+
productTotal:pt,
|
| 242 |
+
grandTotal:order.grandTotal||0,
|
| 243 |
+
remaining:order.remaining||0
|
| 244 |
+
};
|
| 245 |
+
}
|
| 246 |
|
|
|
|
| 247 |
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);}}
|
| 248 |
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));}}
|
| 249 |
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));}}
|
|
|
|
| 251 |
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));}}
|
| 252 |
|
| 253 |
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;
|
|
|
|
| 254 |
document.getElementById('vai-os-refresh').onclick=function(){
|
| 255 |
this.textContent='⏳';var self=this;
|
| 256 |
syncFromServer();
|
| 257 |
setTimeout(function(){self.textContent='🔄';ov.remove();openSearchModal();},2000);
|
| 258 |
};
|
| 259 |
+
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();};});
|
| 260 |
+
}
|
| 261 |
|
| 262 |
+
function openOrderDetail(order){
|
| 263 |
+
var old=document.getElementById('vai-order-detail-modal');if(old)old.remove();
|
| 264 |
+
order=JSON.parse(JSON.stringify(order));
|
| 265 |
+
// FIX: đảm bảo notes luôn là mảng
|
| 266 |
+
if(!order.notes)order.notes=[];
|
| 267 |
+
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);
|
| 268 |
var _searchOpen=false;
|
| 269 |
function addProductToOrder(product){
|
| 270 |
var pn=product.priceNum||product.pn||0;
|
|
|
|
| 340 |
+'<div style="font-size:11px;font-weight:700;color:#003f62;white-space:nowrap">'+fmt(price)+'</div>'
|
| 341 |
+'<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>';
|
| 342 |
}).join('');
|
|
|
|
| 343 |
el.querySelectorAll('.od-sp-item').forEach(function(item){
|
| 344 |
item.onclick=function(e){
|
| 345 |
if(e.target.closest('.od-sp-add'))return;
|
|
|
|
| 357 |
};
|
| 358 |
});
|
| 359 |
}
|
| 360 |
+
function render(){
|
| 361 |
+
recalcOrder(order);var isC=order.status==='confirmed';
|
| 362 |
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">'
|
| 363 |
+
+'<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 style="grid-column:1/-1"><b>Ngày:</b> <input id="od-date" value="'+esc(order.date||'')+'" placeholder="DD/MM/YYYY" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:40%;font-size:11px"></div></div>'
|
| 364 |
+'<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>'
|
| 365 |
+
+'<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 style="padding:5px;text-align:left">Ghi chú</th><th></th></tr></thead><tbody>';
|
| 366 |
+
(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;font-size:9px;max-width:120px;white-space:normal">'+esc(it.note||'')+'</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>';});
|
| 367 |
h+='</tbody></table>';
|
| 368 |
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>';}
|
| 369 |
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>';
|
| 370 |
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>';
|
| 371 |
+
else h+='<div style="padding:4px 10px;background:#fef2f2;border-radius:6px;font-size:10px;margin-bottom:6px"><span style="font-weight:800">Còn lại (100%):</span> <b style="color:#dc2626">'+fmt(order.grandTotal)+'</b></span></div>';
|
| 372 |
+
if(order.notes&&order.notes.length)h+='<div style="padding:4px 10px;background:#f0fdf4;border-radius:6px;font-size:10px;margin-bottom:6px;color:#166534"><b>📝 Ghi chú:</b> '+esc(order.notes.join('; '))+'</div>';
|
| 373 |
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>'
|
| 374 |
+'<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>'
|
| 375 |
+'<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>';
|
|
|
|
| 380 |
h+='</div>';modal.innerHTML=h;
|
| 381 |
document.getElementById('xc').onclick=function(){ov.remove();};
|
| 382 |
modal.querySelectorAll('.xd').forEach(function(b){b.onclick=function(e){e.stopPropagation();order.items.splice(parseInt(this.dataset.i),1);render();};});
|
|
|
|
| 383 |
var addSpBtn=document.getElementById('od-add-sp');
|
| 384 |
if(addSpBtn){addSpBtn.onclick=toggleSearchPanel;}
|
|
|
|
| 385 |
var closeSpBtn=document.getElementById('od-sp-close');
|
| 386 |
if(closeSpBtn){closeSpBtn.onclick=function(){_searchOpen=false;render();};}
|
|
|
|
| 387 |
var searchInput=document.getElementById('od-sp-search');
|
| 388 |
if(searchInput){searchInput.oninput=function(){renderSearchResults();};searchInput.onkeydown=function(e){if(e.key==='Escape'){_searchOpen=false;render();}};}
|
|
|
|
| 389 |
if(_searchOpen){renderSearchResults();}
|
| 390 |
+
function cc(){
|
| 391 |
+
order.customer=document.getElementById('od-name').value;
|
| 392 |
+
order.phone=document.getElementById('od-phone').value;
|
| 393 |
+
order.addr=document.getElementById('od-addr').value;
|
| 394 |
+
// FIX: cập nhật date từ input
|
| 395 |
+
var dt=document.getElementById('od-date');
|
| 396 |
+
if(dt)order.date=dt.value.trim();
|
| 397 |
+
}
|
| 398 |
var mI=document.getElementById('od-makh'),sB=document.getElementById('od-makh-suggest'),mInfo=document.getElementById('od-makh-info');
|
| 399 |
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);};}
|
| 400 |
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();}
|
| 401 |
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();};
|
| 402 |
+
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!');};
|
| 403 |
document.getElementById('od-pdf').onclick=function(){cc();recalcOrder(order);exportPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 404 |
document.getElementById('od-xl').onclick=function(){cc();recalcOrder(order);exportExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 405 |
document.getElementById('od-gh').onclick=function(){cc();recalcOrder(order);exportDeliveryPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
|
|
|
| 423 |
|
| 424 |
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};
|
| 425 |
var qLen=getQueue().length;
|
| 426 |
+
console.log('[OS v19.2] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled | ➕ Add product to order enabled | FIX: notes + CÒN LẠI 100% + date saved');
|
| 427 |
+
})();
|