bep40 commited on
Commit
5953555
·
verified ·
1 Parent(s): ad6c2c4

Upload vai-excel-ultimate-fix.js

Browse files
Files changed (1) hide show
  1. vai-excel-ultimate-fix.js +417 -0
vai-excel-ultimate-fix.js ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO - Ultimate Excel Multi-Download Fix v1.0 FINAL
3
+ * ========================================================
4
+ * FIX TRIỆT ĐỐI VẤN ĐỀ: File Excel chỉ tải được 1 lần, lần 2+ thất bại
5
+ *
6
+ * NGĂN NHÂN GỐC: URL.revokeObjectURL được gọi sau khi click, làm mất blob URL
7
+ * GIẢI PHÁP: Patch URL.revokeObjectURL + registry chung + cleanup khi unload
8
+ */
9
+ (function() {
10
+ 'use strict';
11
+
12
+ // Registry toàn cục - CHỈ DÙNG 1 registry duy nhất cho tất cả
13
+ window.__VAI_ALL_BLOB_URLS = window.__VAI_ALL_BLOB_URLS || [];
14
+ var __isUnloading__ = false;
15
+
16
+ // Cleanup tất cả blob khi tab đóng
17
+ function cleanupAllBlobs() {
18
+ __isUnloading__ = true;
19
+ window.__VAI_ALL_BLOB_URLS.forEach(function(e) {
20
+ try {
21
+ if (e && e.url && !e.revoked) {
22
+ URL.revokeObjectURL(e.url);
23
+ e.revoked = true;
24
+ }
25
+ } catch(err) {}
26
+ });
27
+ window.__VAI_ALL_BLOB_URLS = [];
28
+ }
29
+
30
+ // Đăng ký cleanup events
31
+ window.addEventListener('pagehide', cleanupAllBlobs);
32
+ window.addEventListener('beforeunload', cleanupAllBlobs);
33
+ window.addEventListener('unload', cleanupAllBlobs);
34
+
35
+ // PATCH URL.revokeObjectURL - QUAN TRỌNG NHẤT
36
+ var __originalRevoke__ = URL.revokeObjectURL;
37
+ URL.revokeObjectURL = function(url) {
38
+ // Đang unload hoặc không phải blob thì revoke bình thường
39
+ if (__isUnloading__ || !url || typeof url !== 'string' || !url.startsWith('blob:')) {
40
+ return __originalRevoke__.call(URL, url);
41
+ }
42
+ // Đăng ký để cleanup sau, KHÔNG revoke ngay
43
+ var exists = window.__VAI_ALL_BLOB_URLS.find(function(e) { return e && e.url === url; });
44
+ if (!exists) {
45
+ window.__VAI_ALL_BLOB_URLS.push({ url: url, revoked: false, created: Date.now() });
46
+ }
47
+ // KHÔNG revoke - để download hoạt động được nhiều lần
48
+ };
49
+
50
+ // Hàm download blob an toàn - dùng chung cho mọi export
51
+ window.__VAI_DOWNLOAD_BLOB__ = function(blob, filename) {
52
+ var url = URL.createObjectURL(blob);
53
+ window.__VAI_ALL_BLOB_URLS.push({ url: url, revoked: false, created: Date.now() });
54
+
55
+ var a = document.createElement('a');
56
+ a.href = url;
57
+ a.download = filename;
58
+ a.style.cssText = 'position:fixed;top:-1000px;left:-1000px;width:1px;height:1px;opacity:0;';
59
+ document.body.appendChild(a);
60
+
61
+ // Click ngay lập tức
62
+ a.click();
63
+
64
+ // Xóa anchor sau 100ms
65
+ setTimeout(function() {
66
+ try { document.body.removeChild(a); } catch(e) {}
67
+ }, 100);
68
+ };
69
+
70
+ // Hàm format tiền
71
+ function fmtMoney(n) {
72
+ if (!n || isNaN(n)) return '0đ';
73
+ return Number(n).toLocaleString('vi-VN') + 'đ';
74
+ }
75
+
76
+ // Hàm tạo mã đơn
77
+ function genOrderCode(name) {
78
+ var now = new Date();
79
+ var dd = String(now.getDate()).padStart(2, '0');
80
+ var mm = String(now.getMonth() + 1).padStart(2, '0');
81
+ var yy = String(now.getFullYear()).slice(-2);
82
+ var ini = '';
83
+ if (name && name.trim()) {
84
+ name.trim().split(/\s+/).forEach(function(w) {
85
+ if (w) {
86
+ var ch = w.charAt(0).normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase();
87
+ if (/[A-Z]/.test(ch)) ini += ch;
88
+ }
89
+ });
90
+ }
91
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
92
+ }
93
+
94
+ // ==========================================
95
+ // FIX: exportDeliveryExcel - Phiếu Giao Hàng (cho cả quotation modal và order modal)
96
+ // ==========================================
97
+ window.exportDeliveryExcel = async function(order, opt) {
98
+ var qd, code;
99
+
100
+ if (order && order.items) {
101
+ // Từ order detail modal
102
+ qd = {
103
+ customer: {
104
+ name: order.customer || '',
105
+ phone: order.phone || '',
106
+ addr: order.addr || '',
107
+ date: order.date || ''
108
+ },
109
+ items: (order.items || []).map(function(it, i) {
110
+ return {
111
+ stt: i + 1,
112
+ name: it.name || '',
113
+ model: it.model || '',
114
+ qty: it.qty || 1,
115
+ note: it.note || ''
116
+ };
117
+ })
118
+ };
119
+ code = order.code || genOrderCode(order.customer);
120
+ } else if (window.cart && window.cart.length > 0) {
121
+ // Từ giỏ hàng (quotation modal)
122
+ qd = {
123
+ customer: {
124
+ name: '',
125
+ phone: '',
126
+ addr: '',
127
+ date: new Date().toLocaleDateString('vi-VN')
128
+ },
129
+ items: window.cart.map(function(item, idx) {
130
+ var p = (window.D && window.D[item.idx]) ? window.D[item.idx] : null;
131
+ return {
132
+ stt: idx + 1,
133
+ name: p ? (p.name || p.n || '') : (item.name || ''),
134
+ model: p ? (p.sku || p.mod || '') : (item.sku || item.model || ''),
135
+ qty: item.qty || 1,
136
+ note: item.note || ''
137
+ };
138
+ })
139
+ };
140
+ code = genOrderCode('');
141
+ } else {
142
+ if (typeof showToast === 'function') showToast('⚠️ Giỏ hàng trống!');
143
+ return;
144
+ }
145
+
146
+ try {
147
+ if (typeof ExcelJS === 'undefined') {
148
+ if (typeof showToast === 'function') showToast('⚠️ Chưa có thư viện ExcelJS');
149
+ return;
150
+ }
151
+
152
+ var wb = new ExcelJS.Workbook();
153
+ var ws = wb.addWorksheet('Phiếu Giao Hàng');
154
+
155
+ ws.views = [{ showGridLines: false }];
156
+ ws.columns = [{ width: 5 }, { width: 34 }, { width: 14 }, { width: 8 }, { width: 20 }];
157
+
158
+ // Header
159
+ ws.mergeCells('A1:E1');
160
+ ws.getCell('A1').value = 'PHIẾU GIAO HÀNG V.AI STUDIO';
161
+ ws.getCell('A1').font = { bold: true, size: 14, color: { argb: 'FF003F62' } };
162
+ ws.getCell('A1').alignment = { horizontal: 'center' };
163
+ ws.getCell('A1').fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF0F7FF' } };
164
+
165
+ // Thông tin khách hàng
166
+ ws.getCell('A2').value = 'Ngày giao:';
167
+ ws.getCell('B2').value = new Date().toLocaleDateString('vi-VN');
168
+ ws.getCell('A3').value = 'KH: ' + (qd.customer.name || '');
169
+ ws.getCell('D3').value = 'Mã: ' + code;
170
+ ws.getCell('A4').value = 'SĐT: ' + (qd.customer.phone || '');
171
+ ws.getCell('A5').value = 'Địa chỉ: ' + (qd.customer.addr || '');
172
+
173
+ // Header row
174
+ var hr = ws.getRow(7);
175
+ ['STT', 'Tên sản phẩm', 'Mã SP', 'SL', 'Ghi chú'].forEach(function(h, i) {
176
+ var c = hr.getCell(i + 1);
177
+ c.value = h;
178
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
179
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
180
+ });
181
+ hr.height = 20;
182
+
183
+ // Dữ liệu sản phẩm
184
+ var cr = 8, totalQty = 0;
185
+ (qd.items || []).forEach(function(it, i) {
186
+ var row = ws.getRow(cr);
187
+ row.values = [i + 1, it.name || '', it.model || '', it.qty || 1, it.note || ''];
188
+ totalQty += (it.qty || 1);
189
+ cr++;
190
+ });
191
+
192
+ // Tổng cộng
193
+ var tr = ws.getRow(cr);
194
+ ws.mergeCells(cr, 1, cr, 3);
195
+ ws.getCell('A' + cr).value = 'TỔNG CỘNG SỐ LƯỢNG';
196
+ ws.getCell('A' + cr).font = { bold: true, color: { argb: 'FF1E40AF' } };
197
+ ws.getCell('D' + cr).value = totalQty;
198
+
199
+ // Tạo blob và download
200
+ var buf = await wb.xlsx.writeBuffer();
201
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
202
+
203
+ window.__VAI_DOWNLOAD_BLOB__(blob, 'GH-' + code + '.xlsx');
204
+
205
+ if (typeof showToast === 'function') {
206
+ showToast('✅ Đã tải GH-' + code + '.xlsx');
207
+ }
208
+ } catch (e) {
209
+ console.error('exportDeliveryExcel error:', e);
210
+ if (typeof showToast === 'function') showToast('❌ Lỗi: ' + e.message);
211
+ }
212
+ };
213
+
214
+ // ==========================================
215
+ // FIX: exportExcel - Bảng Báo Giá
216
+ // ==========================================
217
+ window.exportExcel = async function(d, qd, code, qrUrl, bw) {
218
+ // Chuẩn hoá tham số
219
+ if (typeof qd === 'undefined' && d && d.qd) {
220
+ var tmp = d;
221
+ d = tmp;
222
+ qd = tmp.qd;
223
+ code = tmp.code || code;
224
+ }
225
+
226
+ try {
227
+ if (typeof ExcelJS === 'undefined') {
228
+ if (typeof showToast === 'function') showToast('⚠️ Chưa có thư viện ExcelJS');
229
+ return;
230
+ }
231
+
232
+ var MONEY_FMT = '#,##0"đ"';
233
+ var wb = new ExcelJS.Workbook();
234
+ var ws = wb.addWorksheet('Báo giá');
235
+
236
+ ws.views = [{ showGridLines: false }];
237
+ ws.columns = [
238
+ { width: 5 }, { width: 11 }, { width: 28 }, { width: 13 },
239
+ { width: 20 }, { width: 6 }, { width: 13 }, { width: 13 },
240
+ { width: 15 }, { width: 14 }
241
+ ];
242
+
243
+ // Header
244
+ ws.mergeCells('A3:J3');
245
+ ws.getCell('A3').value = 'BẢNG BÁO GIÁ';
246
+ ws.getCell('A3').font = { bold: true, size: 18, color: { argb: 'FFDB9815' } };
247
+ ws.getCell('A3').alignment = { horizontal: 'center' };
248
+
249
+ // Thông tin khách hàng
250
+ if (qd && qd.customer) {
251
+ ws.getCell('A5').value = 'Khách hàng:';
252
+ ws.mergeCells('B5:E5');
253
+ ws.getCell('B5').value = qd.customer.name || '';
254
+ ws.getCell('H5').value = 'Mã đơn:';
255
+ ws.mergeCells('I5:J5');
256
+ ws.getCell('I5').value = code || '';
257
+ ws.getCell('A6').value = 'SĐT:';
258
+ ws.mergeCells('B6:E6');
259
+ ws.getCell('B6').value = qd.customer.phone || '';
260
+ }
261
+
262
+ // Header row
263
+ var hr = ws.getRow(10);
264
+ ['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) {
265
+ var c = hr.getCell(i + 1);
266
+ c.value = h;
267
+ c.font = { bold: true, color: { argb: 'FFFFFFFF' } };
268
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
269
+ });
270
+
271
+ // Dữ liệu sản phẩm
272
+ var cr = 11, firstRow = cr;
273
+ (qd.items || []).forEach(function(it, i) {
274
+ var row = ws.getRow(cr);
275
+ row.getCell(1).value = it.stt || i + 1;
276
+ row.getCell(3).value = it.name || '';
277
+ row.getCell(3).font = { bold: true };
278
+ row.getCell(4).value = it.model || '';
279
+ row.getCell(5).value = it.specs || '';
280
+ row.getCell(6).value = Number(it.qty || 1);
281
+ row.getCell(6).numFmt = '#,##0';
282
+ row.getCell(7).value = Number(it.price || 0);
283
+ row.getCell(7).numFmt = MONEY_FMT;
284
+ row.getCell(8).value = Number(it.discPrice || it.price || 0);
285
+ row.getCell(8).numFmt = MONEY_FMT;
286
+ row.getCell(9).value = Number(it.total || 0);
287
+ row.getCell(9).numFmt = MONEY_FMT;
288
+ row.getCell(10).value = it.note || '';
289
+ cr++;
290
+ });
291
+
292
+ // Tổng cuối
293
+ ws.mergeCells(cr, 1, cr, 5);
294
+ ws.getCell('A' + cr).value = 'TỔNG CỘNG';
295
+ ws.getCell('A' + cr).font = { bold: true, color: { argb: 'FFFFFFFF' } };
296
+ ws.getCell('A' + cr).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF003F62' } };
297
+ ws.getCell('E' + cr).value = Number((d && d.grandTotal) || 0);
298
+ ws.getCell('E' + cr).numFmt = MONEY_FMT;
299
+
300
+ // Xuất
301
+ var buf = await wb.xlsx.writeBuffer();
302
+ var blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
303
+
304
+ window.__VAI_DOWNLOAD_BLOB__(blob, (code || 'quote') + '.xlsx');
305
+
306
+ if (typeof showToast === 'function') {
307
+ showToast('✅ Đã tải ' + (code || 'quote') + '.xlsx');
308
+ }
309
+ } catch (e) {
310
+ console.error('exportExcel error:', e);
311
+ if (typeof showToast === 'function') showToast('❌ Lỗi: ' + e.message);
312
+ }
313
+ };
314
+
315
+ // ==========================================
316
+ // FIX: exportDeliveryPDF - Phiếu Giao Hàng PDF
317
+ // ==========================================
318
+ window.exportDeliveryPDF = async function(order, opt) {
319
+ var qd, code;
320
+
321
+ if (order && order.items) {
322
+ qd = {
323
+ customer: {
324
+ name: order.customer || '',
325
+ phone: order.phone || '',
326
+ addr: order.addr || '',
327
+ date: order.date || ''
328
+ },
329
+ items: (order.items || []).map(function(it, i) {
330
+ return {
331
+ stt: i + 1,
332
+ name: it.name || '',
333
+ model: it.model || '',
334
+ qty: it.qty || 1,
335
+ note: it.note || ''
336
+ };
337
+ })
338
+ };
339
+ code = order.code || genOrderCode(order.customer);
340
+ } else {
341
+ qd = window._origGetQuoteData ? window._origGetQuoteData() : {
342
+ customer: { name: '', phone: '', date: new Date().toLocaleDateString('vi-VN') },
343
+ items: window.cart || []
344
+ };
345
+ code = genOrderCode('');
346
+ }
347
+
348
+ try {
349
+ if (typeof html2canvas === 'undefined' || typeof jspdf === 'undefined') {
350
+ if (typeof showToast === 'function') showToast('⚠️ Chưa có thư viện PDF');
351
+ return;
352
+ }
353
+
354
+ var html = '<div style="padding:20px;font-family:Inter,Arial,sans-serif">'
355
+ + '<div style="text-align:center;margin-bottom:16px">'
356
+ + '<div style="font-size:22px;font-weight:900;color:#003f62">PHIẾU GIAO HÀNG</div>'
357
+ + '</div>'
358
+ + '<div style="margin-bottom:12px;font-size:12px">'
359
+ + '<b>Khách hàng:</b> ' + (qd.customer.name || '') + '<br>'
360
+ + '<b>SĐT:</b> ' + (qd.customer.phone || '') + '<br>'
361
+ + '<b>Mã đơn:</b> ' + code + '<br>'
362
+ + '</div>'
363
+ + '<table style="width:100%;border-collapse:collapse;font-size:12px">'
364
+ + '<thead><tr style="background:#003f62;color:#fff">'
365
+ + '<th style="padding:8px;width:35px">STT</th>'
366
+ + '<th style="padding:8px;text-align:left">Tên sản phẩm</th>'
367
+ + '<th style="padding:8px;width:90px">Mã SP</th>'
368
+ + '<th style="padding:8px;width:40px">SL</th>'
369
+ + '<th style="padding:8px;width:120px;text-align:left">Ghi chú</th>'
370
+ + '</tr></thead><tbody>';
371
+
372
+ (qd.items || []).forEach(function(it, i) {
373
+ html += '<tr style="border-bottom:1px solid #e2e8f0">'
374
+ + '<td style="padding:8px;text-align:center">' + (i + 1) + '</td>'
375
+ + '<td style="padding:8px;font-weight:600">' + (it.name || '') + '</td>'
376
+ + '<td style="padding:8px;text-align:center">' + (it.model || '') + '</td>'
377
+ + '<td style="padding:8px;text-align:center;font-weight:700">' + (it.qty || 1) + '</td>'
378
+ + '<td style="padding:8px">' + (it.note || '') + '</td></tr>';
379
+ });
380
+
381
+ html += '</tbody></table>'
382
+ + '<div style="margin-top:50px;display:flex;justify-content:space-around;font-size:12px">'
383
+ + '<div style="text-align:center"><b>Bên giao</b><br>(Ký)<div style="margin-top:25px">________</div></div>'
384
+ + '<div style="text-align:center"><b>Bên nhận</b><br>(Ký)<div style="margin-top:25px">________</div></div>'
385
+ + '</div></div>';
386
+
387
+ var div = document.createElement('div');
388
+ div.style.cssText = 'position:absolute;left:-10000px;top:0;width:700px;background:#fff;padding:28px;box-sizing:border-box';
389
+ div.innerHTML = html;
390
+ document.body.appendChild(div);
391
+
392
+ var cv = await html2canvas(div, { scale: 2, useCORS: true, backgroundColor: '#fff' });
393
+ div.remove();
394
+
395
+ var img = cv.toDataURL('image/png');
396
+ var pdf = new jspdf.jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
397
+ var margin = 25, usW = 595 - margin * 2, ratio = cv.height / cv.width, imgH = usW * ratio;
398
+
399
+ if (imgH > 842 - margin * 2) imgH = 842 - margin * 2;
400
+
401
+ pdf.addImage(img, 'PNG', margin, margin, usW, imgH);
402
+ pdf.save('GH-' + code + '.pdf');
403
+
404
+ if (typeof showToast === 'function') showToast('✅ Đã tải GH-' + code + '.pdf');
405
+ } catch (e) {
406
+ console.error('exportDeliveryPDF error:', e);
407
+ if (typeof showToast === 'function') showToast('❌ Lỗi: ' + e.message);
408
+ }
409
+ };
410
+
411
+ // Override các hàm cũ nếu tồn tại
412
+ if (window.VAI_QR && window.VAI_QR.exportDeliveryExcel) {
413
+ window.VAI_QR.exportDeliveryExcel = window.exportDeliveryExcel;
414
+ }
415
+
416
+ console.log('[VAI Ultimate Excel Fix v1.0] Multi-download enabled - blob cleanup on tab unload');
417
+ })();