bep40 commited on
Commit
f462ccd
·
verified ·
1 Parent(s): 411967d

v7: Fix Excel export - fees, deposit, notes always render 100%

Browse files
Files changed (1) hide show
  1. vai-export-fixed.js +182 -63
vai-export-fixed.js CHANGED
@@ -1,16 +1,21 @@
1
  /**
2
- * V.AI STUDIO — EXPORT v6.0 DEFINITIVE (100% images guaranteed)
3
  * ======================================================
4
- * https://huggingface.co/spaces/bep40/V.AISTUDIO
5
- *
6
- * FIX TRIỆT ĐỂ:
7
- * - Tự fetch ảnh bằng CORS + AllOrigins proxy fallback (vượt bizweb.dktcdn.net, eurogold.vn)
8
- * - Chuyển ArrayBuffer → base64 → addImage trực tiếp vào Excel workbook
9
- * - KHÔNG dùng DOM canvas, KHÔNG phụ thuộc .qt-img, KHÔNG CORS taint
10
- * - KHÔNG race condition — genXl tự xử lý hết
 
 
11
  * ======================================================
12
  */
13
- (function(){'use strict';console.log('[VAI EXPORT v6] === LOADING ===');
 
 
 
14
 
15
  // ===== IMAGE FETCH ENGINE =====
16
  function fetchImg(url) {
@@ -40,37 +45,39 @@ function getImgUrl(it){
40
  return'';
41
  }
42
 
43
- // ===== BUILD EXCEL =====
44
  async function genXl(data,qd,code){
45
  var M='#,##0"đ"',N='#,##0',WH={argb:'FFFFFFFF'},WB={top:{style:'thin',color:WH},bottom:{style:'thin',color:WH},left:{style:'thin',color:WH},right:{style:'thin',color:WH}};
46
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');ws.views=[{showGridLines:false}];ws.columns=[{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
47
 
 
48
  ws.mergeCells('A1:J1');ws.getRow(1).height=28;ws.getCell('A1').value='V.AI STUDIO ❝ Niềm tin khách hàng là tài sản của chúng tôi ❞';ws.getCell('A1').font={italic:true,size:9,color:{argb:'FF64748B'}};ws.getCell('A1').alignment={vertical:'middle'};ws.getCell('A1').border=WB;
49
  ws.getRow(2).height=5;ws.mergeCells('A3:J3');ws.getRow(3).height=30;ws.getCell('A3').value='BẢNG BÁO GIÁ';ws.getCell('A3').font={bold:true,size:18,color:{argb:'FFDB9815'}};ws.getCell('A3').alignment={horizontal:'center',vertical:'middle'};ws.getCell('A3').border=WB;ws.getRow(4).height=5;
50
 
 
51
  var cust=qd.customer||{};
52
  ws.getCell('A5').value='Khách hàng:';ws.getCell('A5').font={bold:true,size:9};ws.getCell('A5').border=WB;ws.mergeCells('B5:E5');ws.getCell('B5').value=cust.name||'';ws.getCell('B5').font={bold:true,size:10};ws.getCell('B5').border=WB;ws.getCell('H5').value='Mã đơn:';ws.getCell('H5').font={size:9};ws.getCell('H5').border=WB;ws.mergeCells('I5:J5');ws.getCell('I5').value=code;ws.getCell('I5').font={bold:true,size:10,color:{argb:'FF003F62'}};ws.getCell('I5').border=WB;
53
  ws.getCell('A6').value='SĐT:';ws.getCell('A6').font={size:9};ws.getCell('A6').border=WB;ws.getCell('B6').value=cust.phone||'';ws.getCell('B6').border=WB;ws.getCell('H6').value='Ngày:';ws.getCell('H6').font={size:9};ws.getCell('H6').border=WB;ws.getCell('I6').value=cust.date||'';ws.getCell('I6').border=WB;
54
  ws.getCell('A7').value='Email:';ws.getCell('A7').font={size:9};ws.getCell('A7').border=WB;ws.getCell('B7').value=cust.email||'';ws.getCell('B7').border=WB;ws.getCell('A8').value='Địa chỉ:';ws.getCell('A8').font={size:9};ws.getCell('A8').border=WB;ws.mergeCells('B8:J8');ws.getCell('B8').value=cust.addr||'';ws.getCell('B8').border=WB;ws.getRow(9).height=5;
55
 
56
- // HEADER row
57
  var HB={top:{style:'thin',color:{argb:'FF003F62'}},bottom:{style:'thin',color:{argb:'FF003F62'}},left:{style:'thin',color:{argb:'FF003F62'}},right:{style:'thin',color:{argb:'FF003F62'}}};
58
  var hRow=ws.getRow(10);hRow.height=20;
59
  ['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){
60
  var c=hRow.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'},size:9};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};c.alignment={horizontal:'center',vertical:'middle',wrapText:true};c.border=HB;
61
  });
62
 
63
- // PRELOAD images BEFORE building rows
64
  var items=qd.items||[];
65
  var imgUrls=items.map(getImgUrl);
66
- console.log('[VAI EXPORT v6] Fetching',imgUrls.filter(Boolean).length,'images for',items.length,'items...');
67
  var imgResults=await Promise.all(imgUrls.map(function(url){
68
  if(!url)return Promise.resolve(null);
69
  return fetchImg(url).then(function(buf){return bufToB64(buf,url);});
70
  }));
71
- console.log('[VAI EXPORT v6] Loaded',imgResults.filter(Boolean).length,'images');
72
 
73
- // BUILD rows với ảnh trực tiếp
74
  var cr=11;
75
  items.forEach(function(it,i){
76
  var row=ws.getRow(cr);row.height=50;
@@ -80,13 +87,13 @@ async function genXl(data,qd,code){
80
 
81
  row.getCell(1).value=it.stt||(i+1);row.getCell(1).alignment={horizontal:'center',vertical:'middle'};
82
 
83
- // ===== INJECT ẢNH TRỰC TIẾP VÀO EXCEL =====
84
  var ri=imgResults[i];
85
  if(ri&&ri.base64){
86
  try{
87
  var imgId=wb.addImage({base64:ri.base64,extension:ri.ext||'jpeg'});
88
  ws.addImage(imgId,{tl:{col:1,row:cr-1},ext:{width:46,height:46}});
89
- }catch(ie){console.warn('[VAI EXPORT v6] img add error:',ie);}
90
  }
91
 
92
  row.getCell(2).value='';row.getCell(2).font={size:8};row.getCell(2).alignment={horizontal:'center',vertical:'middle'};
@@ -105,67 +112,179 @@ async function genXl(data,qd,code){
105
  // WARRANTY
106
  ws.mergeCells(cr,1,cr,10);ws.getRow(cr).height=18;ws.getCell('A'+cr).value='✅ Bảo hành 2 năm (sản phẩm) — Bảo hành hoen gỉ vĩnh viễn (rổ SUS304)';ws.getCell('A'+cr).font={size:9,color:{argb:'FF059669'},italic:true};ws.getCell('A'+cr).alignment={vertical:'middle'};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};cr++;
107
 
108
- // FEES
109
  var fees=data.fees||[];
110
- if(fees.length>0){fees.forEach(function(f){ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=20;ws.getCell('A'+cr).value=' ⊕ '+(f.label||'Phụ phí');ws.getCell('A'+cr).font={size:9,color:{argb:'FF92400E'}};ws.getCell('A'+cr).alignment={vertical:'middle'};ws.getCell('I'+cr).value=Number(f.amount||0);ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:9,color:{argb:'FF92400E'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFFFBEB'}};cr++;});}
 
 
 
 
 
 
 
 
 
 
111
 
112
- // TOTAL
113
- var TBG='FF003F62',TB={top:{style:'thin',color:{argb:TBG}},bottom:{style:'thin',color:{argb:TBG}},left:{style:'thin',color:{argb:TBG}},right:{style:'thin',color:{argb:TBG}}};ws.mergeCells(cr,1,cr,8);var tRow=ws.getRow(cr);tRow.height=28;tRow.getCell(1).value='TỔNG CỘNG';tRow.getCell(1).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};tRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};for(var ci=1;ci<=10;ci++){tRow.getCell(ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(ci).border=TB;}
114
- tRow.getCell(9).value=Number(data.grandTotal||0);tRow.getCell(9).numFmt=M;tRow.getCell(9).font={bold:true,size:15,color:{argb:'FFF0B840'}};tRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};cr++;
 
 
 
 
 
 
115
 
116
- // DEPOSIT
117
  var deposit=Number(data.deposit||0);
118
- if(deposit>0){ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=22;ws.getCell('A'+cr).value='Đã cọc';ws.getCell('A'+cr).font={bold:true,size:11,color:{argb:'FF166534'}};ws.getCell('A'+cr).alignment={horizontal:'left',vertical:'middle',indent:1};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};ws.getCell('I'+cr).value=deposit;ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:11,color:{argb:'FF166534'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};cr++;
119
- ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=24;ws.getCell('A'+cr).value='CÒN LẠI';ws.getCell('A'+cr).font={bold:true,size:12,color:{argb:'FFDC2626'}};ws.getCell('A'+cr).alignment={horizontal:'left',vertical:'middle',indent:1};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFEF2F2'}};ws.getCell('I'+cr).value=Number(data.remaining||0);ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:13,color:{argb:'FFDC2626'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};cr++;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- // NOTES
122
- var notes=data.notes||[];if(notes.length>0){ws.mergeCells(cr,1,cr,10);ws.getCell('A'+cr).value='📝 Ghi chú: '+notes.join('; ');ws.getCell('A'+cr).font={italic:true,size:9,color:{argb:'FF166534'}};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};cr++;}
 
 
 
 
 
 
 
 
123
 
124
  // BANK INFO
125
- cr++;ws.mergeCells(cr,1,cr,5);ws.getRow(cr).height=18;ws.getCell('A'+cr).value='💳 QR CODE: VIB - 918258385 - Trần Quốc Vương';ws.getCell('A'+cr).font={bold:true,size:10,color:{argb:'FF003F62'}};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF8FAFC'}};cr++;
126
- ws.mergeCells(cr,1,cr,10);ws.getCell('A'+cr).value='🏦 VIB | 👤 Trần Quốc Vương | STK: 918258385 | 💰 '+((deposit>0?data.remaining:data.grandTotal)||0).toLocaleString('vi-VN')+'đ | 📝 '+code;ws.getCell('A'+cr).font={bold:true,size:10};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF8FAFC'}};
 
 
 
 
 
 
 
 
127
 
128
  // Final borders
129
  ws.eachRow(function(row){row.eachCell(function(cell){if(!cell.border)cell.border=WB;});});
130
  wb.calcProperties.fullCalcOnLoad=true;
131
 
132
  var buf=await wb.xlsx.writeBuffer();var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=code+'.xlsx';a.style.display='none';document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);URL.revokeObjectURL(url);},60000);
133
- console.log('[VAI EXPORT v6] ✅ '+code+'.xlsx');
134
  }
135
 
136
- // ===== FALLBACK HTML (khi ExcelJS lỗi) =====
137
- function fallbackXls(){try{var data;if(window.VAI_QR&&typeof window.VAI_QR.getData==='function')data=window.VAI_QR.getData();else if(typeof getData==='function')data=getData();if(!data||!data.qd||!data.qd.items){alert('⚠️ Không có dữ liệu');return;}
138
- var qd=data.qd,cust=qd.customer||{},code=(window.VAI_QR&&typeof window.VAI_QR.getEffectiveOrderCode==='function')?window.VAI_QR.getEffectiveOrderCode():'VAS'+Date.now().toString(36).toUpperCase();
139
- function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
140
- var h='<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>BaoGia</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>td,th{border:1px solid #ccc;padding:4px;font-family:Arial;font-size:11px}th{background:#003f62;color:#fff;font-weight:700}</style></head><body><table cellpadding="3" cellspacing="0" style="border-collapse:collapse;font-family:Arial;font-size:11px;width:100%">';
141
- h+='<tr><th colspan="7" style="font-size:18px;color:#db9815;text-align:center;padding:10px;background:#003f62;color:#fff">V.AI STUDIO — BẢNG BÁO GIÁ</th></tr>';h+='<tr><td colspan="2"><b>KH:</b> '+(cust.name||'')+'</td><td colspan="3"><b>Mã:</b> '+code+'</td><td colspan="2"><b>Ngày:</b> '+(cust.date||'')+'</td></tr>';h+='<tr><td colspan="7"><b>SĐT:</b> '+(cust.phone||'')+' | <b>Email:</b> '+(cust.email||'')+' | <b>ĐC:</b> '+(cust.addr||'')+'</td></tr>';h+='<tr><th>STT</th><th>Tên SP</th><th>Mã SP</th><th>SL</th><th>Đơn giá</th><th>Giá CK</th><th>Thành tiền</th></tr>';
142
- var total=0;qd.items.forEach(function(it,i){var lt=(it.discPrice||it.price||0)*(it.qty||1);total+=lt;var bg=i%2===0?'#f8fafc':'#fff';var ckS=(it.discPrice&&it.price&&it.discPrice<it.price)?' style="color:#dc3545;font-weight:700"':'';h+='<tr style="background:'+bg+'"><td style="text-align:center">'+(i+1)+'</td><td style="font-weight:600">'+(it.name||'')+'</td><td style="text-align:center">'+(it.model||'')+'</td><td style="text-align:center">'+(it.qty||1)+'</td><td style="text-align:right">'+fmt(it.price||0)+'</td><td style="text-align:right"'+ckS+'>'+fmt(it.discPrice||it.price||0)+'</td><td style="text-align:right;font-weight:700">'+fmt(lt)+'</td></tr>';});(data.fees||[]).forEach(function(f){h+='<tr style="background:#fffbeb"><td colspan="6" style="color:#92400e">⊕ '+f.label+'</td><td style="text-align:right;font-weight:700;color:#92400e">'+fmt(f.amount)+'</td></tr>';});
143
- var gt=data.grandTotal||total;h+='<tr style="font-weight:bold;background:#003f62;color:#fff"><td colspan="6" style="text-align:right;padding:8px;font-size:13px">TỔNG CỘNG</td><td style="text-align:right;color:#f0b840;font-size:16px;padding:8px">'+fmt(gt)+'</td></tr>';
144
- if(data.deposit>0){var rem=gt-data.deposit;h+='<tr style="background:#f0fdf4"><td colspan="6" style="color:#166534">Đã cọc</td><td style="text-align:right;font-weight:700;color:#166534">'+fmt(data.deposit)+'</td></tr>';h+='<tr style="background:#fef2f2"><td colspan="6" style="color:#dc2626;font-weight:700">CÒN LẠI</td><td style="text-align:right;font-weight:900;color:#dc2626">'+fmt(rem>0?rem:0)+'</td></tr>';}
145
- h+='<tr><td colspan="7" style="font-size:10px;color:#059669;font-style:italic;padding:6px;background:#f0fdf4">✅ Bảo hành 2 năm (sản phẩm) — Bảo hành hoen gỉ vĩnh viễn (rổ SUS304)</td></tr>';h+='<tr><td colspan="7" style="font-size:10px;background:#f8fafc;padding:6px">💳 VIB | Trần Quốc Vương | STK: 918258385 | Số tiền: '+fmt(data.deposit>0?gt-data.deposit:gt)+' | ND: '+code+'</td></tr></table></body></html>';
146
- var blob=new Blob([h],{type:'application/vnd.ms-excel'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=code+'.xls';a.style.display='none';document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);URL.revokeObjectURL(url);},60000);console.log('[VAI EXPORT v6] ✅ Fallback '+code+'.xls');}catch(e){console.error('[VAI EXPORT v6] Fallback error:',e);alert('❌ Lỗi xuất Excel: '+(e.message||e));}}
147
-
148
- // ===== SET EXPORT =====
149
- setInterval(function(){
150
- if(typeof ExcelJS!=='undefined'&&window.VAI_QR){
151
- window.exportExcel=async function(data,qd,code,qrUrl,bw){
152
- console.log('[VAI EXPORT v6] exportExcel');
153
- try{
154
- // Use passed data or try to get it
155
- if(!data || !data.qd || !data.qd.items || !data.qd.items.length){
156
- if(window.VAI_QR&&typeof window.VAI_QR.getData==='function')data=window.VAI_QR.getData();
157
- else if(typeof getData==='function')data=getData();
158
- if(!data||!data.qd){
159
- if(typeof getQuoteData==='function'){var qd2=getQuoteData();data={qd:qd2,fees:[],notes:[],deposit:0,discountPercent:0,itemDiscounts:{},grandTotal:qd2.grandTotal||0,remaining:qd2.grandTotal||0,productTotal:qd2.grandTotal||0};}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
- }
162
- if(!data||!data.qd||!data.qd.items||!data.qd.items.length){console.error('[VAI EXPORT v6] No data');return;}
163
- var qd=data.qd,code=code||(window.VAI_QR&&typeof window.VAI_QR.getEffectiveOrderCode==='function')?window.VAI_QR.getEffectiveOrderCode():(qd.customer&&qd.customer.orderCode)||'VAS'+Date.now().toString(36).toUpperCase();
164
- try{await genXl(data,qd,code);}catch(e){console.error('[VAI EXPORT v6] genXl error:',e);fallbackXls();}
165
- }catch(e){console.error('[VAI EXPORT v6] Error:',e);fallbackXls();}
166
- };
167
- window._doExportExcel=window.exportExcel;
168
- window.VAI_QR.exportExcel=window.exportExcel;
 
 
 
 
 
 
 
169
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  },200);
171
- console.log('[VAI EXPORT v6] ✅ Loaded');})();
 
 
1
  /**
2
+ * V.AI STUDIO — EXPORT v7 DEFINITIVE (100% fees + deposit + notes guaranteed)
3
  * ======================================================
4
+ * FIX TRIỆT ĐỂ v7:
5
+ * - Tái cấu trúc hoàn toàn genXl: luôn đọc fees/deposit/notes từ VAI_QR.getData()
6
+ * - Luôn render "CÒN LẠI (100%)" khi không có cọc
7
+ * - Luôn render "Đã cọc" + "Còn lại" khi có cọc
8
+ * - Luôn render phụ phí khi có fees
9
+ * - Luôn render ghi chú khi có notes
10
+ * - Tự động pre-fetch ảnh bằng CORS + proxy fallback
11
+ * - KHÔNG phụ thuộc DOM canvas, KHÔNG CORS taint
12
+ * - KHÔNG xung đột với các hàm khác: chỉ override exportExcel 1 lần duy nhất
13
  * ======================================================
14
  */
15
+ (function(){'use strict';console.log('[VAI EXPORT v7] === LOADING ===');
16
+
17
+ // ===== UTILITY =====
18
+ function fmtVND(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
19
 
20
  // ===== IMAGE FETCH ENGINE =====
21
  function fetchImg(url) {
 
45
  return'';
46
  }
47
 
48
+ // ===== BUILD EXCEL v7 =====
49
  async function genXl(data,qd,code){
50
  var M='#,##0"đ"',N='#,##0',WH={argb:'FFFFFFFF'},WB={top:{style:'thin',color:WH},bottom:{style:'thin',color:WH},left:{style:'thin',color:WH},right:{style:'thin',color:WH}};
51
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');ws.views=[{showGridLines:false}];ws.columns=[{width:5},{width:11},{width:28},{width:13},{width:20},{width:6},{width:13},{width:13},{width:15},{width:14}];
52
 
53
+ // HEADER
54
  ws.mergeCells('A1:J1');ws.getRow(1).height=28;ws.getCell('A1').value='V.AI STUDIO ❝ Niềm tin khách hàng là tài sản của chúng tôi ❞';ws.getCell('A1').font={italic:true,size:9,color:{argb:'FF64748B'}};ws.getCell('A1').alignment={vertical:'middle'};ws.getCell('A1').border=WB;
55
  ws.getRow(2).height=5;ws.mergeCells('A3:J3');ws.getRow(3).height=30;ws.getCell('A3').value='BẢNG BÁO GIÁ';ws.getCell('A3').font={bold:true,size:18,color:{argb:'FFDB9815'}};ws.getCell('A3').alignment={horizontal:'center',vertical:'middle'};ws.getCell('A3').border=WB;ws.getRow(4).height=5;
56
 
57
+ // CUSTOMER INFO
58
  var cust=qd.customer||{};
59
  ws.getCell('A5').value='Khách hàng:';ws.getCell('A5').font={bold:true,size:9};ws.getCell('A5').border=WB;ws.mergeCells('B5:E5');ws.getCell('B5').value=cust.name||'';ws.getCell('B5').font={bold:true,size:10};ws.getCell('B5').border=WB;ws.getCell('H5').value='Mã đơn:';ws.getCell('H5').font={size:9};ws.getCell('H5').border=WB;ws.mergeCells('I5:J5');ws.getCell('I5').value=code;ws.getCell('I5').font={bold:true,size:10,color:{argb:'FF003F62'}};ws.getCell('I5').border=WB;
60
  ws.getCell('A6').value='SĐT:';ws.getCell('A6').font={size:9};ws.getCell('A6').border=WB;ws.getCell('B6').value=cust.phone||'';ws.getCell('B6').border=WB;ws.getCell('H6').value='Ngày:';ws.getCell('H6').font={size:9};ws.getCell('H6').border=WB;ws.getCell('I6').value=cust.date||'';ws.getCell('I6').border=WB;
61
  ws.getCell('A7').value='Email:';ws.getCell('A7').font={size:9};ws.getCell('A7').border=WB;ws.getCell('B7').value=cust.email||'';ws.getCell('B7').border=WB;ws.getCell('A8').value='Địa chỉ:';ws.getCell('A8').font={size:9};ws.getCell('A8').border=WB;ws.mergeCells('B8:J8');ws.getCell('B8').value=cust.addr||'';ws.getCell('B8').border=WB;ws.getRow(9).height=5;
62
 
63
+ // TABLE HEADER
64
  var HB={top:{style:'thin',color:{argb:'FF003F62'}},bottom:{style:'thin',color:{argb:'FF003F62'}},left:{style:'thin',color:{argb:'FF003F62'}},right:{style:'thin',color:{argb:'FF003F62'}}};
65
  var hRow=ws.getRow(10);hRow.height=20;
66
  ['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){
67
  var c=hRow.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'},size:9};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};c.alignment={horizontal:'center',vertical:'middle',wrapText:true};c.border=HB;
68
  });
69
 
70
+ // FETCH IMAGES
71
  var items=qd.items||[];
72
  var imgUrls=items.map(getImgUrl);
73
+ console.log('[VAI EXPORT v7] Fetching',imgUrls.filter(Boolean).length,'images for',items.length,'items...');
74
  var imgResults=await Promise.all(imgUrls.map(function(url){
75
  if(!url)return Promise.resolve(null);
76
  return fetchImg(url).then(function(buf){return bufToB64(buf,url);});
77
  }));
78
+ console.log('[VAI EXPORT v7] Loaded',imgResults.filter(Boolean).length,'images');
79
 
80
+ // BUILD ITEM ROWS
81
  var cr=11;
82
  items.forEach(function(it,i){
83
  var row=ws.getRow(cr);row.height=50;
 
87
 
88
  row.getCell(1).value=it.stt||(i+1);row.getCell(1).alignment={horizontal:'center',vertical:'middle'};
89
 
90
+ // INJECT ẢNH
91
  var ri=imgResults[i];
92
  if(ri&&ri.base64){
93
  try{
94
  var imgId=wb.addImage({base64:ri.base64,extension:ri.ext||'jpeg'});
95
  ws.addImage(imgId,{tl:{col:1,row:cr-1},ext:{width:46,height:46}});
96
+ }catch(ie){console.warn('[VAI EXPORT v7] img add error:',ie);}
97
  }
98
 
99
  row.getCell(2).value='';row.getCell(2).font={size:8};row.getCell(2).alignment={horizontal:'center',vertical:'middle'};
 
112
  // WARRANTY
113
  ws.mergeCells(cr,1,cr,10);ws.getRow(cr).height=18;ws.getCell('A'+cr).value='✅ Bảo hành 2 năm (sản phẩm) — Bảo hành hoen gỉ vĩnh viễn (rổ SUS304)';ws.getCell('A'+cr).font={size:9,color:{argb:'FF059669'},italic:true};ws.getCell('A'+cr).alignment={vertical:'middle'};for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};cr++;
114
 
115
+ // ===== FEES - LUÔN render nếu có =====
116
  var fees=data.fees||[];
117
+ if(fees.length>0){
118
+ fees.forEach(function(f){
119
+ ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=20;
120
+ ws.getCell('A'+cr).value=' ⊕ '+(f.label||'Phụ phí');
121
+ ws.getCell('A'+cr).font={size:9,color:{argb:'FF92400E'}};ws.getCell('A'+cr).alignment={vertical:'middle'};
122
+ ws.getCell('I'+cr).value=Number(f.amount||0);ws.getCell('I'+cr).numFmt=M;
123
+ ws.getCell('I'+cr).font={bold:true,size:9,color:{argb:'FF92400E'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};
124
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFFFBEB'}};
125
+ cr++;
126
+ });
127
+ }
128
 
129
+ // ===== TỔNG CỘNG =====
130
+ var grandTotal=Number(data.grandTotal||0);
131
+ var TBG='FF003F62',TB={top:{style:'thin',color:{argb:TBG}},bottom:{style:'thin',color:{argb:TBG}},left:{style:'thin',color:{argb:TBG}},right:{style:'thin',color:{argb:TBG}}};
132
+ ws.mergeCells(cr,1,cr,8);var tRow=ws.getRow(cr);tRow.height=28;
133
+ tRow.getCell(1).value='TỔNG CỘNG';tRow.getCell(1).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};tRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};
134
+ tRow.getCell(9).value=grandTotal;tRow.getCell(9).numFmt=M;tRow.getCell(9).font={bold:true,size:15,color:{argb:'FFF0B840'}};tRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};
135
+ for(var ci=1;ci<=10;ci++){tRow.getCell(ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(ci).border=TB;}
136
+ var totalRowNum=cr;
137
+ cr++;
138
 
139
+ // ===== CỌC / CÒN LẠI =====
140
  var deposit=Number(data.deposit||0);
141
+ if(deposit>0){
142
+ // Đã cọc
143
+ ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=22;
144
+ ws.getCell('A'+cr).value='Đã cọc';ws.getCell('A'+cr).font={bold:true,size:11,color:{argb:'FF166534'}};ws.getCell('A'+cr).alignment={horizontal:'left',vertical:'middle',indent:1};
145
+ ws.getCell('I'+cr).value=deposit;ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:11,color:{argb:'FF166534'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};
146
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};
147
+ cr++;
148
+ // Còn lại
149
+ var remaining=Math.max(0,grandTotal-deposit);
150
+ ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=24;
151
+ ws.getCell('A'+cr).value='CÒN LẠI';ws.getCell('A'+cr).font={bold:true,size:12,color:{argb:'FFDC2626'}};ws.getCell('A'+cr).alignment={horizontal:'left',vertical:'middle',indent:1};
152
+ ws.getCell('I'+cr).value=remaining;ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:13,color:{argb:'FFDC2626'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};
153
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFEF2F2'}};
154
+ cr++;
155
+ } else {
156
+ // CÒN LẠI (100%) - luôn hiển thị khi không có cọc
157
+ ws.mergeCells(cr,1,cr,8);ws.getRow(cr).height=24;
158
+ ws.getCell('A'+cr).value='CÒN LẠI (100%)';ws.getCell('A'+cr).font={bold:true,size:12,color:{argb:'FFDC2626'}};ws.getCell('A'+cr).alignment={horizontal:'left',vertical:'middle',indent:1};
159
+ ws.getCell('I'+cr).value=grandTotal;ws.getCell('I'+cr).numFmt=M;ws.getCell('I'+cr).font={bold:true,size:13,color:{argb:'FFDC2626'}};ws.getCell('I'+cr).alignment={horizontal:'right',vertical:'middle'};
160
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFFEF2F2'}};
161
+ cr++;
162
+ }
163
 
164
+ // ===== NOTES - LUÔN render nếu có =====
165
+ var notes=data.notes||[];
166
+ if(notes.length>0){
167
+ ws.mergeCells(cr,1,cr,10);ws.getRow(cr).height=20;
168
+ ws.getCell('A'+cr).value='📝 Ghi chú: '+notes.join('; ');
169
+ ws.getCell('A'+cr).font={italic:true,size:9,color:{argb:'FF166534'}};
170
+ ws.getCell('A'+cr).alignment={wrapText:true,vertical:'middle'};
171
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF0FDF4'}};
172
+ cr++;
173
+ }
174
 
175
  // BANK INFO
176
+ cr++;ws.mergeCells(cr,1,cr,5);ws.getRow(cr).height=18;
177
+ ws.getCell('A'+cr).value='💳 QR CODE: VIB - 918258385 - Trần Quốc Vương';
178
+ ws.getCell('A'+cr).font={bold:true,size:10,color:{argb:'FF003F62'}};
179
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF8FAFC'}};
180
+ cr++;
181
+ ws.mergeCells(cr,1,cr,10);
182
+ var paymentAmt=deposit>0?remaining:grandTotal;
183
+ ws.getCell('A'+cr).value='🏦 VIB | 👤 Trần Quốc Vương | STK: 918258385 | 💰 '+fmtVND(paymentAmt)+' | 📝 '+code;
184
+ ws.getCell('A'+cr).font={bold:true,size:10};
185
+ for(var ci=1;ci<=10;ci++)ws.getCell(cr,ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FFF8FAFC'}};
186
 
187
  // Final borders
188
  ws.eachRow(function(row){row.eachCell(function(cell){if(!cell.border)cell.border=WB;});});
189
  wb.calcProperties.fullCalcOnLoad=true;
190
 
191
  var buf=await wb.xlsx.writeBuffer();var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=code+'.xlsx';a.style.display='none';document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);URL.revokeObjectURL(url);},60000);
192
+ console.log('[VAI EXPORT v7] ✅ '+code+'.xlsx');
193
  }
194
 
195
+ // ===== GET COMPLETE DATA =====
196
+ function getExportData(){
197
+ var data=null;
198
+ if(window.VAI_QR&&typeof window.VAI_QR.getData==='function'){
199
+ try{data=window.VAI_QR.getData();}catch(e){console.warn('[VAI EXPORT v7] getData error:',e);}
200
+ }
201
+ if(!data||!data.qd||!data.qd.items||!data.qd.items.length){
202
+ if(typeof getQuoteData==='function'){
203
+ var qd2=getQuoteData();
204
+ // Parse prompt for fees/deposit/notes manually
205
+ var promptTxt='';
206
+ var inp=document.getElementById('qcAiPrompt');
207
+ if(inp)promptTxt=inp.value||'';
208
+
209
+ // Simple inline parse
210
+ var fees=[],deposit=0,notes=[],discountPct=0;
211
+ if(promptTxt){
212
+ var lines=promptTxt.split(/[,;\n]+/);
213
+ lines.forEach(function(line){
214
+ line=(line||'').trim();if(!line)return;
215
+ var lo=line.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[đĐ]/g,'d');
216
+
217
+ // CK
218
+ var ckm=lo.match(/^ck\s*([\d.,]+)\s*%/);
219
+ if(ckm){discountPct=parseFloat(ckm[1].replace(/\./g,'').replace(',','.'));return;}
220
+ // Brand CK
221
+ var bck=lo.match(/^(malloca|eurogold|grob|canzy|demax|hafele)\s+ck\s*([\d.,]+)\s*%/);
222
+ if(bck){discountPct=parseFloat(bck[2].replace(/\./g,'').replace(',','.'));return;}
223
+ // Ghi chú
224
+ if(lo.match(/^ghi chu/)){notes.push(line.replace(/^ghi chú|ghi chu[:\s]*/i,''));return;}
225
+ // Cọc
226
+ if(lo.match(/coc/)){
227
+ var dm=lo.match(/([\d.,]+)\s*(k|tr|trieu|m)?/);
228
+ if(dm){var a=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var u=(dm[2]||'').toLowerCase();if(u==='k')a*=1000;else if(u==='tr'||u==='trieu'||u==='m')a*=1000000;deposit=a;}
229
+ return;
230
+ }
231
+ // Phí
232
+ var fm=lo.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi)\s*([\d.,]+)\s*(k|tr)?/);
233
+ if(fm){
234
+ var a=parseFloat(fm[2].replace(/\./g,'').replace(',','.'));var u=(fm[3]||'').toLowerCase();
235
+ if(u==='k')a*=1000;else if(u==='tr')a*=1000000;else if(a>0&&a<500)a*=1000;
236
+ var labelMap={'giao hang':'Phí giao hàng','giao':'Phí giao hàng','lap dat':'Phí lắp đặt','lap':'Phí lắp đặt','van chuyen':'Phí vận chuyển','ship':'Phí vận chuyển','phi':'Phụ phí'};
237
+ fees.push({label:labelMap[fm[1]]||'Phụ phí',amount:a});return;
238
  }
239
+ // Số có đơn vị fallback
240
+ var nm2=lo.match(/([\d.,]+)\s*(k|tr)/);
241
+ if(nm2){var a2=parseFloat(nm2[1].replace(/\./g,'').replace(',','.'));var u2=nm2[2].toLowerCase();if(u2==='k')a2*=1000;else if(u2==='tr')a2*=1000000;var lb=line.replace(nm2[0],'').trim();if(!lb||lb.length<2)lb='Phụ phí';fees.push({label:lb.charAt(0).toUpperCase()+lb.slice(1),amount:a2});}
242
+ });
243
+ }
244
+
245
+ var pt=0;
246
+ qd2.items.forEach(function(it){
247
+ var dp=Number(it.discPrice||it.price||0);
248
+ pt+=dp*Number(it.qty||1);
249
+ });
250
+ var sc=0;fees.forEach(function(f){sc+=f.amount;});
251
+ var gt=pt+sc;
252
+ data={qd:qd2,fees:fees,notes:notes,deposit:deposit,discountPercent:discountPct,itemDiscounts:{},productTotal:pt,grandTotal:gt,remaining:Math.max(0,gt-deposit)};
253
+ }
254
  }
255
+ return data;
256
+ }
257
+
258
+ // ===== SET EXPORT - definitive single override =====
259
+ function setExport(){
260
+ if(typeof ExcelJS==='undefined'||!window.VAI_QR)return false;
261
+
262
+ window.exportExcel=async function(data,qd,code,qrUrl,bw){
263
+ console.log('[VAI EXPORT v7] exportExcel called');
264
+ try{
265
+ data=getExportData();
266
+ if(!data||!data.qd||!data.qd.items||!data.qd.items.length){
267
+ console.error('[VAI EXPORT v7] No data');
268
+ showToast('⚠️ Không có dữ liệu xuất Excel');
269
+ return;
270
+ }
271
+ code=code||(data.qd.customer&&data.qd.customer.orderCode)||'VAS'+Date.now().toString(36).toUpperCase();
272
+ try{await genXl(data,data.qd,code);showToast('✅ Đã xuất Excel thành công!');}
273
+ catch(e){console.error('[VAI EXPORT v7] genXl error:',e);showToast('���️ Lỗi xuất Excel, thử lại...');}
274
+ }catch(e){console.error('[VAI EXPORT v7] Error:',e);showToast('⚠️ Lỗi: '+(e.message||e));}
275
+ };
276
+ window._doExportExcel=window.exportExcel;
277
+ window.VAI_QR.exportExcel=window.exportExcel;
278
+
279
+ console.log('[VAI EXPORT v7] ✅ Set as definitive export');
280
+ return true;
281
+ }
282
+
283
+ // Poll until ready
284
+ var _v7Att=0;
285
+ var _v7Timer=setInterval(function(){
286
+ if(setExport()||_v7Att>150)clearInterval(_v7Timer);
287
+ _v7Att++;
288
  },200);
289
+
290
+ console.log('[VAI EXPORT v7] ✅ Loaded');})();