| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| (function() { |
| 'use strict'; |
| |
| |
| window.__VAI_BLOB_REGISTRY = window.__VAI_BLOB_REGISTRY || { blobs: [], isUnloading: false }; |
| var reg = window.__VAI_BLOB_REGISTRY; |
| |
| |
| function cleanupBlobs() { |
| reg.isUnloading = true; |
| reg.blobs.forEach(function(b) { |
| if (!b.revoked) { |
| try { URL.revokeObjectURL(b.url); b.revoked = true; } catch(e) {} |
| } |
| }); |
| reg.blobs = []; |
| } |
| window.addEventListener('pagehide', cleanupBlobs); |
| window.addEventListener('beforeunload', cleanupBlobs); |
| window.addEventListener('unload', cleanupBlobs); |
| |
| |
| if (!window.__VAI_REVOKE_PATCHED_AUTO) { |
| var origRevoke = URL.revokeObjectURL; |
| URL.revokeObjectURL = function(url) { |
| if (reg.isUnloading || !url || typeof url !== 'string' || !url.startsWith('blob:')) { |
| return origRevoke.call(this, url); |
| } |
| var exists = reg.blobs.find(function(b) { return b.url === url; }); |
| if (!exists) reg.blobs.push({ url: url, revoked: false, created: Date.now() }); |
| return undefined; |
| }; |
| window.__VAI_REVOKE_PATCHED_AUTO = true; |
| } |
| |
| |
| function fmt(n) { return n && !isNaN(n) ? Number(n).toLocaleString('vi-VN') + 'đ' : '0đ'; } |
| function stripDiacritics(s) { return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D'); } |
| function genCode(name) { |
| var d = new Date(), dd = String(d.getDate()).padStart(2,'0'), mm = String(d.getMonth()+1).padStart(2,'0'), yy = String(d.getFullYear()).slice(-2); |
| var ini = ''; |
| if (name && name.trim()) { |
| name.trim().split(/\s+/).forEach(function(w) { |
| if (w) { var c = stripDiacritics(w.charAt(0)).toUpperCase(); if (/[A-Z]/.test(c)) ini += c; } |
| }); |
| } |
| return 'VAS' + (ini || 'X') + dd + mm + yy; |
| } |
| |
| |
| function setBtn(b, exporting) { |
| if (!b) return; |
| if (exporting) { |
| b.disabled = true; |
| b.dataset.vaiExporting = '1'; |
| b._orig = b.innerHTML; |
| b.innerHTML = '<span style="opacity:0.7">⏳ Đang xuất...</span>'; |
| } else { |
| b.disabled = false; |
| b.dataset.vaiExporting = '0'; |
| if (b._orig) b.innerHTML = b._orig; |
| } |
| } |
| |
| |
| function dlBlob(blob, filename) { |
| var url = URL.createObjectURL(blob); |
| reg.blobs.push({ url: url, revoked: false }); |
| var a = document.createElement('a'); |
| a.href = url; a.download = filename; a.style.cssText = 'position:absolute;left:-9999px'; |
| document.body.appendChild(a); a.click(); |
| setTimeout(function() { try { document.body.removeChild(a); } catch(e) {} }, 100); |
| } |
| |
| |
| async function exportQuoteExcel(d, c) { |
| if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false; |
| try { |
| 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}]; |
| 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' }; |
| ws.getCell('A5').value='Khách hàng:'; ws.mergeCells('B5:E5'); ws.getCell('B5').value=d.customer?.name||''; |
| ws.getCell('H5').value='Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value=c; |
| ws.getCell('A6').value='SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value=d.customer?.phone||''; |
| ws.getCell('H6').value='Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value=d.customer?.date||''; |
| ws.getCell('A7').value='Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value=d.customer?.email||''; |
| ws.getCell('A8').value='Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value=d.customer?.addr||''; |
| |
| 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'}}; |
| }); |
| |
| var cr = 11, fr = cr; |
| (d.items||[]).forEach(function(it,i){ |
| var r = ws.getRow(cr), bg = i%2===0?'FFF8FAFC':'FFFFFFFF'; r.height=50; |
| r.getCell(1).value=it.stt||i+1; r.getCell(3).value=it.name||''; r.getCell(3).font={bold:true,size:9}; |
| r.getCell(4).value=it.model||''; r.getCell(5).value=it.specs||''; r.getCell(5).font={size:8,color:{argb:'FF64748B'}}; |
| r.getCell(6).value=Number(it.qty||1); r.getCell(6).numFmt='#,##0'; |
| r.getCell(7).value=Number(it.price||0); r.getCell(7).numFmt='#,##0"đ"'; |
| r.getCell(8).value=Number(it.discPrice||it.price||0); r.getCell(8).numFmt='#,##0"đ"'; |
| r.getCell(8).font=(it.price||0)>(it.discPrice||0)?{bold:true,color:{argb:'FFDC3545'}}:{}; |
| r.getCell(9).value=Number(it.total||0); r.getCell(9).numFmt='#,##0"đ"'; r.getCell(9).font={bold:true}; |
| r.getCell(10).value=it.note||''; cr++; |
| }); |
| |
| ws.getRow(cr).height=28; 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'}}; |
| ws.getCell('E'+cr).value={formula:'SUM(I'+fr+':I'+(cr-1)+')',result:Number(d.grandTotal||0)}; ws.getCell('E'+cr).numFmt='#,##0"đ"'; |
| ws.getCell('E'+cr).font={bold:true,size:14,color:{argb:'FFF0B840'}}; |
| wb.calcProperties.fullCalcOnLoad=true; |
| |
| var buf = await wb.xlsx.writeBuffer(); |
| dlBlob(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), c+'.xlsx'); |
| return true; |
| } catch(e) { console.error('[AutoPatch] exportQuoteExcel error:',e); alert('❌ Lỗi: '+e.message); return false; } |
| } |
| |
| |
| async function exportDeliveryExcel(d, c) { |
| if (typeof ExcelJS === 'undefined') return alert('⚠️ Chưa có ExcelJS'), false; |
| try { |
| var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Giao hàng'); |
| ws.views = [{ showGridLines: false }]; ws.columns = [{width:5},{width:34},{width:14},{width:8},{width:20}]; |
| 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'}; |
| ws.getCell('A2').value='KH: '+(d.customer?.name||''); ws.getCell('D2').value='Mã: '+c; |
| ws.getCell('A3').value='SĐT: '+(d.customer?.phone||''); ws.getCell('D3').value='Ngày: '+(d.customer?.date||''); |
| ws.getCell('A4').value='Địa chỉ: '+(d.customer?.addr||''); |
| |
| 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'}}; |
| }); |
| |
| var cr = 7, tq = 0; |
| (d.items||[]).forEach(function(it,i){ |
| var r = ws.getRow(cr); r.height=20; |
| r.getCell(1).value=it.stt||i+1; r.getCell(2).value=it.name||''; r.getCell(3).value=it.model||''; r.getCell(4).value=it.qty||1; r.getCell(5).value=it.note||''; |
| tq += Number(it.qty||1); cr++; |
| }); |
| |
| ws.mergeCells(cr,1,cr,3); ws.getCell('A'+cr).value='TỔNG CỘNG SỐ LƯỢNG'; ws.getCell('D'+cr).value=tq; |
| |
| var buf = await wb.xlsx.writeBuffer(); |
| dlBlob(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), 'GH-'+c+'.xlsx'); |
| return true; |
| } catch(e) { console.error('[AutoPatch] exportDeliveryExcel error:',e); alert('❌ Lỗi: '+e.message); return false; } |
| } |
| |
| |
| function init() { |
| |
| if (typeof window.VAI_EXCEL_FIX !== 'undefined' && typeof window.VAI_EXCEL_FIX.exportQuoteExcel === 'function') { |
| console.log('[AutoPatch v2.0] VAI_EXCEL_FIX v2 already loaded - done'); |
| return; |
| } |
| |
| |
| var s = document.createElement('script'); |
| s.src = '/vai-excel-fix-v2.js?v=' + Date.now(); |
| s.onload = function() { |
| console.log('[AutoPatch v2.0] Excel fix v2 loaded'); |
| }; |
| s.onerror = function() { |
| console.warn('[AutoPatch v2.0] Failed to load v2, applying inline patch'); |
| |
| if (typeof window.exportDeliveryExcel === 'function') { |
| var origDE = window.exportDeliveryExcel; |
| window.exportDeliveryExcel = async function(order, opt) { |
| var btn = document.activeElement; |
| if (btn && btn.dataset.vaiExporting === '1') return; |
| if (btn) setBtn(btn, true); |
| try { |
| var d = { customer: { name: order?.customer||'', phone: order?.phone||'', addr: order?.addr||'', date: order?.date||'' }, |
| items: (order?.items||[]).map(function(it,i){return{stt:i+1,name:it.name||'',model:it.model||'',qty:it.qty||1,note:it.note||'',specs:it.specs||'',price:it.price||0,discPrice:it.discPrice||it.price||0,total:it.total||0};}), |
| grandTotal: order?.grandTotal||0 }; |
| var c = order?.code || genCode(order?.customer||''); |
| await exportDeliveryExcel(d, c); |
| } finally { if (btn) setBtn(btn, false); } |
| }; |
| } |
| }; |
| document.head.appendChild(s); |
| } |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', init); |
| } else { |
| init(); |
| } |
| setTimeout(init, 2000); |
| setTimeout(init, 5000); |
| |
| })(); |