FIX: Thêm phí giao hàng, lắp đặt, cọc vào Excel báo giá

#24
by bep40 - opened
Files changed (1) hide show
  1. qr-payment.js +550 -179
qr-payment.js CHANGED
@@ -1,195 +1,566 @@
1
- /**
2
- * V.AI STUDIO - Excel Export v1102
3
- * FIX: Hình ảnh 100x100px trong Excel
4
- */
5
- (function(){
6
- 'use strict';
7
-
8
- function fmt(n){
9
- if(!n||isNaN(n))return'0đ';
10
- return Number(n).toLocaleString('vi-VN')+'đ';
11
- }
12
-
13
- function compactKey(s){
14
- return (s||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[Đđ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');
15
- }
16
-
17
- async function fetchImageArrayBuffer(url){
18
- if(!url||!url.startsWith('http')) return null;
19
- try{
20
- var r = await fetch(url,{mode:'cors'});
21
- if(r.ok) return await r.arrayBuffer();
22
- }catch(e){}
23
- try{
24
- var r2 = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url));
25
- if(r2.ok) return await r2.arrayBuffer();
26
- }catch(e){}
27
- return null;
28
- }
29
-
30
- function generateOrderCode(name){
31
- var now = new Date(), dd=String(now.getDate()).padStart(2,'0'),
32
- mm=String(now.getMonth()+1).padStart(2,'0'), yy=String(now.getFullYear()).slice(-2), ini='';
33
- if(name && name.trim()){
34
- name.trim().split(/\s+/).forEach(function(w){
35
- var ch = stripDiacritics(w.charAt(0)).toUpperCase();
36
- if(ch && /[A-Z]/.test(ch)) ini += ch;
37
- });
38
- }
39
- return 'VAS'+(ini||'X')+dd+mm+yy;
40
- }
41
-
42
- function stripDiacritics(s){
43
- return s.normalize('NFD').replace(/[̀-ͯ]/g,'');
44
- }
45
-
46
- function getEffectiveOrderCode(){
47
- var n = document.getElementById('quoteName')?.value || '';
48
- if(n && n.trim()) return generateOrderCode(n);
49
- var h = window.location.hash;
50
- if(h && h.length>1 && /^VAS[A-Z0-9]{3,}$/.test(h.substring(1)))
51
- return h.substring(1);
52
- return generateOrderCode('');
53
- }
54
-
55
- function getQRUrl(amount, code){
56
- return 'https://img.vietqr.io/image/vib-918258385-compact2.png?amount='+(Math.round(amount||0))+'&addInfo='+encodeURIComponent(code||'VAISTUDIO')+'&accountName='+encodeURIComponent('TRAN QUOC VUONG');
57
- }
58
-
59
- // MAIN EXPORT EXCEL - compatible with order-store.js call
60
- async function _doExportExcel(d, qd, code, qrUrl, blackWhite){
61
- qd = qd || d?.qd;
62
- code = code || getEffectiveOrderCode();
63
- qrUrl = qrUrl || getQRUrl(d?.grandTotal || qd?.grandTotal || 0, code);
64
-
65
- var items = (qd?.items || window.cart || []).map(function(it, i){
66
- var p = window.D && D[it.idx] ? D[it.idx] : null;
67
- return {
68
- stt: i+1,
69
- name: it.name || (p?p.name||p.n:'') || '',
70
- model: it.model || (p?p.sku||p.model:'') || '',
71
- image: p?.image || it.image || it.img || '',
72
- specs: it.specs || (p && p.specs ? formatSpecs(p.specs) : '') || '',
73
- qty: Number(it.qty||1),
74
- price: Number(it.price||it.priceNum||0),
75
- discPrice: Number(it.discPrice||it.priceNum||it.price||0),
76
- total: Number(it.total||(Number(it.discPrice||it.priceNum||0)*Number(it.qty||1))||0),
77
- note: it.note || ''
78
- };
79
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- if(!items.length){
82
- alert('❌ Giỏ trống!');
83
- return;
84
- }
85
-
86
- try{
87
- var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá');
88
-
89
- ws.columns = [
90
- {width:6},{width:15},{width:30},{width:15},
91
- {width:22},{width:8},{width:15},{width:15},{width:18},{width:16}
92
- ];
93
 
94
- // Header
95
- ws.mergeCells('A3:J3');
96
- ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
97
- ws.getCell('A3').font = {bold:true, size:18, color:{argb:'FFDB9815'}};
98
- ws.getRow(3).height = 30;
99
-
100
- var headerRow = ws.getRow(10);
101
- ['STT','Hình','Tên sản phẩm','Mã SP','Thông tin','SL','Đơn giá','Giá CK','Thành tiền','Ghi chú'].forEach(function(h,i){
102
- var c = headerRow.getCell(i+1);
103
- c.value = h;
104
- c.font = {bold:true, color:{argb:'FFFFFFFF'}, size:9};
105
- c.fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF003F62'}};
106
- c.alignment = {horizontal:'center', vertical:'middle'};
107
- });
108
- headerRow.height = 20;
109
 
110
- // Items with 100x100px images
111
- var startRow = 11;
112
- await Promise.all(items.map(async function(it, i){
113
- var row = ws.getRow(startRow + i);
114
- row.height = 120; // 100px + padding
115
-
116
- var bgColor = i % 2 === 0 ? 'FFF8FAFC' : 'FFFFFFFF';
117
-
118
- row.getCell(1).value = it.stt;
119
- row.getCell(1).alignment = {horizontal:'center', vertical:'middle'};
120
-
121
- // IMAGE 100x100px with CORS fallback
122
- if(it.image && it.image.startsWith('http')){
123
- var imgBuf = await fetchImageArrayBuffer(it.image);
124
- if(imgBuf){
125
- try{
126
- wb.addImage(wb.addImage({buffer:imgBuf, extension:'png'}),
127
- {tl:{col:1, row:startRow + i - 1}, ext:{width:100, height:100}});
128
- }catch(e){}
129
- }
130
  }
131
-
132
- row.getCell(3).value = it.name; row.getCell(3).font = {bold:true, size:9};
133
- row.getCell(4).value = it.model;
134
- row.getCell(5).value = it.specs;
135
- row.getCell(6).value = it.qty;
136
- row.getCell(7).value = it.price; row.getCell(7).numFmt = '#,##0"đ"';
137
- row.getCell(8).value = it.discPrice; row.getCell(8).numFmt = '#,##0"đ"';
138
- row.getCell(9).value = it.total; row.getCell(9).numFmt = '#,##0"đ"';
139
- row.getCell(10).value = it.note;
140
-
141
- for(var ci=1; ci<=10; ci++){
142
- row.getCell(ci).fill = {type:'pattern', pattern:'solid', fgColor:{argb:bgColor}};
143
- }
144
- }));
145
-
146
- var lastRow = startRow + items.length - 1;
147
- // Total row
148
- ws.getCell('A'+(lastRow+2)).value = 'TỔNG CỘNG';
149
- ws.getCell('A'+(lastRow+2)).font = {bold:true, size:13, color:{argb:'FFFFFFFF'}};
150
- ws.getCell('A'+(lastRow+2)).fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF003F62'}};
151
- ws.getCell('G'+(lastRow+2)).value = {formula:'SUM(G'+startRow+':G'+lastRow+')'};
152
- ws.getCell('G'+(lastRow+2)).numFmt = '#,##0"đ"';
153
- ws.getCell('G'+(lastRow+2)).font = {bold:true, size:14, color:{argb:'FFF0B840'}};
154
 
155
- wb.calcProperties.fullCalcOnLoad = true;
156
- var buf = await wb.xlsx.writeBuffer();
157
- var blob = new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
158
- var dlUrl = URL.createObjectURL(blob);
159
- var a = document.createElement('a');
160
- a.href = dlUrl;
161
- a.download = code + '.xlsx';
162
- document.body.appendChild(a);
163
- a.click();
164
- setTimeout(function(){ document.body.removeChild(a); URL.revokeObjectURL(dlUrl); }, 30000);
 
 
165
 
166
- console.log('[VAI v1102] Excel export completed with 100x100px images');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- }catch(e){
169
- console.error('[VAI v1102] Lỗi export Excel:', e);
170
- alert('❌ Lỗi: ' + (e.message || e));
 
 
 
 
 
 
 
 
 
 
 
171
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
 
174
- function formatSpecs(specs){
175
- if(!specs) return '';
176
- if(typeof specs === 'string') return specs.slice(0,100);
177
- if(typeof specs === 'object'){
178
- var parts = [];
179
- Object.entries(specs).forEach(function(e){
180
- if(e[1] != null) parts.push(e[0] + ': ' + e[1]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  });
182
- return parts.join('; ').slice(0,100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
- return '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  }
186
 
187
- // Register to VAI_QR
188
- if(!window.VAI_QR) window.VAI_QR = {};
189
- window.VAI_QR.exportExcel = _doExportExcel;
190
- window.VAI_QR.exportDeliveryExcel = function(qd, code){ /* not implemented for now */ };
191
- window.VAI_QR.getEffectiveOrderCode = getEffectiveOrderCode;
192
- window.VAI_QR.getQRUrl = getQRUrl;
193
- window.VAI_QR.fmt = fmt;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
- console.log('[VAI v1102] Script loaded - Excel with 100px images ready');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* V.AI STUDIO — QR Payment / Quote Data v1043 - FIX: THÊM PHÍ GIAO HÀNG, LẮP ĐẶT, CỌC VÀO EXCEL */
2
+ (function(){'use strict';
3
+
4
+ // ===== BLOB REGISTRY =====
5
+ window.__VAI_BLOB_REG = window.__VAI_BLOB_REG || { blobs: [], isUnloading: false };
6
+ var blobRegistry = window.__VAI_BLOB_REG.blobs;
7
+
8
+ function cleanupBlobs(){
9
+ window.__VAI_BLOB_REG.isUnloading = true;
10
+ blobRegistry.forEach(function(e) {
11
+ if (!e.revoked) { try { URL.revokeObjectURL(e.url); e.revoked = true; } catch(err) {} }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  });
13
+ blobRegistry = [];
14
+ }
15
+ window.addEventListener('pagehide', cleanupBlobs);
16
+ window.addEventListener('beforeunload', cleanupBlobs);
17
+
18
+ function dlBlob(blob, filename) {
19
+ var url = URL.createObjectURL(blob);
20
+ blobRegistry.push({ url: url, revoked: false });
21
+ var a = document.createElement('a');
22
+ a.href = url;
23
+ a.download = filename;
24
+ a.style.cssText = 'position:absolute;left:-9999px';
25
+ document.body.appendChild(a);
26
+ a.click();
27
+ setTimeout(function() { try { document.body.removeChild(a); } catch(err) {} }, 100);
28
+ }
29
+
30
+ var BANK_ID='vib',BANK_ACCOUNT='918258385',BANK_NAME='TRAN QUOC VUONG';
31
+ function stripDiacritics(s){return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D');}
32
+ function generateOrderCode(name){var now=new Date(),dd=String(now.getDate()).padStart(2,'0'),mm=String(now.getMonth()+1).padStart(2,'0'),yy=String(now.getFullYear()).slice(-2),ini='';if(name&&name.trim()){name.trim().split(/\s+/).forEach(function(w){if(w){var ch=stripDiacritics(w.charAt(0)).toUpperCase();if(/[A-Z]/.test(ch))ini+=ch;}});}return 'VAS'+(ini||'X')+dd+mm+yy;}
33
+ function getCustomerName(){var sels=['#quoteName','#quoteCustomerName','#checkoutName','.quote-field input:first-of-type'];for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.value&&el.value.trim().length>1)return el.value.trim();}return '';}
34
+ function getEffectiveOrderCode(){var n=getCustomerName();if(n)return generateOrderCode(n);var h=window.location.hash;if(h&&h.length>1){var c=h.substring(1);if(/^VAS[A-Z0-9]{3,}$/.test(c))return c;}return generateOrderCode('');}
35
+ function getQRUrl(a,c){return'https://img.vietqr.io/image/'+BANK_ID+'-'+BANK_ACCOUNT+'-compact2.png?amount='+(Math.round(a)||0)+'&addInfo='+encodeURIComponent(c||'VAISTUDIO')+'&accountName='+encodeURIComponent(BANK_NAME);}
36
+ function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
37
+ function compactKey(s){return stripDiacritics(String(s||'')).toLowerCase().replace(/[^a-z0-9]/g,'');}
38
+ 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;});}
39
+ function matchItemDiscount(it,itemDiscounts){itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);for(var i=0;i<keys.length;i++){var k=keys[i];if(itemDiscounts[k])return itemDiscounts[k];}for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var kk=keys[j];if(code.length>=3&&kk.length>=3&&(kk.indexOf(code)!==-1||code.indexOf(kk)!==-1))return itemDiscounts[code];}}return 0;}
40
+ function parseItemDiscountLine(line,map){var lo=stripDiacritics(String(line||'')).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;}}
41
+
42
+ function applyPromptDiscounts(qd,parsed){if(!qd||!qd.items)return qd;var productTotal=0;var itemDiscounts=(parsed&&parsed.itemDiscounts)||{};var globalPct=Number((parsed&&parsed.discountPercent)||0);qd.items.forEach(function(it){var base=Number(it.price||0),qty=Number(it.qty||1);var pct=matchItemDiscount(it,itemDiscounts);if(pct>0){it.discPrice=Math.round(base*(1-pct/100));it.discountPercent=pct;}else if(globalPct>0&&globalPct<=100){it.discPrice=Math.round(base*(1-globalPct/100));it.discountPercent=globalPct;}else if(!it.discPrice){it.discPrice=base;}it.total=Number(it.discPrice||base)*qty;productTotal+=Number(it.total||0);});qd.grandTotal=productTotal;return qd;}
43
+
44
+ function parsePrompt(text){
45
+ var fees=[],notes=[],discount=null,deposit=0,discountPercent=0,itemDiscounts={};
46
+ if(!text)return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
47
 
48
+ text.split(/[,;\n]+/).forEach(function(line){
49
+ line=line.trim();if(!line)return;
50
+ parseItemDiscountLine(line,itemDiscounts);
 
 
 
 
 
 
 
 
 
51
 
52
+ 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;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ // CK
55
+ if(line.match(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)/i)){
56
+ discount=line;
57
+ var ckm=line.match(/([\d.,]+)\\s*(%)?/);
58
+ if(ckm){
59
+ var v=parseFloat(ckm[1].replace(/\\./g,'').replace(',','.'));
60
+ if(v>0&&v<=100)discountPercent=v;
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
+ return;
63
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ // Cọc
66
+ if(line.match(/cọc|coc|deposit/i)){
67
+ var dm=line.match(/([\d.,]+)\\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/);
68
+ if(dm){
69
+ var da=parseFloat(dm[1].replace(/\\./g,'').replace(',','.'));
70
+ var du=(dm[2]||'').toLowerCase();
71
+ if(du==='k'||du==='nghìn'||du==='nghin')da*=1000;
72
+ if(du==='triệu'||du==='tr'||du==='m')da*=1000000;
73
+ deposit=da;
74
+ }
75
+ return;
76
+ }
77
 
78
+ // Phí giao hàng, lắp đặt - FIX QUAN TRỌNG
79
+ var feeMatch=line.match(/([\d.,]+)\\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/);
80
+ if(feeMatch){
81
+ var amt=parseFloat(feeMatch[1].replace(/\\./g,'').replace(',','.'));
82
+ var u=(feeMatch[2]||'').toLowerCase();
83
+ if(u==='k'||u==='nghìn'||u==='nghin')amt*=1000;
84
+ if(u==='triệu'||u==='tr'||u==='m')amt*=1000000;
85
+ if(amt>0){
86
+ var lb=line.replace(feeMatch[0],'').trim();
87
+ // Xác định loại phí
88
+ var lo=line.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d');
89
+ if(lo.match(/giao|ship|phi giao|phí giao/))lb='Phí giao hàng';
90
+ else if(lo.match(/lap|install|phi lap|phí lắp/))lb='Phí lắp đặt';
91
+ else if(lo.match(/bốc|boc|xep|phi bot/))lb='Phí bốc xếp';
92
+ else if(!lb||lb.length<2)lb='Phụ phí';
93
+ fees.push({label:lb,amount:amt});
94
+ }
95
+ return;
96
+ }
97
 
98
+ notes.push(line);
99
+ });
100
+ return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
101
+ }
102
+
103
+ var _lastPromptText='';
104
+ function getPromptText(){
105
+ var direct=document.getElementById('qcAiPrompt');
106
+ if(direct){
107
+ var dv=(direct.value||'').trim();
108
+ if(dv&&dv.toUpperCase().replace(/[\s.]/g,'')!=='VAISTUDIO'){
109
+ _lastPromptText=dv;
110
+ return dv;
111
+ }
112
  }
113
+ var inputs=document.querySelectorAll('input,textarea');
114
+ for(var j=0;j<inputs.length;j++){
115
+ var ph=(inputs[j].placeholder||'').toLowerCase();
116
+ if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('yeu cau')||ph.includes('chiet khau')||ph.includes('ck')||ph.includes('giao hàng')){
117
+ var val=(inputs[j].value||'').trim();
118
+ if(!val||val.toUpperCase().replace(/[\s.]/g,'')==='VAISTUDIO')continue;
119
+ _lastPromptText=val;
120
+ return val;
121
+ }
122
+ }
123
+ return _lastPromptText;
124
+ }
125
+
126
+ function getData(){
127
+ var qd=window._origGetQuoteData?window._origGetQuoteData():getQuoteData();
128
+ var parsed=parsePrompt(getPromptText());
129
+ qd=applyPromptDiscounts(qd,parsed);
130
+ var surcharge=0;
131
+ parsed.fees.forEach(function(f){surcharge+=f.amount;});
132
+ var grandTotal=(qd.grandTotal||0)+surcharge;
133
+ var remaining=grandTotal-parsed.deposit;
134
+ return{qd:qd,fees:parsed.fees,notes:parsed.notes,discount:parsed.discount,deposit:parsed.deposit,grandTotal:grandTotal,remaining:remaining>0?remaining:0};
135
+ }
136
+
137
+ function orderToVAIData(order){
138
+ return{
139
+ qd:{
140
+ customer:{name:order.customer||'',phone:order.phone||'',email:order.email||'',addr:order.addr||'',date:order.date||''},
141
+ items:(order.items||[]).map(function(it,i){
142
+ return{
143
+ stt:i+1,
144
+ image:it.image||'',
145
+ name:it.name||'',
146
+ model:it.model||'',
147
+ specs:it.specs||it.info||'',
148
+ qty:it.qty||1,
149
+ price:it.price||it.listPrice||it.discPrice||0,
150
+ discPrice:it.discPrice||it.price||0,
151
+ total:it.total||0,
152
+ note:it.note||''
153
+ };
154
+ }),
155
+ grandTotal:order.grandTotal||0
156
+ },
157
+ fees:order.fees||[],
158
+ deposit:order.deposit||0,
159
+ remaining:order.remaining||0
160
+ };
161
  }
162
 
163
+ function setExportButtonState(btn,exporting){
164
+ if(!btn)return;
165
+ if(exporting){
166
+ btn.disabled=true;
167
+ btn.dataset.vaiExporting='1';
168
+ btn.dataset.vaiOrigText=btn.innerHTML;
169
+ btn.innerHTML='<span style="opacity:0.7">⏳</span>';
170
+ }else{
171
+ btn.disabled=false;
172
+ btn.dataset.vaiExporting='0';
173
+ if(btn.dataset.vaiOrigText)btn.innerHTML=btn.dataset.vaiOrigText;
174
+ }
175
+ }
176
+
177
+ function injectToModal(){
178
+ 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"]');
179
+ if(!modal)return;
180
+ var old=modal.querySelector('.vai-extra-section');
181
+ old&&old.remove();
182
+ var parsed=parsePrompt(getPromptText());
183
+ if(!parsed.fees.length&&!parsed.deposit)return;
184
+ var surcharge=0;
185
+ parsed.fees.forEach(function(f){surcharge+=f.amount;});
186
+ var qd=window._origGetQuoteData?window._origGetQuoteData():(typeof getQuoteData==='function'?getQuoteData():{grandTotal:0});
187
+ var grandTotal=(qd.grandTotal||0)+surcharge;
188
+ var sec=document.createElement('div');
189
+ sec.className='vai-extra-section';
190
+ sec.style.cssText='margin:10px 0;padding:0 16px;font-size:13px';
191
+ var html='';
192
+ if(parsed.fees.length){
193
+ html+='<div style="padding:8px 12px;background:#fffbeb;border-radius:6px;border:1px solid #fde68a;margin-bottom:8px"><div style="font-weight:700;color:#92400e;font-size:12px;margin-bottom:4px">PHỤ PHÍ</div>';
194
+ parsed.fees.forEach(function(f){
195
+ html+='<div style="display:flex;justify-content:space-between;padding:2px 0"><span>'+f.label+'</span><span style="font-weight:700">'+fmt(f.amount)+'</span></div>';
196
+ });
197
+ html+='</div>';
198
+ }
199
+ html+='<div style="padding:8px 12px;background:#003f62;border-radius:6px;display:flex;justify-content:space-between;align-items:center"><span style="color:#fff;font-weight:800">TỔNG CỘNG</span><span style="color:#f0b840;font-weight:900;font-size:16px">'+fmt(grandTotal)+'</span></div>';
200
+ sec.innerHTML=html;
201
+ var actions=modal.querySelector('.quote-actions,[class*="actions"],[class*="button"]');
202
+ if(actions&&actions.parentNode)actions.parentNode.insertBefore(sec,actions);
203
+ else modal.appendChild(sec);
204
+ }
205
+
206
+ async function toCanvas(html,w){
207
+ var div=document.createElement('div');
208
+ div.style.cssText='position:absolute;left:-10000px;top:0;width:'+(w||1000)+'px;background:#fff;padding:28px;font-family:Inter,Arial,DejaVu Sans,sans-serif;z-index:-1;box-sizing:border-box';
209
+ div.innerHTML=html;
210
+ document.body.appendChild(div);
211
+ try{if(document.fonts&&document.fonts.ready)await document.fonts.ready;}catch(e){}
212
+ var imgs=Array.from(div.querySelectorAll('img'));
213
+ await Promise.all(imgs.map(function(img){
214
+ return new Promise(function(res){
215
+ if(img.complete)return res();
216
+ img.onload=img.onerror=function(){res();};
217
+ setTimeout(res,2500);
218
  });
219
+ }));
220
+ await new Promise(function(r){setTimeout(r,500);});
221
+ var h=Math.max(div.scrollHeight,div.offsetHeight);
222
+ var cv=await html2canvas(div,{scale:2,useCORS:true,allowTaint:false,backgroundColor:'#fff',width:w||1000,height:h,windowWidth:w||1000,windowHeight:h,scrollX:0,scrollY:0});
223
+ div.remove();
224
+ return cv;
225
+ }
226
+
227
+ function buildQuoteHTML(d,code,qrUrl){
228
+ var qd=d.qd;
229
+ var rows=qd.items.map(function(it){
230
+ return '<tr style="border-bottom:1px solid #e2e8f0">'
231
+ +'<td style="padding:6px 4px;text-align:center;font-size:11px">'+(it.stt||'')+'</td>'
232
+ +'<td style="padding:6px 4px"><img src="'+(it.image||'')+'" style="width:50px;height:50px;object-fit:contain;border-radius:4px" onerror="this.style.display=\'none\'"></td>'
233
+ +'<td style="padding:6px 4px;font-size:11px;font-weight:600">'+(it.name||'')+'</td>'
234
+ +'<td style="padding:6px 4px;text-align:center;font-size:10px">'+(it.model||'')+'</td>'
235
+ +'<td style="padding:6px 4px;font-size:9px;color:#64748b">'+(it.specs||'')+'</td>'
236
+ +'<td style="padding:6px 4px;text-align:center">'+(it.qty||'')+'</td>'
237
+ +'<td style="padding:6px 4px;text-align:right;font-size:11px">'+(it.price?fmt(it.price):'')+'</td>'
238
+ +'<td style="padding:6px 4px;text-align:right;font-size:11px">'+(it.discPrice?fmt(it.discPrice):'')+'</td>'
239
+ +'<td style="padding:6px 4px;text-align:right;font-size:11px;font-weight:700">'+(it.total?fmt(it.total):'')+'</td>'
240
+ +'<td style="padding:6px 4px;font-size:9px">'+(it.note||'')+'</td>'
241
+ +'</tr>';
242
+ }).join('');
243
+ var feeRows='';
244
+ if(d.fees&&d.fees.length){
245
+ d.fees.forEach(function(f){
246
+ feeRows+='<tr style="background:#fffbeb"><td colspan="8" style="padding:5px 10px;font-size:11px;color:#92400e">⊕ '+f.label+'</td><td style="padding:5px 4px;text-align:right;font-size:11px;font-weight:700">'+fmt(f.amount)+'</td></tr>';
247
+ });
248
+ }
249
+ return '<div style="display:flex;align-items:center;gap:10px">'
250
+ +'<img src="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png" style="width:40px">'
251
+ +'</div>'
252
+ +'<div style="text-align:center;margin-bottom:12px">'
253
+ +'<div style="font-size:20px;font-weight:900;color:#db9815">BẢNG BÁO GIÁ</div>'
254
+ +'</div>'
255
+ +'<div style="display:flex;justify-content:space-between;margin-bottom:12px;font-size:11px">'
256
+ +'<div><b>Khách hàng:</b> '+(qd.customer.name||'')+'<br><b>SĐT:</b> '+(qd.customer.phone||'')+'</div>'
257
+ +'<div><b>Mã đơn:</b> '+code+'<br><b>Ngày:</b> '+(qd.customer.date||'')+'</div>'
258
+ +'</div>'
259
+ +'<table style="width:100%;border-collapse:collapse">'
260
+ +'<thead><tr style="background:#003f62;color:#fff">'
261
+ +'<th>STT</th><th>Hình</th><th>Tên SP</th><th>Mã</th><th>Thông tin</th><th>SL</th><th>Đơn giá</th><th>Giá CK</th><th>Thành tiền</th><th>Ghi chú</th>'
262
+ +'</tr></thead>'
263
+ +'<tbody>'+rows+feeRows+'</tbody>'
264
+ +'</table>'
265
+ +'<div style="padding:10px 14px;background:#003f62">'
266
+ +'<span style="color:#fff;font-weight:800">TỔNG CỘNG</span>'
267
+ +'<span style="color:#f0b840;font-weight:900;margin-left:10px">'+fmt(d.grandTotal)+'</span>'
268
+ +'</div>'
269
+ +(d.deposit>0?'<div style="margin-top:6px;padding:6px 12px;background:#f0fdf4;font-size:12px"><span>Cọc: <b>'+fmt(d.deposit)+'</b></span> | <span>Còn lại: <b style="color:#dc2626">'+fmt(d.remaining)+'</b></span></div>':'');
270
+ }
271
+
272
+ function buildDeliveryHTML(qd,code){
273
+ var rows=(qd.items||[]).map(function(it,i){
274
+ return '<tr style="border-bottom:1px solid #e2e8f0">'
275
+ +'<td style="padding:8px 4px;text-align:center;font-size:12px">'+(it.stt||(i+1))+'</td>'
276
+ +'<td style="padding:8px 4px;font-weight:600;font-size:12px">'+(it.name||'')+'</td>'
277
+ +'<td style="padding:8px 4px;text-align:center;font-size:11px">'+(it.model||'')+'</td>'
278
+ +'<td style="padding:8px 4px;text-align:center;font-weight:700;font-size:13px">'+(it.qty||1)+'</td>'
279
+ +'<td style="padding:8px 4px;font-size:11px">'+(it.note||'')+'</td>'
280
+ +'</tr>';
281
+ }).join('');
282
+ return '<div style="text-align:center;margin-bottom:14px">'
283
+ +'<div style="font-size:20px;font-weight:900;color:#003f62">PHIẾU GIAO HÀNG</div>'
284
+ +'</div>'
285
+ +'<div style="margin-bottom:12px;font-size:11px;line-height:1.6">'
286
+ +'<b>Khách hàng:</b> '+(qd.customer?qd.customer.name:'')+' | <b>SĐT:</b> '+(qd.customer?qd.customer.phone:'')+'<br>'
287
+ +'<b>Địa chỉ:</b> '+(qd.customer?qd.customer.addr||'':'')+'<br>'
288
+ +'<b>Mã đơn:</b> '+code+' | <b>Ngày:</b> '+(qd.customer?qd.customer.date||'':'')+'</div>'
289
+ +'<table style="width:100%;border-collapse:collapse;font-size:12px">'
290
+ +'<thead><tr style="background:#003f62;color:#fff">'
291
+ +'<th style="padding:8px 4px;width:35px">STT</th>'
292
+ +'<th style="padding:8px 4px;text-align:left">Tên sản phẩm</th>'
293
+ +'<th style="padding:8px 4px;width:90px">Mã SP</th>'
294
+ +'<th style="padding:8px 4px;width:40px">SL</th>'
295
+ +'<th style="padding:8px 4px;text-align:left;width:120px">Ghi chú</th>'
296
+ +'</tr></thead>'
297
+ +'<tbody>'+rows+'</tbody></table>'
298
+ +'<div style="margin-top:60px;display:flex;justify-content:space-around;font-size:12px">'
299
+ +'<div style="text-align:center"><b>Bên giao</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div>'
300
+ +'<div style="text-align:center"><b>Bên nhận</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div>'
301
+ +'</div>';
302
+ }
303
+
304
+ async function doOverride(){
305
+ if(typeof getQuoteData!=='function'||typeof html2canvas!=='function')return false;
306
+ window._origGetQuoteData=getQuoteData;
307
+ window.exportPDF=async function(){
308
+ var d=getData(),code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);
309
+ try{
310
+ await _doExportPDF(d,code);
311
+ }catch(e){
312
+ console.error(e);
313
+ alert('Không xuất được PDF: '+(e&&e.message?e.message:e));
314
+ }
315
+ };
316
+ window.exportDeliveryExcel=async function(order,opt){
317
+ if(typeof ExcelJS==='undefined')return;
318
+ var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();
319
+ var code=order&&order.code?order.code:getEffectiveOrderCode();
320
+ var activeBtn=document.activeElement;
321
+ if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;
322
+ if(activeBtn)setExportButtonState(activeBtn,true);
323
+ try{await _doExportDeliveryExcel(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,false);}
324
+ };
325
+ window.exportDeliveryPDF=async function(order,opt){
326
+ var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();
327
+ var code=order&&order.code?order.code:getEffectiveOrderCode();
328
+ var activeBtn=document.activeElement;
329
+ if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;
330
+ if(activeBtn)setExportButtonState(activeBtn,true);
331
+ try{await _doExportDeliveryPDF(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,false);}
332
+ };
333
+ window.exportExcel=async function(){
334
+ if(typeof ExcelJS==='undefined')return;
335
+ var d=getData(),qd=d.qd,code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);
336
+ await _doExportExcel(d,qd,code,qr);
337
+ };
338
+ window.getQuoteData=function(){
339
+ var qd=window._origGetQuoteData.apply(this,arguments);
340
+ if(qd.customer)qd.customer.orderCode=getEffectiveOrderCode();
341
+ return qd;
342
+ };
343
+ console.log('✅ QR Payment v1043 — FIX: PHÍ GIAO HÀNG, LẮP ĐẶT ĐƯỢC THÊM VÀO EXCEL');
344
+ return true;
345
+ }
346
+
347
+ async function _doExportPDF(d,code){
348
+ var qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);
349
+ var cv=await toCanvas(buildQuoteHTML(d,code,qr),1000);
350
+ var img=cv.toDataURL('image/jpeg',0.92);
351
+ var pageW=595,pageH=842,imgW=pageW,imgH=cv.height*imgW/cv.width;
352
+ var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});
353
+ if(imgH<=pageH){pdf.addImage(img,'JPEG',0,0,imgW,imgH);}
354
+ else{
355
+ var y=0,rem=imgH,page=0;
356
+ while(rem>0){
357
+ if(page>0)pdf.addPage();
358
+ pdf.addImage(img,'JPEG',0,-y,imgW,imgH);
359
+ y+=pageH;
360
+ rem-=pageH;
361
+ page++;
362
+ }
363
+ }
364
+ pdf.save(code+'.pdf');
365
+ }
366
+
367
+ async function _doExportDeliveryPDF(qd,code){
368
+ var cv=await toCanvas(buildDeliveryHTML({qd:qd},code),700);
369
+ var img=cv.toDataURL('image/png');
370
+ var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});
371
+ var margin=25,usableW=595-margin*2,ratio=cv.height/cv.width,imgH=usableW*ratio;
372
+ if(imgH>842-margin*2){imgH=842-margin*2;usableW=imgH/ratio;}
373
+ pdf.addImage(img,'PNG',margin,margin,usableW,imgH);
374
+ pdf.save('GH-'+code+'.xlsx');
375
+ }
376
+
377
+ // ===== EXPORT EXCEL - FIX BAO GỒM PHÍ =====
378
+ async function _doExportExcel(d,qd,code,qrUrl){
379
+ var MONEY_FMT='#,##0"đ"';
380
+ var NUM_FMT='#,##0';
381
+
382
+ var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');
383
+ ws.views=[{showGridLines:false}];
384
+ ws.columns=[{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
385
+
386
+ ws.mergeCells('A3:J3');
387
+ ws.getCell('A3').value='BẢNG BÁO GIÁ';
388
+ ws.getCell('A3').font={bold:true,size:18,color:{argb:'FFDB9815'}};
389
+ ws.getCell('A3').alignment={horizontal:'center'};
390
+
391
+ ws.getCell('A5').value='Khách hàng:';ws.mergeCells('B5:E5');ws.getCell('B5').value=qd.customer?qd.customer.name:'';
392
+ ws.getCell('H5').value='Mã đơn:';ws.mergeCells('I5:J5');ws.getCell('I5').value=code;
393
+ ws.getCell('A6').value='SĐT:';ws.mergeCells('B6:E6');ws.getCell('B6').value=qd.customer?qd.customer.phone:'';
394
+ ws.getCell('H6').value='Ngày:';ws.mergeCells('I6:J6');ws.getCell('I6').value=qd.customer?qd.customer.date:'';
395
+ ws.getCell('A7').value='Email:';ws.mergeCells('B7:E7');ws.getCell('B7').value=qd.customer?qd.customer.email:'';
396
+ ws.getCell('A8').value='Địa chỉ:';ws.mergeCells('B8:J8');ws.getCell('B8').value=qd.customer?qd.customer.addr:'';
397
+
398
+ var hr=ws.getRow(10);
399
+ ['STT','Hình','Tên sản phẩm','Mã SP','Th��ng tin','SL','Đơn giá','Giá CK','Thành tiền','Ghi chú'].forEach(function(h,i){
400
+ var c=hr.getCell(i+1);
401
+ c.value=h;
402
+ c.font={bold:true,color:{argb:'FFFFFFFF'}};
403
+ c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};
404
+ });
405
+
406
+ var cr=11,firstItemRow=cr;
407
+ // Dòng sản phẩm
408
+ for(var i=0;i<qd.items.length;i++){
409
+ var it=qd.items[i],row=ws.getRow(cr);
410
+ row.height=50;
411
+ row.getCell(1).value=it.stt||i+1;
412
+ row.getCell(3).value=it.name||'';
413
+ row.getCell(3).font={bold:true,size:9};
414
+ row.getCell(4).value=it.model||'';
415
+ row.getCell(5).value=it.specs||'';
416
+ row.getCell(5).font={size:8,color:{argb:'FF64748B'};};
417
+ row.getCell(6).value=Number(it.qty||1);
418
+ row.getCell(6).numFmt=NUM_FMT;
419
+ row.getCell(7).value=Number(it.price||0);
420
+ row.getCell(7).numFmt=MONEY_FMT;
421
+ row.getCell(8).value=Number(it.discPrice||it.price||0);
422
+ row.getCell(8).numFmt=MONEY_FMT;
423
+ if((it.discPrice||0)&&(it.price||0)&&it.discPrice<it.price)row.getCell(8).font={bold:true,color:{argb:'FFDC3545'};};
424
+ row.getCell(9).value=Number(it.total||0);
425
+ row.getCell(9).numFmt=MONEY_FMT;
426
+ row.getCell(9).font={bold:true};
427
+ cr++;
428
+ }
429
+
430
+ // FIX: THÊM CÁC DÒNG PHÍ (giao hàng, lắp đặt...)
431
+ var fees = d.fees || [];
432
+ var feeStartRow = cr;
433
+ for(var f=0; f<fees.length; f++){
434
+ var feeRow = ws.getRow(cr);
435
+ feeRow.height=22;
436
+ ws.mergeCells(cr, 2, cr, 8);
437
+ feeRow.getCell(2).value='⊕ ' + fees[f].label;
438
+ feeRow.getCell(2).font={bold:true};
439
+ feeRow.getCell(9).value=fees[f].amount;
440
+ feeRow.getCell(9).numFmt=MONEY_FMT;
441
+ feeRow.getCell(9).font={bold:true};
442
+ cr++;
443
  }
444
+
445
+ // Tổng cộng
446
+ ws.getRow(cr).height=28;
447
+ ws.mergeCells(cr,1,cr,8);
448
+ ws.getCell('A'+cr).value='TỔNG CỘNG';
449
+ ws.getCell('A'+cr).font={bold:true,size:13,color:{argb:'FFFFFFFF'};};
450
+ ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};};
451
+
452
+ var totalFormula = 'SUM(I'+firstItemRow+':I'+(feeStartRow-1)+')';
453
+ if(fees.length > 0){
454
+ totalFormula += '+SUM(I'+feeStartRow+':I'+(cr-1)+')';
455
+ }
456
+ ws.getCell('I'+cr).value={formula:totalFormula,result:Number(d.grandTotal||0)};
457
+ ws.getCell('I'+cr).numFmt=MONEY_FMT;
458
+ ws.getCell('I'+cr).font={bold:true,size:14,color:{argb:'FFF0B840'};};
459
+
460
+ // Thông tin cọc nếu có
461
+ if(d.deposit>0){
462
+ cr++;
463
+ ws.mergeCells(cr,1,cr,10);
464
+ ws.getCell('A'+cr).value='Cọc: '+fmt(d.deposit)+' | Còn lại: '+fmt(d.remaining);
465
+ ws.getCell('A'+cr).font={size:11};
466
+ }
467
+
468
+ wb.calcProperties.fullCalcOnLoad=true;
469
+ var buf=await wb.xlsx.writeBuffer();
470
+ var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
471
+ dlBlob(blob,code+'.xlsx');
472
  }
473
 
474
+ async function _doExportDeliveryExcel(qd,code){
475
+ var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Giao hàng');
476
+ ws.views=[{showGridLines:false}];
477
+ ws.columns=[{width:5},{width:34},{width:14},{width:8},{width:20}];
478
+
479
+ ws.mergeCells('A1:E1');
480
+ ws.getCell('A1').value='PHIẾU GIAO HÀNG';
481
+ ws.getCell('A1').font={bold:true,size:16,color:{argb:'FF003F62'};};
482
+
483
+ ws.getCell('A2').value='KH: '+(qd.customer?qd.customer.name:'');
484
+ ws.getCell('D2').value='Mã: '+code;
485
+ ws.getCell('A3').value='SĐT: '+(qd.customer?qd.customer.phone:'');
486
+ ws.getCell('A4').value='Địa chỉ: '+(qd.customer?qd.customer.addr||'':'');
487
+
488
+ var hr=ws.getRow(6);
489
+ ['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){
490
+ var c=hr.getCell(i+1);
491
+ c.value=h;
492
+ c.font={bold:true,color:{argb:'FFFFFFFF'}};
493
+ c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};
494
+ });
495
+
496
+ var cr=7,items=qd.items||[],totalQty=0;
497
+ for(var i=0;i<items.length;i++){
498
+ var it=items[i],row=ws.getRow(cr);
499
+ row.height=20;
500
+ row.getCell(1).value=it.stt||i+1;
501
+ row.getCell(2).value=it.name||'';
502
+ row.getCell(3).value=it.model||'';
503
+ row.getCell(4).value=it.qty||1;
504
+ row.getCell(5).value=it.note||'';
505
+ totalQty+=Number(it.qty||1);
506
+ cr++;
507
+ }
508
+
509
+ var totalQtyRow=cr;
510
+ ws.mergeCells(totalQtyRow,1,totalQtyRow,3);
511
+ ws.getCell('A'+totalQtyRow).value='TỔNG CỘNG SỐ LƯỢNG';
512
+ ws.getCell('D'+totalQtyRow).value=totalQty;
513
+
514
+ var buf=await wb.xlsx.writeBuffer();
515
+ var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
516
+ dlBlob(blob,'GH-'+code+'.xlsx');
517
+ }
518
+
519
+ var att=0,t=setInterval(function(){if(doOverride()||att>120)clearInterval(t);att++;},500);
520
+
521
+ document.addEventListener('click',function(e){
522
+ var btn=e.target.closest&&e.target.closest('button,a');
523
+ if(!btn)return;
524
+ var txt=(btn.textContent||'').toLowerCase();
525
+ if(!window._origGetQuoteData)return;
526
+ if(txt.includes('excel')&&txt.includes('xuất')){
527
+ e.preventDefault();e.stopPropagation();
528
+ if(btn.dataset.vaiExporting==='1')return;
529
+ setExportButtonState(btn,true);
530
+ window.exportExcel().finally(function(){setExportButtonState(btn,false);});
531
+ return false;
532
+ }
533
+ if((txt.includes('pdf')&&txt.includes('xuất'))||(txt.includes('pdf')&&!txt.includes('giao hàng'))){
534
+ e.preventDefault();e.stopPropagation();
535
+ if(btn.dataset.vaiExporting==='1')return;
536
+ setExportButtonState(btn,true);
537
+ window.exportPDF().finally(function(){setExportButtonState(btn,false);});
538
+ return false;
539
+ }
540
+ },true);
541
 
542
+ window.VAI_QR={
543
+ getEffectiveOrderCode:getEffectiveOrderCode,
544
+ getQRUrl:getQRUrl,
545
+ generateOrderCode:generateOrderCode,
546
+ parsePrompt:parsePrompt,
547
+ getPromptText:getPromptText,
548
+ injectToModal:injectToModal,
549
+ getData:getData,
550
+ buildQuoteHTML:buildQuoteHTML,
551
+ buildDeliveryHTML:buildDeliveryHTML,
552
+ toCanvas:toCanvas,
553
+ exportPDF:_doExportPDF,
554
+ exportExcel:_doExportExcel,
555
+ exportDeliveryPDF:_doExportDeliveryPDF,
556
+ exportDeliveryExcel:_doExportDeliveryExcel,
557
+ fmt:fmt,
558
+ compactKey:compactKey,
559
+ matchItemDiscount:matchItemDiscount,
560
+ parseItemDiscountLine:parseItemDiscountLine,
561
+ applyPromptDiscounts:applyPromptDiscounts,
562
+ setExportButtonState:setExportButtonState,
563
+ orderToVAIData:orderToVAIData
564
+ };
565
+ console.log('[VAI QR v1043 FIX] Loaded - Phí giao hàng, lắp đặt, cọc được thêm vào Excel');
566
+ })();