Sửa qr-payment-v1039.js thêm blob registry

#15
by bep40 - opened
Files changed (1) hide show
  1. qr-payment-v1039.js +92 -40
qr-payment-v1039.js CHANGED
@@ -1,17 +1,54 @@
1
- /* V.AI STUDIO — QR Payment / Quote Data v1039
2
- * FIX TRIỆT ĐỐI: Multi-download Excel - removed URL.revokeObjectURL timeout
3
- * Thay thế v1038 - xóa 2 dòng setTimeout revoke URL gây lỗi tải 2 lần
4
  */
5
- (function(){'use strict';var excelBlobRegistry=[],isUnloading=!1;
 
 
 
 
 
6
 
7
  // Cleanup khi tab đóng
8
- function cleanupBlobs(){isUnloading=!0,excelBlobRegistry.forEach(function(e){e.url&&URL.revokeObjectURL(e.url),e.revoked=!0}),excelBlobRegistry=[];}
9
- window.addEventListener('pagehide',cleanupBlobs);
10
- window.addEventListener('beforeunload',cleanupBlobs);
11
- window.addEventListener('unload',cleanupBlobs);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- // Patch URL.revokeObjectURL - không revoke blob URLs ngay
14
- (function(){var e=URL.revokeObjectURL;URL.revokeObjectURL=function(t){if(!isUnloading&&t&&t.startsWith('blob:')){var n=excelBlobRegistry.find(function(e){return e.url===t});n||excelBlobRegistry.push({url:t,revoked:!1,created:Date.now()});return;}e.call(this,t);};})();
 
 
 
 
 
 
 
 
 
 
15
 
16
  var BANK_ID='vib',BANK_ACCOUNT='918258385',BANK_NAME='TRAN QUOC VUONG',BANK_NAME_VN='Trần Quốc Vương';
17
  function stripDiacritics(s){return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D');}
@@ -44,14 +81,14 @@ function buildQuoteHTML(d,code,qrUrl){var qd=d.qd;var rows=qd.items.map(function
44
 
45
  function buildDeliveryHTML(qd,code){var rows=(qd.items||[]).map(function(it,i){return '<tr style="border-bottom:1px solid #e2e8f0"><td style="padding:8px 4px;text-align:center;font-size:12px">'+(it.stt||(i+1))+'</td><td style="padding:8px 4px;font-weight:600;font-size:12px">'+(it.name||'')+'</td><td style="padding:8px 4px;text-align:center;font-size:11px">'+(it.model||'')+'</td><td style="padding:8px 4px;text-align:center;font-weight:700;font-size:13px">'+(it.qty||1)+'</td><td style="padding:8px 4px;font-size:11px">'+(it.note||'')+'</td></tr>';}).join('');return '<div style="text-align:center;margin-bottom:14px"><div style="font-size:20px;font-weight:900;color:#003f62">PHIẾU GIAO HÀNG</div></div><div style="margin-bottom:12px;font-size:11px;line-height:1.6"><b>Khách hàng:</b> '+(qd.customer?qd.customer.name:'')+' | <b>SĐT:</b> '+(qd.customer?qd.customer.phone:'')+'<br><b>Địa chỉ:</b> '+(qd.customer?qd.customer.addr||'':'')+'<br><b>Mã đơn:</b> '+code+' | <b>Ngày:</b> '+(qd.customer?qd.customer.date||'':'')+'</div><table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:#003f62;color:#fff"><th style="padding:8px 4px;width:35px">STT</th><th style="padding:8px 4px;text-align:left">Tên sản phẩm</th><th style="padding:8px 4px;width:90px">Mã SP</th><th style="padding:8px 4px;width:40px">SL</th><th style="padding:8px 4px;text-align:left;width:120px">Ghi chú</th></tr></thead><tbody>'+rows+'</tbody></table><div style="margin-top:60px;display:flex;justify-content:space-around;font-size:12px"><div style="text-align:center"><b>Bên giao</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div><div style="text-align:center"><b>Bên nhận</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div></div>';}
46
 
47
- function doOverride(){if(typeof getQuoteData!=='function'||typeof html2canvas!=='function')return false;window._origGetQuoteData=getQuoteData;window.shareQuoteImage=async function(){var d=getData(),code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);try{var cv=await toCanvas(buildQuoteHTML(d,code,qr),1000);var blob=await new Promise(function(r){cv.toBlob(r,'image/png');});var f=new File([blob],code+'.png',{type:'image/png'});if(navigator.share&&navigator.canShare&&navigator.canShare({files:[f]})){await navigator.share({title:code,files:[f]});}else{var url=URL.createObjectURL(blob);var p=document.createElement('div');p.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:999';p.innerHTML='<div style="background:#fff;border-radius:16px;max-width:90vw;max-height:85vh"><img src="'+url+'" style="max-width:100%"><a href="'+url+'" download="'+code+'.png" style="padding:10px 20px;background:#003f62;color:#fff;border-radius:8px">⬇ Tải</a></div>';document.body.appendChild(p);}}catch(e){console.error(e);}};window.exportPDF=async function(){var d=getData(),code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);try{await _doExportPDF(d,code);}catch(e){console.error(e);alert('Không xuất được PDF: '+(e&&e.message?e.message:e));}};window.exportDeliveryExcel=async function(order,opt){if(typeof ExcelJS==='undefined')return;var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();var code=order&&order.code?order.code:getEffectiveOrderCode();var activeBtn=document.activeElement;if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;if(activeBtn)setExportButtonState(activeBtn,!0);try{await _doExportDeliveryExcel(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,!1);}};window.exportDeliveryPDF=async function(order,opt){var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();var code=order&&order.code?order.code:getEffectiveOrderCode();var activeBtn=document.activeElement;if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;if(activeBtn)setExportButtonState(activeBtn,!0);try{await _doExportDeliveryPDF(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,!1);}};window.exportExcel=async function(){if(typeof ExcelJS==='undefined')return;var d=getData(),qd=d.qd,code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);await _doExportExcel(d,qd,code,qr);};window.getQuoteData=function(){var qd=window._origGetQuoteData.apply(this,arguments);if(qd.customer)qd.customer.orderCode=getEffectiveOrderCode();return qd;};console.log('✅ QR Payment v1039 — Multi-download Excel fixed');return true;}
48
 
49
  async function _doExportPDF(d,code){var qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);var cv=await toCanvas(buildQuoteHTML(d,code,qr),1000);var img=cv.toDataURL('image/jpeg',0.92);var pageW=595,pageH=842,imgW=pageW,imgH=cv.height*imgW/cv.width;var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});if(imgH<=pageH){pdf.addImage(img,'JPEG',0,0,imgW,imgH);}else{var y=0,rem=imgH,page=0;while(rem>0){if(page>0)pdf.addPage();pdf.addImage(img,'JPEG',0,-y,imgW,imgH);y+=pageH;rem-=pageH;page++;}}pdf.save(code+'.pdf');}
50
 
51
  async function _doExportDeliveryPDF(qd,code){var cv=await toCanvas(buildDeliveryHTML({qd:qd},code),700);var img=cv.toDataURL('image/png');var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});var margin=25,usableW=595-margin*2,ratio=cv.height/cv.width,imgH=usableW*ratio;if(imgH>842-margin*2){imgH=842-margin*2;usableW=imgH/ratio;}pdf.addImage(img,'PNG',margin,margin,usableW,imgH);pdf.save('GH-'+code+'.pdf');}
52
 
53
  async function _doExportExcel(d,qd,code,qrUrl){
54
- var MONEY_FMT='#,##0"đ"';var NUM_FMT='#,##0';var WHITE={argb:'FFFFFFFF'};var WB={top:{style:'thin',color:WHITE},bottom:{style:'thin',color:WHITE},left:{style:'thin',color:WHITE},right:{style:'thin',color:WHITE}};
55
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');
56
  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}];
57
  ws.mergeCells('A3:J3');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'};
@@ -61,12 +98,11 @@ async function _doExportExcel(d,qd,code,qrUrl){
61
  ws.getCell('H6').value='Ngày:';ws.mergeCells('I6:J6');ws.getCell('I6').value=qd.customer?qd.customer.date:'';
62
  ws.getCell('A7').value='Email:';ws.mergeCells('B7:E7');ws.getCell('B7').value=qd.customer?qd.customer.email:'';
63
  ws.getCell('A8').value='Địa chỉ:';ws.mergeCells('B8:J8');ws.getCell('B8').value=qd.customer?qd.customer.addr:'';
64
- var hr=ws.getRow(10);['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){var c=hr.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'}};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};});
65
  var cr=11,firstItemRow=cr,ckPct=Number((d.discountPercent||0));
66
  for(var i=0;i<qd.items.length;i++){
67
  var it=qd.items[i],row=ws.getRow(cr);row.height=50;
68
  var bgColor=i%2===0?'FFF8FAFC':'FFFFFFFF';
69
- var RB={top:{style:'thin',color:{argb:bgColor}},bottom:{style:'thin',color:{argb:bgColor}},left:{style:'thin',color:{argb:bgColor}},right:{style:'thin',color:{argb:bgColor}}};
70
  row.getCell(1).value=it.stt||i+1;row.getCell(3).value=it.name||'';
71
  row.getCell(3).font={bold:true,size:9};row.getCell(4).value=it.model||'';
72
  row.getCell(5).value=it.specs||'';row.getCell(5).font={size:8,color:{argb:'FF64748B'}};
@@ -75,35 +111,26 @@ async function _doExportExcel(d,qd,code,qrUrl){
75
  row.getCell(8).value=Number(it.discPrice||it.price||0);row.getCell(8).numFmt=MONEY_FMT;
76
  if((it.discPrice||0)&&(it.price||0)&&it.discPrice<it.price)row.getCell(8).font={bold:true,color:{argb:'FFDC3545'}};
77
  row.getCell(9).value=Number(it.total||0);row.getCell(9).numFmt=MONEY_FMT;row.getCell(9).font={bold:true};
78
- row.getCell(10).value=it.note||'';
79
- for(var ci=1;ci<=10;ci++){row.getCell(ci).fill={type:'pattern',pattern:'solid',fgColor:{argb:bgColor}};row.getCell(ci).border=RB;}
80
  cr++;
81
  }
82
  ws.getRow(cr).height=28;
83
- ws.mergeCells(cr,1,cr,5);ws.getCell('A'+cr).value='TỔNG CỘNG';ws.getCell('A'+cr).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};
84
- ws.getCell('E'+cr).value={formula:'SUM(I'+firstItemRow+':I'+(cr-1)+')',result:Number(d.grandTotal||0)};ws.getCell('E'+cr).numFmt=MONEY_FMT;ws.getCell('E'+cr).font={bold:true,size:14,color:{argb:'FFF0B840'}};
85
  wb.calcProperties.fullCalcOnLoad=true;
86
  var buf=await wb.xlsx.writeBuffer();
87
  var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
88
- var url=URL.createObjectURL(blob);
89
- var a=document.createElement('a');
90
- a.href=url;a.download=code+'.xlsx';
91
- document.body.appendChild(a);a.click();
92
- document.body.removeChild(a);
93
- // FIX v1039: KHÔNG REVOKE URL - để blob tự giải phóng khi unload
94
  }
95
 
96
  async function _doExportDeliveryExcel(qd,code){
97
- var WHITE={argb:'FFFFFFFF'};var WB={top:{style:'thin',color:WHITE},bottom:{style:'thin',color:WHITE},left:{style:'thin',color:WHITE},right:{style:'thin',color:WHITE}};
98
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Giao hàng');
99
  ws.views=[{showGridLines:false}];ws.columns=[{width:5},{width:34},{width:14},{width:8},{width:20}];
100
- ws.mergeCells('A1:E1');ws.getCell('A1').value='PHIẾU GIAO HÀNG';ws.getCell('A1').font={bold:true,size:16,color:{argb:'FF003F62'}};ws.getCell('A1').alignment={horizontal:'center'};
101
  ws.getCell('A2').value='KH: '+(qd.customer?qd.customer.name:'');
102
  ws.getCell('D2').value='Mã: '+code;
103
  ws.getCell('A3').value='SĐT: '+(qd.customer?qd.customer.phone:'');
104
- ws.getCell('D3').value='Ngày: '+(qd.customer?qd.customer.date||'':'');
105
  ws.getCell('A4').value='Địa chỉ: '+(qd.customer?qd.customer.addr||'':'');
106
- var hr=ws.getRow(6);['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){var c=hr.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'}};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'}};});
107
  var cr=7,items=qd.items||[],totalQty=0;
108
  for(var i=0;i<items.length;i++){
109
  var it=items[i],row=ws.getRow(cr);row.height=20;
@@ -115,12 +142,7 @@ async function _doExportDeliveryExcel(qd,code){
115
  var totalQtyRow=cr;ws.mergeCells(totalQtyRow,1,totalQtyRow,3);ws.getCell('A'+totalQtyRow).value='TỔNG CỘNG SỐ LƯỢNG';ws.getCell('D'+totalQtyRow).value=totalQty;
116
  var buf=await wb.xlsx.writeBuffer();
117
  var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
118
- var url=URL.createObjectURL(blob);
119
- var a=document.createElement('a');
120
- a.href=url;a.download='GH-'+code+'.xlsx';
121
- document.body.appendChild(a);a.click();
122
- document.body.removeChild(a);
123
- // FIX v1039: KHÔNG REVOKE URL - để blob tự giải phóng khi unload
124
  }
125
 
126
  var att=0,t=setInterval(function(){if(doOverride()||att>120)clearInterval(t);att++;},500);
@@ -128,13 +150,43 @@ var att=0,t=setInterval(function(){if(doOverride()||att>120)clearInterval(t);att
128
  document.addEventListener('click',function(e){
129
  var btn=e.target.closest&&e.target.closest('button,a');if(!btn)return;
130
  var txt=(btn.textContent||'').toLowerCase();if(!window._origGetQuoteData)return;
131
- if(txt.includes('excel')&&txt.includes('xuất')){e.preventDefault();e.stopPropagation();if(btn.dataset.vaiExporting==='1')return;setExportButtonState(btn,!0);window.exportExcel().finally(function(){setExportButtonState(btn,!1);});return!1;}
132
- if((txt.includes('pdf')&&txt.includes('xuất'))||(txt.includes('pdf')&&!txt.includes('giao hàng'))){e.preventDefault();e.stopPropagation();if(btn.dataset.vaiExporting==='1')return;setExportButtonState(btn,!0);window.exportPDF().finally(function(){setExportButtonState(btn,!1);});return!1;}
133
- if(txt.includes('chia sẻ')&&txt.includes('ảnh')){e.preventDefault();e.stopPropagation();if(btn.dataset.vaiExporting==='1')return;setExportButtonState(btn,!0);window.shareQuoteImage().finally(function(){setExportButtonState(btn,!1);});return!1;}
134
- if((txt.includes('gh')||txt.includes('giao hàng'))&&txt.includes('excel')){e.preventDefault();e.stopPropagation();if(btn.dataset.vaiExporting==='1')return;setExportButtonState(btn,!0);window.exportDeliveryExcel().finally(function(){setExportButtonState(btn,!1);});return!1;}
135
- if((txt.includes('gh')||txt.includes('giao hàng'))&&txt.includes('pdf')){e.preventDefault();e.stopPropagation();if(btn.dataset.vaiExporting==='1')return;setExportButtonState(btn,!0);window.exportDeliveryPDF().finally(function(){setExportButtonState(btn,!1);});return!1;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  },!0);
137
 
138
  window.VAI_QR={getEffectiveOrderCode:getEffectiveOrderCode,getQRUrl:getQRUrl,generateOrderCode:generateOrderCode,parsePrompt:parsePrompt,getPromptText:getPromptText,injectToModal:injectToModal,getData:getData,buildQuoteHTML:buildQuoteHTML,buildDeliveryHTML:buildDeliveryHTML,toCanvas:toCanvas,exportPDF:_doExportPDF,exportExcel:_doExportExcel,exportDeliveryPDF:_doExportDeliveryPDF,exportDeliveryExcel:_doExportDeliveryExcel,fmt:fmt,compactKey:compactKey,matchItemDiscount:matchItemDiscount,parseItemDiscountLine:parseItemDiscountLine,applyPromptDiscounts:applyPromptDiscounts,setExportButtonState:setExportButtonState,orderToVAIData:orderToVAIData};
139
- console.log('[VAI QR v1039] Loaded - Multi-download Excel fix');
140
  })();
 
1
+ /* V.AI STUDIO — QR Payment / Quote Data v1039 - FIXED
2
+ * FIX TRIỆT ĐỐI: Multi-download Excel - added blob registry
 
3
  */
4
+ (function(){'use strict';
5
+
6
+ // ===== BLOB REGISTRY - QUAN TRỌNG =====
7
+ window.__VAI_BLOB_REG = window.__VAI_BLOB_REG || { blobs: [], isUnloading: false };
8
+ var blobRegistry = window.__VAI_BLOB_REG.blobs;
9
+ var isUnloading = window.__VAI_BLOB_REG.isUnloading;
10
 
11
  // Cleanup khi tab đóng
12
+ function cleanupBlobs(){
13
+ window.__VAI_BLOB_REG.isUnloading = true;
14
+ blobRegistry.forEach(function(e) {
15
+ if (!e.revoked) { try { URL.revokeObjectURL(e.url); e.revoked = true; } catch(err) {} }
16
+ });
17
+ blobRegistry = [];
18
+ }
19
+ window.addEventListener('pagehide', cleanupBlobs);
20
+ window.addEventListener('beforeunload', cleanupBlobs);
21
+ window.addEventListener('unload', cleanupBlobs);
22
+
23
+ // Patch URL.revokeObjectURL
24
+ if (!window.__VAI_REVOKE_PATCHED_QR) {
25
+ var _origRevoke = URL.revokeObjectURL;
26
+ URL.revokeObjectURL = function(url) {
27
+ if (window.__VAI_BLOB_REG.isUnloading) return _origRevoke.call(this, url);
28
+ if (!url || typeof url !== 'string' || !url.startsWith('blob:')) {
29
+ return _origRevoke.call(this, url);
30
+ }
31
+ var exists = blobRegistry.find(function(e){ return e.url === url; });
32
+ if (!exists) {
33
+ blobRegistry.push({ url: url, revoked: false, created: Date.now() });
34
+ }
35
+ return;
36
+ };
37
+ window.__VAI_REVOKE_PATCHED_QR = true;
38
+ }
39
 
40
+ // Download function với registry
41
+ function dlBlob(blob, filename) {
42
+ var url = URL.createObjectURL(blob);
43
+ blobRegistry.push({ url: url, revoked: false });
44
+ var a = document.createElement('a');
45
+ a.href = url;
46
+ a.download = filename;
47
+ a.style.cssText = 'position:absolute;left:-9999px';
48
+ document.body.appendChild(a);
49
+ a.click();
50
+ setTimeout(function() { try { document.body.removeChild(a); } catch(err) {} }, 100);
51
+ }
52
 
53
  var BANK_ID='vib',BANK_ACCOUNT='918258385',BANK_NAME='TRAN QUOC VUONG',BANK_NAME_VN='Trần Quốc Vương';
54
  function stripDiacritics(s){return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D');}
 
81
 
82
  function buildDeliveryHTML(qd,code){var rows=(qd.items||[]).map(function(it,i){return '<tr style="border-bottom:1px solid #e2e8f0"><td style="padding:8px 4px;text-align:center;font-size:12px">'+(it.stt||(i+1))+'</td><td style="padding:8px 4px;font-weight:600;font-size:12px">'+(it.name||'')+'</td><td style="padding:8px 4px;text-align:center;font-size:11px">'+(it.model||'')+'</td><td style="padding:8px 4px;text-align:center;font-weight:700;font-size:13px">'+(it.qty||1)+'</td><td style="padding:8px 4px;font-size:11px">'+(it.note||'')+'</td></tr>';}).join('');return '<div style="text-align:center;margin-bottom:14px"><div style="font-size:20px;font-weight:900;color:#003f62">PHIẾU GIAO HÀNG</div></div><div style="margin-bottom:12px;font-size:11px;line-height:1.6"><b>Khách hàng:</b> '+(qd.customer?qd.customer.name:'')+' | <b>SĐT:</b> '+(qd.customer?qd.customer.phone:'')+'<br><b>Địa chỉ:</b> '+(qd.customer?qd.customer.addr||'':'')+'<br><b>Mã đơn:</b> '+code+' | <b>Ngày:</b> '+(qd.customer?qd.customer.date||'':'')+'</div><table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:#003f62;color:#fff"><th style="padding:8px 4px;width:35px">STT</th><th style="padding:8px 4px;text-align:left">Tên sản phẩm</th><th style="padding:8px 4px;width:90px">Mã SP</th><th style="padding:8px 4px;width:40px">SL</th><th style="padding:8px 4px;text-align:left;width:120px">Ghi chú</th></tr></thead><tbody>'+rows+'</tbody></table><div style="margin-top:60px;display:flex;justify-content:space-around;font-size:12px"><div style="text-align:center"><b>Bên giao</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div><div style="text-align:center"><b>Bên nhận</b><br>(Ký, họ tên)<div style="margin-top:30px">_______________</div></div></div>';}
83
 
84
+ function doOverride(){if(typeof getQuoteData!=='function'||typeof html2canvas!=='function')return false;window._origGetQuoteData=getQuoteData;window.shareQuoteImage=async function(){var d=getData(),code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);try{var cv=await toCanvas(buildQuoteHTML(d,code,qr),1000);var blob=await new Promise(function(r){cv.toBlob(r,'image/png');});var f=new File([blob],code+'.png',{type:'image/png'});if(navigator.share&&navigator.canShare&&navigator.canShare({files:[f]})){await navigator.share({title:code,files:[f]});}else{var url=URL.createObjectURL(blob);var p=document.createElement('div');p.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:999';p.innerHTML='<div style="background:#fff;border-radius:16px;max-width:90vw;max-height:85vh"><img src="'+url+'" style="max-width:100%"><a href="'+url+'" download="'+code+'.png" style="padding:10px 20px;background:#003f62;color:#fff;border-radius:8px">⬇ Tải</a></div>';document.body.appendChild(p);}}catch(e){console.error(e);}};window.exportPDF=async function(){var d=getData(),code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);try{await _doExportPDF(d,code);}catch(e){console.error(e);alert('Không xuất được PDF: '+(e&&e.message?e.message:e));}};window.exportDeliveryExcel=async function(order,opt){if(typeof ExcelJS==='undefined')return;var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();var code=order&&order.code?order.code:getEffectiveOrderCode();var activeBtn=document.activeElement;if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;if(activeBtn)setExportButtonState(activeBtn,!0);try{await _doExportDeliveryExcel(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,!1);}};window.exportDeliveryPDF=async function(order,opt){var qd=order&&order.items?orderToVAIData(order).qd:window._origGetQuoteData();var code=order&&order.code?order.code:getEffectiveOrderCode();var activeBtn=document.activeElement;if(activeBtn&&activeBtn.dataset.vaiExporting==='1')return;if(activeBtn)setExportButtonState(activeBtn,!0);try{await _doExportDeliveryPDF(qd,code);}finally{if(activeBtn)setExportButtonState(activeBtn,!1);}};window.exportExcel=async function(){if(typeof ExcelJS==='undefined')return;var d=getData(),qd=d.qd,code=getEffectiveOrderCode(),qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);await _doExportExcel(d,qd,code,qr);};window.getQuoteData=function(){var qd=window._origGetQuoteData.apply(this,arguments);if(qd.customer)qd.customer.orderCode=getEffectiveOrderCode();return qd;};console.log('✅ QR Payment v1039 — Multi-download Excel FIXED');return true;}
85
 
86
  async function _doExportPDF(d,code){var qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);var cv=await toCanvas(buildQuoteHTML(d,code,qr),1000);var img=cv.toDataURL('image/jpeg',0.92);var pageW=595,pageH=842,imgW=pageW,imgH=cv.height*imgW/cv.width;var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});if(imgH<=pageH){pdf.addImage(img,'JPEG',0,0,imgW,imgH);}else{var y=0,rem=imgH,page=0;while(rem>0){if(page>0)pdf.addPage();pdf.addImage(img,'JPEG',0,-y,imgW,imgH);y+=pageH;rem-=pageH;page++;}}pdf.save(code+'.pdf');}
87
 
88
  async function _doExportDeliveryPDF(qd,code){var cv=await toCanvas(buildDeliveryHTML({qd:qd},code),700);var img=cv.toDataURL('image/png');var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});var margin=25,usableW=595-margin*2,ratio=cv.height/cv.width,imgH=usableW*ratio;if(imgH>842-margin*2){imgH=842-margin*2;usableW=imgH/ratio;}pdf.addImage(img,'PNG',margin,margin,usableW,imgH);pdf.save('GH-'+code+'.pdf');}
89
 
90
  async function _doExportExcel(d,qd,code,qrUrl){
91
+ var MONEY_FMT='#,##0"đ"';var NUM_FMT='#,##0';var WHITE={argb:'FFFFFFFF'};
92
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');
93
  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}];
94
  ws.mergeCells('A3:J3');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'};
 
98
  ws.getCell('H6').value='Ngày:';ws.mergeCells('I6:J6');ws.getCell('I6').value=qd.customer?qd.customer.date:'';
99
  ws.getCell('A7').value='Email:';ws.mergeCells('B7:E7');ws.getCell('B7').value=qd.customer?qd.customer.email:'';
100
  ws.getCell('A8').value='Địa chỉ:';ws.mergeCells('B8:J8');ws.getCell('B8').value=qd.customer?qd.customer.addr:'';
101
+ var hr=ws.getRow(10);['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){var c=hr.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'}};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};});
102
  var cr=11,firstItemRow=cr,ckPct=Number((d.discountPercent||0));
103
  for(var i=0;i<qd.items.length;i++){
104
  var it=qd.items[i],row=ws.getRow(cr);row.height=50;
105
  var bgColor=i%2===0?'FFF8FAFC':'FFFFFFFF';
 
106
  row.getCell(1).value=it.stt||i+1;row.getCell(3).value=it.name||'';
107
  row.getCell(3).font={bold:true,size:9};row.getCell(4).value=it.model||'';
108
  row.getCell(5).value=it.specs||'';row.getCell(5).font={size:8,color:{argb:'FF64748B'}};
 
111
  row.getCell(8).value=Number(it.discPrice||it.price||0);row.getCell(8).numFmt=MONEY_FMT;
112
  if((it.discPrice||0)&&(it.price||0)&&it.discPrice<it.price)row.getCell(8).font={bold:true,color:{argb:'FFDC3545'}};
113
  row.getCell(9).value=Number(it.total||0);row.getCell(9).numFmt=MONEY_FMT;row.getCell(9).font={bold:true};
 
 
114
  cr++;
115
  }
116
  ws.getRow(cr).height=28;
117
+ ws.mergeCells(cr,1,cr,5);ws.getCell('A'+cr).value='TỔNG CỘNG';ws.getCell('A'+cr).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};};
118
+ ws.getCell('E'+cr).value={formula:'SUM(I'+firstItemRow+':I'+(cr-1)+')',result:Number(d.grandTotal||0)};ws.getCell('E'+cr).numFmt=MONEY_FMT;ws.getCell('E'+cr).font={bold:true,size:14,color:{argb:'FFF0B840'};};
119
  wb.calcProperties.fullCalcOnLoad=true;
120
  var buf=await wb.xlsx.writeBuffer();
121
  var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
122
+ dlBlob(blob,code+'.xlsx');
 
 
 
 
 
123
  }
124
 
125
  async function _doExportDeliveryExcel(qd,code){
 
126
  var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Giao hàng');
127
  ws.views=[{showGridLines:false}];ws.columns=[{width:5},{width:34},{width:14},{width:8},{width:20}];
128
+ ws.mergeCells('A1:E1');ws.getCell('A1').value='PHIẾU GIAO HÀNG';ws.getCell('A1').font={bold:true,size:16,color:{argb:'FF003F62'};};
129
  ws.getCell('A2').value='KH: '+(qd.customer?qd.customer.name:'');
130
  ws.getCell('D2').value='Mã: '+code;
131
  ws.getCell('A3').value='SĐT: '+(qd.customer?qd.customer.phone:'');
 
132
  ws.getCell('A4').value='Địa chỉ: '+(qd.customer?qd.customer.addr||'':'');
133
+ var hr=ws.getRow(6);['STT','Tên sản phẩm','Mã SP','SL','Ghi chú'].forEach(function(h,i){var c=hr.getCell(i+1);c.value=h;c.font={bold:true,color:{argb:'FFFFFFFF'}};c.fill={type:'pattern',pattern:'solid',fgColor:{argb:'FF003F62'};};});
134
  var cr=7,items=qd.items||[],totalQty=0;
135
  for(var i=0;i<items.length;i++){
136
  var it=items[i],row=ws.getRow(cr);row.height=20;
 
142
  var totalQtyRow=cr;ws.mergeCells(totalQtyRow,1,totalQtyRow,3);ws.getCell('A'+totalQtyRow).value='TỔNG CỘNG SỐ LƯỢNG';ws.getCell('D'+totalQtyRow).value=totalQty;
143
  var buf=await wb.xlsx.writeBuffer();
144
  var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
145
+ dlBlob(blob,'GH-'+code+'.xlsx');
 
 
 
 
 
146
  }
147
 
148
  var att=0,t=setInterval(function(){if(doOverride()||att>120)clearInterval(t);att++;},500);
 
150
  document.addEventListener('click',function(e){
151
  var btn=e.target.closest&&e.target.closest('button,a');if(!btn)return;
152
  var txt=(btn.textContent||'').toLowerCase();if(!window._origGetQuoteData)return;
153
+ if(txt.includes('excel')&&txt.includes('xuất')){
154
+ e.preventDefault();e.stopPropagation();
155
+ if(btn.dataset.vaiExporting==='1')return;
156
+ setExportButtonState(btn,!0);
157
+ window.exportExcel().finally(function(){setExportButtonState(btn,!1);});
158
+ return!1;
159
+ }
160
+ if((txt.includes('pdf')&&txt.includes('xuất'))||(txt.includes('pdf')&&!txt.includes('giao hàng'))){
161
+ e.preventDefault();e.stopPropagation();
162
+ if(btn.dataset.vaiExporting==='1')return;
163
+ setExportButtonState(btn,!0);
164
+ window.exportPDF().finally(function(){setExportButtonState(btn,!1);});
165
+ return!1;
166
+ }
167
+ if(txt.includes('chia sẻ')&&txt.includes('ảnh')){
168
+ e.preventDefault();e.stopPropagation();
169
+ if(btn.dataset.vaiExporting==='1')return;
170
+ setExportButtonState(btn,!0);
171
+ window.shareQuoteImage().finally(function(){setExportButtonState(btn,!1);});
172
+ return!1;
173
+ }
174
+ if((txt.includes('gh')||txt.includes('giao hàng'))&&txt.includes('excel')){
175
+ e.preventDefault();e.stopPropagation();
176
+ if(btn.dataset.vaiExporting==='1')return;
177
+ setExportButtonState(btn,!0);
178
+ window.exportDeliveryExcel().finally(function(){setExportButtonState(btn,!1);});
179
+ return!1;
180
+ }
181
+ if((txt.includes('gh')||txt.includes('giao hàng'))&&txt.includes('pdf')){
182
+ e.preventDefault();e.stopPropagation();
183
+ if(btn.dataset.vaiExporting==='1')return;
184
+ setExportButtonState(btn,!0);
185
+ window.exportDeliveryPDF().finally(function(){setExportButtonState(btn,!1);});
186
+ return!1;
187
+ }
188
  },!0);
189
 
190
  window.VAI_QR={getEffectiveOrderCode:getEffectiveOrderCode,getQRUrl:getQRUrl,generateOrderCode:generateOrderCode,parsePrompt:parsePrompt,getPromptText:getPromptText,injectToModal:injectToModal,getData:getData,buildQuoteHTML:buildQuoteHTML,buildDeliveryHTML:buildDeliveryHTML,toCanvas:toCanvas,exportPDF:_doExportPDF,exportExcel:_doExportExcel,exportDeliveryPDF:_doExportDeliveryPDF,exportDeliveryExcel:_doExportDeliveryExcel,fmt:fmt,compactKey:compactKey,matchItemDiscount:matchItemDiscount,parseItemDiscountLine:parseItemDiscountLine,applyPromptDiscounts:applyPromptDiscounts,setExportButtonState:setExportButtonState,orderToVAIData:orderToVAIData};
191
+ console.log('[VAI QR v1039 FIXED] Loaded - Multi-download Excel with Registry');
192
  })();