| |
| |
| |
| |
| |
| (function(){ |
| 'use strict'; |
|
|
| |
| var AUTO_FIX_VERSION = 'v2.0'; |
| var SCRIPT_INJECTED = false; |
|
|
| |
| window.__VAI_BLOB = window.__VAI_BLOB || { blobs: [], isUnloading: false }; |
| var REG = window.__VAI_BLOB; |
|
|
| |
| function cleanupBlobs() { |
| REG.isUnloading = true; |
| REG.blobs.forEach(function(e) { |
| e.revoked || (URL.revokeObjectURL(e.url), e.revoked = true); |
| }); |
| REG.blobs = []; |
| } |
| window.addEventListener('pagehide', cleanupBlobs); |
| window.addEventListener('beforeunload', cleanupBlobs); |
| window.addEventListener('unload', cleanupBlobs); |
|
|
| |
| if (!window.__VAI_REVOKE_PATCHED) { |
| var _orig = URL.revokeObjectURL; |
| URL.revokeObjectURL = function(url) { |
| if (REG.isUnloading || !url || typeof url !== 'string' || !url.startsWith('blob:')) { |
| return _orig.call(this, url); |
| } |
| |
| var exists = REG.blobs.find(function(e) { return e.url === url; }); |
| exists || REG.blobs.push({ url: url, revoked: false }); |
| console.log('[Auto Inject] Blob preserved for multi-download'); |
| return undefined; |
| }; |
| window.__VAI_REVOKE_PATCHED = true; |
| console.log('[Auto Inject v2.0] URL.revokeObjectURL patched'); |
| } |
|
|
| |
| 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 genOrderCode(name) { |
| var d = new Date(); |
| var dd = String(d.getDate()).padStart(2, '0'); |
| var mm = String(d.getMonth() + 1).padStart(2, '0'); |
| var yy = String(d.getFullYear()).slice(-2); |
| var ini = ''; |
| if (name && name.trim()) { |
| name.trim().split(/\s+/).forEach(function(w) { |
| w && /[A-Z]/.test(stripDiacritics(w.charAt(0))) && (ini += stripDiacritics(w.charAt(0)).toUpperCase()); |
| }); |
| } |
| return 'VAS' + (ini || 'X') + dd + mm + yy; |
| } |
|
|
| |
| function setBtnState(btn, exporting) { |
| if (!btn) return; |
| if (exporting) { |
| btn.disabled = true; |
| btn.dataset.vaiExporting = '1'; |
| btn._orig = btn.innerHTML; |
| btn.innerHTML = '<span style="opacity:0.7">⏳</span>'; |
| } else { |
| btn.disabled = false; |
| btn.dataset.vaiExporting = '0'; |
| btn._orig && (btn.innerHTML = btn._orig); |
| } |
| } |
|
|
| |
| function downloadBlob(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;top:-9999px'; |
| document.body.appendChild(a); |
| a.click(); |
| setTimeout(function() { try { document.body.removeChild(a); } catch(e) {} }, 100); |
| } |
|
|
| |
| function getQuoteData() { |
| var data = { |
| customer: { name: '', phone: '', email: '', addr: '', date: new Date().toLocaleDateString('vi-VN') }, |
| items: [], |
| grandTotal: 0 |
| }; |
| var cart = window.cart || []; |
| var products = window.D || []; |
| cart.forEach(function(item, i) { |
| var prod = products[item.idx] || (products.find ? products.find(function(p) { return p.id === item.id; }) : null); |
| var price = item.priceNum || item.price || (prod ? prod.price : 0) || 0; |
| data.items.push({ |
| stt: i + 1, |
| image: prod ? prod.img : '', |
| name: prod ? (prod.name || prod.n || '') : (item.name || ''), |
| model: prod ? (prod.sku || prod.mod || '') : (item.sku || item.model || ''), |
| specs: prod ? (prod.info || prod.desc || '') : '', |
| qty: item.qty || 1, |
| price: price, |
| discPrice: price, |
| total: (item.qty || 1) * price, |
| note: '' |
| }); |
| }); |
| |
| var nameInputs = ['#quoteName', '#quoteCustomerName', '#checkoutName']; |
| for (var j = 0; j < nameInputs.length; j++) { |
| var el = document.querySelector(nameInputs[j]); |
| if (el && el.value && el.value.trim()) { data.customer.name = el.value.trim(); break; } |
| } |
| var ph = document.querySelector('#quotePhone, #checkoutPhone'); ph && ph.value && (data.customer.phone = ph.value.trim()); |
| var em = document.querySelector('#quoteEmail'); em && em.value && (data.customer.email = em.value.trim()); |
| var ad = document.querySelector('#quoteAddr, #checkoutAddr'); ad && ad.value && (data.customer.addr = ad.value.trim()); |
| data.grandTotal = data.items.reduce(function(s, i) { return s + (i.total || 0); }, 0); |
| return data; |
| } |
|
|
| |
| async function exportQuoteExcel(quoteData, code) { |
| 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 = quoteData.customer.name; |
| ws.getCell('H5').value = 'Mã đơn:'; ws.mergeCells('I5:J5'); ws.getCell('I5').value = code; |
| ws.getCell('A6').value = 'SĐT:'; ws.mergeCells('B6:E6'); ws.getCell('B6').value = quoteData.customer.phone; |
| ws.getCell('H6').value = 'Ngày:'; ws.mergeCells('I6:J6'); ws.getCell('I6').value = quoteData.customer.date; |
| ws.getCell('A7').value = 'Email:'; ws.mergeCells('B7:E7'); ws.getCell('B7').value = quoteData.customer.email; |
| ws.getCell('A8').value = 'Địa chỉ:'; ws.mergeCells('B8:J8'); ws.getCell('B8').value = quoteData.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, firstRow = cr; |
| quoteData.items.forEach(function(it, i) { |
| var row = ws.getRow(cr); row.height = 50; |
| row.getCell(1).value = it.stt || i+1; |
| row.getCell(3).value = it.name || ''; row.getCell(3).font = { bold:true, size:9 }; |
| row.getCell(4).value = it.model || ''; |
| row.getCell(5).value = it.specs || ''; row.getCell(5).font = { size:8, color:{argb:'FF64748B'} }; |
| row.getCell(6).value = Number(it.qty||1); row.getCell(6).numFmt = '#,##0'; |
| row.getCell(7).value = Number(it.price||0); row.getCell(7).numFmt = '#,##0"đ"'; |
| row.getCell(8).value = Number(it.discPrice||it.price||0); row.getCell(8).numFmt = '#,##0"đ"'; |
| row.getCell(8).font = (it.price||0) > (it.discPrice||0) ? { bold:true, color:{argb:'FFDC3545'} } : {}; |
| row.getCell(9).value = Number(it.total||0); row.getCell(9).numFmt = '#,##0"đ"'; row.getCell(9).font = { bold:true }; |
| row.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'+firstRow+':I'+(cr-1)+')', result:Number(quoteData.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(); |
| downloadBlob(new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), code + '.xlsx'); |
| return true; |
| } catch (e) { |
| alert('❌ Lỗi: ' + e.message); |
| console.error('[Auto Inject] exportQuoteExcel error:', e); |
| return false; |
| } |
| } |
|
|
| async function exportDeliveryExcel(quoteData, code) { |
| 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: ' + (quoteData.customer.name||''); ws.getCell('D2').value = 'Mã: ' + code; |
| ws.getCell('A3').value = 'SĐT: ' + (quoteData.customer.phone||''); ws.getCell('D3').value = 'Ngày: ' + (quoteData.customer.date||''); |
| ws.getCell('A4').value = 'Địa chỉ: ' + (quoteData.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, totalQty = 0; |
| quoteData.items.forEach(function(it, i) { |
| var row = ws.getRow(cr); row.height = 20; |
| row.getCell(1).value = it.stt || i+1; row.getCell(2).value = it.name || ''; row.getCell(3).value = it.model || ''; row.getCell(4).value = it.qty || 1; row.getCell(5).value = it.note || ''; |
| totalQty += 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 = totalQty; |
| var buf = await wb.xlsx.writeBuffer(); |
| downloadBlob(new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}), 'GH-' + code + '.xlsx'); |
| return true; |
| } catch (e) { |
| alert('❌ Lỗi: ' + e.message); |
| console.error('[Auto Inject] exportDeliveryExcel error:', e); |
| return false; |
| } |
| } |
|
|
| |
| function handleClick(e) { |
| var btn = e.target.closest('button, a'); |
| if (!btn) return; |
| var txt = (btn.textContent || '').toLowerCase(); |
| |
| if (txt.includes('excel') && !txt.includes('giao') && !txt.includes('gh-')) { |
| if (btn.dataset.vaiExporting === '1') return; |
| e.preventDefault(); e.stopPropagation(); |
| setBtnState(btn, true); |
| var qd = getQuoteData(); var code = genOrderCode(qd.customer.name); |
| exportQuoteExcel(qd, code).then(function() { |
| setBtnState(btn, false); |
| showToast && showToast('✅ Excel báo giá tải thành công!'); |
| }); |
| return false; |
| } |
| |
| if ((txt.includes('gh') || txt.includes('giao hàng')) && txt.includes('excel')) { |
| if (btn.dataset.vaiExporting === '1') return; |
| e.preventDefault(); e.stopPropagation(); |
| setBtnState(btn, true); |
| var order = window.VAI_CURRENT_ORDER || {}; |
| var ed = { |
| 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 ocode = order.code || genOrderCode(order.customer||''); |
| exportDeliveryExcel(ed, ocode).then(function() { |
| setBtnState(btn, false); |
| showToast && showToast('✅ Excel giao hàng tải thành công!'); |
| }); |
| return false; |
| } |
| } |
|
|
| |
| function init() { |
| if (SCRIPT_INJECTED) return; |
| document.removeEventListener('click', handleClick, true); |
| document.addEventListener('click', handleClick, true); |
| SCRIPT_INJECTED = true; |
| console.log('[Auto Inject v2.0] Excel fix active - multi-download enabled'); |
| } |
|
|
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', init); |
| } else { |
| init(); |
| } |
| setTimeout(init, 2000); |
| setTimeout(init, 5000); |
|
|
| window.VAI_AUTO_FIX = { init: init, exportQuoteExcel: exportQuoteExcel, exportDeliveryExcel: exportDeliveryExcel }; |
| })(); |
|
|
| console.log('[VAI] Auto Excel Fix v2.0 loaded!'); |