bep40 commited on
Commit
2d02553
·
verified ·
1 Parent(s): 8cfe5f2

Khôi phục vai-ultimate-fix-v1100.js về commit a75919ca

Browse files
Files changed (1) hide show
  1. vai-ultimate-fix-v1100.js +688 -0
vai-ultimate-fix-v1100.js ADDED
@@ -0,0 +1,688 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * V.AI STUDIO — ULTIMATE FIX v1100 (FINAL)
3
+ *
4
+ * ===== VẤN ĐỀ =====
5
+ * 1. _doExportExcel trong qr-payment.js tạo blob URL và revoke sau 60s → không tải lại
6
+ * 2. 3 file fix cũ (v1042, robust-v2, multi-download) ghi đè lẫn nhau → conflict
7
+ * 3. Nut Excell/PDF trên modal không bind đúng hoặc bị ghi đè nhiều lần
8
+ * 4. Window._vaiExportCache bị các file fix cũ ghi đè sai → không tìm thấy file đã xuất
9
+ *
10
+ * ===== FIX TRIỆT ĐỂ =====
11
+ * 1. Intercept URL.createObjectURL + URL.revokeObjectURL — GIỮ blob VĨNH VIỄN
12
+ * 2. Lưu tất cả blob export vào _vaiFinalExportStore (dùng array, ko dùng object dễ bị ghi đè)
13
+ * 3. Thêm UI "Tải lại file đã xuất" vào cả modal báo giá và modal chi tiết đơn hàng
14
+ * 4. Bind trực tiếp các nút export (ko qua trung gian)
15
+ * 5. Xuất lại file mới nếu blob cũ không còn (tự động fallback)
16
+ */
17
+ (function() {
18
+ 'use strict';
19
+ console.log('[VAI ULTIMATE FIX v1100] === LOADING ===');
20
+
21
+ // =============================================
22
+ // 1. GLOBAL EXPORT STORE (ARRAY — không bị ghi đè)
23
+ // =============================================
24
+ window._vaiFinalExportStore = window._vaiFinalExportStore || [];
25
+ window._vaiFinalExportStore.all = function() { return this; };
26
+ window._vaiFinalExportStore.add = function(entry) { this.push(entry); };
27
+ window._vaiFinalExportStore.getByCode = function(code) {
28
+ return this.filter(function(e) { return e.code === code; });
29
+ };
30
+ window._vaiFinalExportStore.clear = function() { this.length = 0; };
31
+
32
+ // =============================================
33
+ // 2. INTERCEPT URL.createObjectURL + revokeObjectURL
34
+ // =============================================
35
+ if (!window._vaiFinalBlobIntercept) {
36
+ window._vaiFinalBlobIntercept = true;
37
+
38
+ var _origCreate = URL.createObjectURL;
39
+ var _origRevoke = URL.revokeObjectURL;
40
+ var _blobMap = {}; // url -> blob
41
+
42
+ URL.createObjectURL = function(blob) {
43
+ var url = _origCreate.call(URL, blob);
44
+ if (blob instanceof Blob) {
45
+ _blobMap[url] = blob;
46
+ // Auto-register if it looks like an export (xlsx, xls, pdf)
47
+ var isExport = false;
48
+ if (blob.type) {
49
+ if (blob.type.indexOf('spreadsheet') >= 0 ||
50
+ blob.type.indexOf('excel') >= 0 ||
51
+ blob.type.indexOf('openxml') >= 0 ||
52
+ blob.type.indexOf('pdf') >= 0) {
53
+ isExport = true;
54
+ }
55
+ }
56
+ if (!isExport && blob.size > 512) {
57
+ isExport = true; // conservative: any non-tiny blob
58
+ }
59
+ console.log('[VAI ULTIMATE] 📦 Blob stored [' + (isExport ? 'EXPORT' : 'other') + '] size=' +
60
+ (blob.size/1024).toFixed(1) + 'KB type=' + blob.type);
61
+ }
62
+ return url;
63
+ };
64
+
65
+ URL.revokeObjectURL = function(url) {
66
+ if (url && url.startsWith('blob:') && _blobMap[url]) {
67
+ // NEVER revoke — keep forever
68
+ return undefined;
69
+ }
70
+ // For non-cached blobs, still block to be safe
71
+ if (url && url.startsWith('blob:')) {
72
+ return undefined;
73
+ }
74
+ return _origRevoke.call(URL, url);
75
+ };
76
+
77
+ // Cleanup only on page unload
78
+ window.addEventListener('pagehide', function() {
79
+ var keys = Object.keys(_blobMap);
80
+ keys.forEach(function(url) {
81
+ try { _origRevoke.call(URL, url); } catch(e) {}
82
+ });
83
+ _blobMap = {};
84
+ });
85
+
86
+ window._vaiFinalBlobMap = _blobMap;
87
+ window._vaiFinalOrigRevoke = _origRevoke;
88
+
89
+ console.log('[VAI ULTIMATE] ✅ URL.createObjectURL/revokeObjectURL intercepted');
90
+ }
91
+
92
+ // =============================================
93
+ // 3. REGISTER EXPORT — lưu vào store
94
+ // =============================================
95
+ function registerExport(fileName, code, type, url) {
96
+ if (!code) code = 'BAOGIA';
97
+ if (!type) type = 'Excel';
98
+
99
+ // Remove old entry for same code+type (if exists)
100
+ var store = window._vaiFinalExportStore;
101
+ for (var i = 0; i < store.length; i++) {
102
+ if (store[i].code === code && store[i].type === type) {
103
+ store.splice(i, 1);
104
+ break;
105
+ }
106
+ }
107
+
108
+ store.add({
109
+ code: code,
110
+ type: type,
111
+ fileName: fileName,
112
+ url: url,
113
+ date: new Date().toISOString()
114
+ });
115
+
116
+ console.log('[VAI ULTIMATE] ✅ Export registered: [' + type + '] ' + code + ' → ' + fileName);
117
+
118
+ // Also set on window._vaiExportCache for backward compat
119
+ if (!window._vaiExportCache) window._vaiExportCache = {};
120
+ if (!window._vaiExportCache[code]) window._vaiExportCache[code] = {};
121
+ window._vaiExportCache[code][type] = { url: url, fileName: fileName, date: new Date().toISOString() };
122
+
123
+ // Inject re-download UI
124
+ setTimeout(injectRedownloadUI, 100);
125
+ }
126
+
127
+ // =============================================
128
+ // 4. WRAP EXPORT FUNCTIONS
129
+ // =============================================
130
+ var _wrapAttempts = 0;
131
+ var _wrapTimer = setInterval(function() {
132
+ _wrapAttempts++;
133
+
134
+ // Wrap _doExportExcel
135
+ if (typeof _doExportExcel === 'function' && !window._vaiUltimateWrappedExcel) {
136
+ window._vaiUltimateWrappedExcel = true;
137
+ var origExcel = _doExportExcel;
138
+ _doExportExcel = async function(d, qd, code, qrUrl, bw) {
139
+ try {
140
+ await origExcel(d, qd, code, qrUrl, bw);
141
+ // Find the blob URL that was just created
142
+ setTimeout(function() {
143
+ var url = findLatestBlobURL(code);
144
+ if (url) {
145
+ registerExport(code + '.xlsx', code, 'Excel', url);
146
+ }
147
+ }, 100);
148
+ } catch(e) {
149
+ console.error('[VAI ULTIMATE] Excel error:', e);
150
+ // Fallback: simple HTML table export
151
+ try {
152
+ await fallbackHTMLTableExport(qd, code, 'Excel');
153
+ } catch(e2) {
154
+ console.error('[VAI ULTIMATE] Fallback failed:', e2);
155
+ }
156
+ }
157
+ };
158
+ console.log('[VAI ULTIMATE] ✅ Wrapped _doExportExcel');
159
+ }
160
+
161
+ // Wrap _doExportDeliveryExcel
162
+ if (typeof _doExportDeliveryExcel === 'function' && !window._vaiUltimateWrappedDelivery) {
163
+ window._vaiUltimateWrappedDelivery = true;
164
+ var origDel = _doExportDeliveryExcel;
165
+ _doExportDeliveryExcel = async function(qd, code) {
166
+ try {
167
+ await origDel(qd, code);
168
+ setTimeout(function() {
169
+ var url = findLatestBlobURL(code);
170
+ if (url) {
171
+ registerExport('GH-' + code + '.xlsx', code, 'GiaoHang', url);
172
+ }
173
+ }, 100);
174
+ } catch(e) {
175
+ console.error('[VAI ULTIMATE] Delivery Excel error:', e);
176
+ }
177
+ };
178
+ console.log('[VAI ULTIMATE] ✅ Wrapped _doExportDeliveryExcel');
179
+ }
180
+
181
+ // Wrap _doExportPDF
182
+ if (typeof _doExportPDF === 'function' && !window._vaiUltimateWrappedPDF) {
183
+ window._vaiUltimateWrappedPDF = true;
184
+ var origPDF = _doExportPDF;
185
+ _doExportPDF = async function(d, code) {
186
+ try {
187
+ await origPDF(d, code);
188
+ setTimeout(function() {
189
+ var url = findLatestBlobURL(code);
190
+ if (url) {
191
+ registerExport(code + '.pdf', code, 'PDF', url);
192
+ }
193
+ }, 100);
194
+ } catch(e) {
195
+ console.error('[VAI ULTIMATE] PDF error:', e);
196
+ }
197
+ };
198
+ console.log('[VAI ULTIMATE] ✅ Wrapped _doExportPDF');
199
+ }
200
+
201
+ // Wrap _doExportDeliveryPDF
202
+ if (typeof _doExportDeliveryPDF === 'function' && !window._vaiUltimateWrappedDelPDF) {
203
+ window._vaiUltimateWrappedDelPDF = true;
204
+ var origDelPDF = _doExportDeliveryPDF;
205
+ _doExportDeliveryPDF = async function(qd, code) {
206
+ try {
207
+ await origDelPDF(qd, code);
208
+ setTimeout(function() {
209
+ var url = findLatestBlobURL(code);
210
+ if (url) {
211
+ registerExport('GH-' + code + '.pdf', code, 'GH-PDF', url);
212
+ }
213
+ }, 100);
214
+ } catch(e) {
215
+ console.error('[VAI ULTIMATE] Delivery PDF error:', e);
216
+ }
217
+ };
218
+ console.log('[VAI ULTIMATE] ✅ Wrapped _doExportDeliveryPDF');
219
+ }
220
+
221
+ // Stop after 30 attempts (15s)
222
+ if (_wrapAttempts >= 30) {
223
+ clearInterval(_wrapTimer);
224
+ console.log('[VAI ULTIMATE] Wrap attempts finished (' + _wrapAttempts + ')');
225
+ }
226
+ }, 500);
227
+
228
+ // =============================================
229
+ // 5. FIND LATEST BLOB URL
230
+ // =============================================
231
+ function findLatestBlobURL(code) {
232
+ var map = window._vaiFinalBlobMap || {};
233
+ var urls = Object.keys(map);
234
+ // Look for the most recent blob matching export characteristics
235
+ for (var i = urls.length - 1; i >= 0; i--) {
236
+ var url = urls[i];
237
+ var blob = map[url];
238
+ if (blob && blob.type) {
239
+ var t = blob.type;
240
+ if (t.indexOf('spreadsheet') >= 0 || t.indexOf('excel') >= 0 ||
241
+ t.indexOf('openxml') >= 0 || t.indexOf('pdf') >= 0) {
242
+ // Check if this blob is already registered
243
+ var already = false;
244
+ var store = window._vaiFinalExportStore;
245
+ for (var j = 0; j < store.length; j++) {
246
+ if (store[j].url === url) { already = true; break; }
247
+ }
248
+ if (!already) return url;
249
+ }
250
+ }
251
+ }
252
+ // Fallback: return any unregistered blob
253
+ var store = window._vaiFinalExportStore;
254
+ for (var k = urls.length - 1; k >= 0; k--) {
255
+ var u = urls[k];
256
+ var found = false;
257
+ for (var l = 0; l < store.length; l++) {
258
+ if (store[l].url === u) { found = true; break; }
259
+ }
260
+ if (!found) return u;
261
+ }
262
+ return null;
263
+ }
264
+
265
+ // =============================================
266
+ // 6. FALLBACK EXPORT (HTML table → xls)
267
+ // =============================================
268
+ async function fallbackHTMLTableExport(qd, code, type) {
269
+ var html = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel"><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]--></head><body><table>';
270
+ html += '<tr><th colspan="10" style="font-size:18px;color:#db9815;text-align:center;background:#003f62;color:#fff">BẢNG BÁO GIÁ</th></tr>';
271
+ html += '<tr><td colspan="2"><b>KH:</b> ' + (qd.customer ? qd.customer.name : '') + '</td><td colspan="3"><b>Mã:</b> ' + code + '</td></tr>';
272
+ html += '<tr style="background:#003f62;color:#fff"><th>STT</th><th>Tên SP</th><th>Mã</th><th>SL</th><th>Đơn giá</th><th>Giá CK</th><th>Thành tiền</th></tr>';
273
+ (qd.items || []).forEach(function(it, i) {
274
+ html += '<tr><td>' + (it.stt || (i + 1)) + '</td><td>' + (it.name || '') + '</td><td>' + (it.model || '') + '</td><td>' + (it.qty || 1) + '</td><td>' + Number(it.price || 0).toLocaleString('vi-VN') + '</td><td>' + Number(it.discPrice || it.price || 0).toLocaleString('vi-VN') + '</td><td>' + Number((it.discPrice || it.price || 0) * (it.qty || 1)).toLocaleString('vi-VN') + '</td></tr>';
275
+ });
276
+ var total = (qd.items || []).reduce(function(s, it) { return s + Number((it.discPrice || it.price || 0) * (it.qty || 1)); }, 0);
277
+ html += '<tr style="font-weight:bold;background:#003f62;color:#fff"><td colspan="6" style="text-align:right">TỔNG CỘNG</td><td style="color:#f0b840">' + total.toLocaleString('vi-VN') + 'đ</td></tr>';
278
+ html += '</table></body></html>';
279
+
280
+ var blob = new Blob([html], { type: 'application/vnd.ms-excel' });
281
+ var url = URL.createObjectURL(blob);
282
+ registerExport(code + '.xls', code, 'Excel', url);
283
+
284
+ downloadFromURL(url, code + '.xls');
285
+ console.log('[VAI ULTIMATE] Fallback export done: ' + code);
286
+ }
287
+
288
+ // =============================================
289
+ // 7. DOWNLOAD HELPER
290
+ // =============================================
291
+ function downloadFromURL(url, fileName) {
292
+ var a = document.createElement('a');
293
+ a.href = url;
294
+ a.download = fileName;
295
+ a.style.display = 'none';
296
+ a.target = '_blank';
297
+ document.body.appendChild(a);
298
+ a.click();
299
+ setTimeout(function() {
300
+ try { document.body.removeChild(a); } catch(e) {}
301
+ }, 500);
302
+ }
303
+
304
+ // =============================================
305
+ // 8. INJECT RE-DOWNLOAD UI
306
+ // =============================================
307
+ function injectRedownloadUI() {
308
+ // Find the currently open modal
309
+ var modal = findOpenModal();
310
+ if (!modal) return;
311
+
312
+ // Get the order code from the modal
313
+ var code = extractCodeFromModal(modal);
314
+ if (!code) return;
315
+
316
+ // Check if we have exports for this code
317
+ var exports = window._vaiFinalExportStore.getByCode(code);
318
+ if (!exports.length) {
319
+ // Also check _vaiExportCache (backward compat)
320
+ if (window._vaiExportCache && window._vaiExportCache[code]) {
321
+ var cache = window._vaiExportCache[code];
322
+ Object.keys(cache).forEach(function(type) {
323
+ if (cache[type] && cache[type].url) {
324
+ exports.push({
325
+ code: code,
326
+ type: type,
327
+ fileName: cache[type].fileName || (code + '.' + (type === 'PDF' ? 'pdf' : 'xlsx')),
328
+ url: cache[type].url,
329
+ date: cache[type].date || ''
330
+ });
331
+ }
332
+ });
333
+ }
334
+ }
335
+
336
+ if (!exports.length) return;
337
+
338
+ // Remove old section
339
+ var oldSec = modal.querySelector('.vai-ultimate-redownload');
340
+ if (oldSec) oldSec.remove();
341
+
342
+ // Build section
343
+ var sec = document.createElement('div');
344
+ sec.className = 'vai-ultimate-redownload';
345
+ sec.style.cssText = 'margin:8px 14px;padding:8px 12px;background:#f0fdf4;border-radius:8px;border:2px solid #86efac;font-family:Inter,system-ui,sans-serif';
346
+
347
+ var labels = {
348
+ 'Excel': '📊 Báo giá Excel',
349
+ 'PDF': '📄 Báo giá PDF',
350
+ 'GiaoHang': '📦 Phiếu giao hàng',
351
+ 'GH-PDF': '📦 Phiếu giao hàng PDF'
352
+ };
353
+
354
+ var html = '<div style="font-size:10px;font-weight:700;color:#166534;margin-bottom:5px">📥 <b>Tải lại file đã xuất</b> (không giới hạn số lần):</div><div style="display:flex;flex-wrap:wrap;gap:4px">';
355
+
356
+ exports.forEach(function(exp) {
357
+ var label = labels[exp.type] || ('📎 ' + exp.type);
358
+ html += '<button class="vai-ultimate-redl-btn" data-code="' + code + '" data-type="' + exp.type + '" data-url="' + (exp.url || '') + '" data-fn="' + (exp.fileName || code + '.xlsx') + '" style="padding:5px 10px;background:#16a34a;color:#fff;border:none;border-radius:5px;cursor:pointer;font-size:10px;font-weight:700;display:inline-flex;align-items:center;gap:3px">⬇ ' + label + '</button>';
359
+ });
360
+
361
+ html += '</div>';
362
+ sec.innerHTML = html;
363
+
364
+ // Find insertion point — after quote-actions or in the modal body
365
+ var actions = modal.querySelector('.quote-actions, .modal-footer, [class*="footer"], [class*="action"]');
366
+ if (actions && actions.parentNode) {
367
+ actions.parentNode.insertBefore(sec, actions.nextSibling);
368
+ } else {
369
+ var body = modal.querySelector('.quote-body, .modal-body, .modal-content') || modal;
370
+ body.appendChild(sec);
371
+ }
372
+
373
+ // Bind click events
374
+ sec.querySelectorAll('.vai-ultimate-redl-btn').forEach(function(btn) {
375
+ btn.onclick = function(e) {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+
379
+ var url = this.dataset.url;
380
+ var fn = this.dataset.fn;
381
+ var c = this.dataset.code;
382
+ var t = this.dataset.type;
383
+
384
+ // Try to download from cached URL
385
+ if (url && url.startsWith('blob:')) {
386
+ downloadFromURL(url, fn);
387
+ // Visual feedback
388
+ var origText = this.innerHTML;
389
+ this.innerHTML = '✅ Đã tải!';
390
+ this.style.background = '#15803d';
391
+ setTimeout(function() {
392
+ if (btn) {
393
+ var labels = {
394
+ 'Excel': '📊 Báo giá Excel',
395
+ 'PDF': '📄 Báo giá PDF',
396
+ 'GiaoHang': '📦 Phiếu giao hàng',
397
+ 'GH-PDF': '📦 Phiếu giao hàng PDF'
398
+ };
399
+ btn.innerHTML = '⬇ ' + (labels[t] || '📎 ' + t);
400
+ btn.style.background = '#16a34a';
401
+ }
402
+ }, 1500);
403
+ return false;
404
+ }
405
+
406
+ // If URL is gone, try to re-export
407
+ alert('⚠️ File đã xuất không còn trong bộ nhớ. Đang xuất lại...');
408
+ if (window.VAI_QR) {
409
+ if (t === 'Excel' && typeof window.VAI_QR.exportExcel === 'function') {
410
+ window.VAI_QR.exportExcel();
411
+ } else if (t === 'PDF' && typeof window.VAI_QR.exportPDF === 'function') {
412
+ window.VAI_QR.exportPDF();
413
+ } else if (t === 'GiaoHang' && typeof window.VAI_QR.exportDeliveryExcel === 'function') {
414
+ window.VAI_QR.exportDeliveryExcel();
415
+ }
416
+ } else {
417
+ // Try via window functions
418
+ if (t === 'Excel' && typeof window.exportExcel === 'function') {
419
+ window.exportExcel();
420
+ } else if (t === 'PDF' && typeof window.exportPDF === 'function') {
421
+ window.exportPDF();
422
+ } else if (t === 'GiaoHang' && typeof window.exportDeliveryExcel === 'function') {
423
+ window.exportDeliveryExcel();
424
+ }
425
+ }
426
+ return false;
427
+ };
428
+ });
429
+ }
430
+
431
+ // =============================================
432
+ // 9. FIND OPEN MODAL
433
+ // =============================================
434
+ function findOpenModal() {
435
+ // 1. Quote modal
436
+ var quoteModal = document.querySelector('.quote-overlay.open .quote-modal, .quote-overlay[style*="flex"] .quote-modal');
437
+ if (quoteModal) return quoteModal;
438
+
439
+ // 2. Order detail modal
440
+ var orderModal = document.getElementById('vai-order-detail-modal');
441
+ if (orderModal && orderModal.offsetParent !== null) {
442
+ var modalContent = orderModal.querySelector('div[style*="max-width"]');
443
+ if (modalContent) return modalContent;
444
+ return orderModal;
445
+ }
446
+
447
+ // 3. Any visible modal
448
+ var modals = document.querySelectorAll('[class*="modal"][style*="block"], [class*="modal"][style*="flex"]');
449
+ for (var i = 0; i < modals.length; i++) {
450
+ if (modals[i].offsetWidth > 0 || modals[i].offsetHeight > 0) {
451
+ return modals[i];
452
+ }
453
+ }
454
+
455
+ return null;
456
+ }
457
+
458
+ // =============================================
459
+ // 10. EXTRACT CODE FROM MODAL
460
+ // =============================================
461
+ function extractCodeFromModal(modal) {
462
+ if (!modal) return null;
463
+ var text = modal.textContent || '';
464
+
465
+ // Try VAS code pattern
466
+ var m = text.match(/VAS[A-Z0-9]{5,}/);
467
+ if (m) return m[0];
468
+
469
+ // Try DH pattern
470
+ m = text.match(/DH\d{3,}/);
471
+ if (m) return m[0];
472
+
473
+ // Try BAOGIA
474
+ if (text.indexOf('BÁO GIÁ') >= 0 || text.indexOf('Bao gia') >= 0) {
475
+ return 'BAOGIA';
476
+ }
477
+
478
+ // Try to find customer name + generate code
479
+ var inputs = modal.querySelectorAll('input');
480
+ for (var j = 0; j < inputs.length; j++) {
481
+ var ph = (inputs[j].placeholder || '').toLowerCase();
482
+ var val = (inputs[j].value || '').trim();
483
+ if ((ph.indexOf('khách') >= 0 || ph.indexOf('khach') >= 0 || ph.indexOf('tên') >= 0) && val.length > 1) {
484
+ // Generate code from name
485
+ var now = new Date();
486
+ var dd = String(now.getDate()).padStart(2, '0');
487
+ var mm = String(now.getMonth() + 1).padStart(2, '0');
488
+ var yy = String(now.getFullYear()).slice(-2);
489
+ var name = val.trim();
490
+ var ini = '';
491
+ name.split(/\s+/).forEach(function(w) {
492
+ if (w) {
493
+ var ch = w.charAt(0).toUpperCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
494
+ if (/[A-Z]/.test(ch)) ini += ch;
495
+ }
496
+ });
497
+ return 'VAS' + (ini || 'X') + dd + mm + yy;
498
+ }
499
+ }
500
+
501
+ // Fallback to any code-like word
502
+ m = text.match(/\b[A-Z]{2,}\d{3,}\b/);
503
+ if (m) return m[0];
504
+
505
+ return null;
506
+ }
507
+
508
+ // =============================================
509
+ // 11. PATCH EXPORT BUTTONS DIRECTLY
510
+ // =============================================
511
+ function patchAllExportButtons() {
512
+ document.querySelectorAll('button, a, [role="button"]').forEach(function(btn) {
513
+ var text = (btn.textContent || '').toLowerCase();
514
+ var cls = (btn.className || '').toLowerCase();
515
+
516
+ // Excel export buttons
517
+ if ((text.indexOf('excel') >= 0 || cls.indexOf('excel') >= 0) &&
518
+ (text.indexOf('xuất') >= 0 || text.indexOf('export') >= 0 || cls.indexOf('quote-btn-excel') >= 0)) {
519
+ btn.dataset.vaiUltimateFixed = 'true';
520
+ btn.onclick = function(e) {
521
+ e.preventDefault();
522
+ e.stopPropagation();
523
+ console.log('[VAI ULTIMATE] Excel button clicked');
524
+
525
+ if (window.VAI_QR && typeof window.VAI_QR.exportExcel === 'function') {
526
+ window.VAI_QR.exportExcel();
527
+ } else if (typeof window.exportExcel === 'function') {
528
+ window.exportExcel();
529
+ } else if (typeof _doExportExcel === 'function') {
530
+ var d = (typeof getData === 'function') ? getData() : null;
531
+ if (d) {
532
+ var code = (window.VAI_QR && window.VAI_QR.getEffectiveOrderCode) ?
533
+ window.VAI_QR.getEffectiveOrderCode() : 'BAOGIA';
534
+ var qrUrl = (window.VAI_QR && window.VAI_QR.getQRUrl) ?
535
+ window.VAI_QR.getQRUrl(d.deposit > 0 ? d.remaining : d.grandTotal, code) : '';
536
+ _doExportExcel(d, d.qd, code, qrUrl);
537
+ }
538
+ }
539
+ return false;
540
+ };
541
+ }
542
+
543
+ // PDF export buttons
544
+ if ((text.indexOf('pdf') >= 0 || cls.indexOf('pdf') >= 0) &&
545
+ (text.indexOf('xuất') >= 0 || text.indexOf('export') >= 0 || cls.indexOf('quote-btn-pdf') >= 0)) {
546
+ btn.dataset.vaiUltimatePDFFixed = 'true';
547
+ btn.onclick = function(e) {
548
+ e.preventDefault();
549
+ e.stopPropagation();
550
+ console.log('[VAI ULTIMATE] PDF button clicked');
551
+
552
+ if (window.VAI_QR && typeof window.VAI_QR.exportPDF === 'function') {
553
+ window.VAI_QR.exportPDF();
554
+ } else if (typeof window.exportPDF === 'function') {
555
+ window.exportPDF();
556
+ } else if (typeof _doExportPDF === 'function') {
557
+ var d = (typeof getData === 'function') ? getData() : null;
558
+ if (d) {
559
+ var code = (window.VAI_QR && window.VAI_QR.getEffectiveOrderCode) ?
560
+ window.VAI_QR.getEffectiveOrderCode() : 'BAOGIA';
561
+ _doExportPDF(d, code);
562
+ }
563
+ }
564
+ return false;
565
+ };
566
+ }
567
+
568
+ // Delivery Excel buttons
569
+ if ((text.indexOf('giao hàng') >= 0 || text.indexOf('gh') >= 0) &&
570
+ text.indexOf('excel') >= 0) {
571
+ btn.dataset.vaiUltimateGHFixed = 'true';
572
+ btn.onclick = function(e) {
573
+ e.preventDefault();
574
+ e.stopPropagation();
575
+ console.log('[VAI ULTIMATE] GH Excel button clicked');
576
+ if (window.VAI_QR && typeof window.VAI_QR.exportDeliveryExcel === 'function') {
577
+ window.VAI_QR.exportDeliveryExcel();
578
+ } else if (typeof window.exportDeliveryExcel === 'function') {
579
+ window.exportDeliveryExcel();
580
+ }
581
+ return false;
582
+ };
583
+ }
584
+
585
+ // Delivery PDF buttons
586
+ if ((text.indexOf('giao hàng') >= 0 || text.indexOf('gh') >= 0) &&
587
+ text.indexOf('pdf') >= 0) {
588
+ btn.dataset.vaiUltimateGHPDFFixed = 'true';
589
+ btn.onclick = function(e) {
590
+ e.preventDefault();
591
+ e.stopPropagation();
592
+ console.log('[VAI ULTIMATE] GH PDF button clicked');
593
+ if (window.VAI_QR && typeof window.VAI_QR.exportDeliveryPDF === 'function') {
594
+ window.VAI_QR.exportDeliveryPDF();
595
+ } else if (typeof window.exportDeliveryPDF === 'function') {
596
+ window.exportDeliveryPDF();
597
+ }
598
+ return false;
599
+ };
600
+ }
601
+ });
602
+ }
603
+
604
+ // =============================================
605
+ // 12. PATCH ORDER DETAIL MODAL EXPORT BUTTONS
606
+ // =============================================
607
+ function patchOrderDetailButtons() {
608
+ var modal = document.getElementById('vai-order-detail-modal');
609
+ if (!modal) return;
610
+
611
+ var btnIds = ['od-xl', 'od-pdf', 'od-gh-xl', 'od-gh'];
612
+ var handlers = {
613
+ 'od-xl': function() {
614
+ console.log('[VAI ULTIMATE] Order detail Excel');
615
+ if (window.VAI_QR && typeof window.VAI_QR.exportExcel === 'function') {
616
+ window.VAI_QR.exportExcel();
617
+ }
618
+ },
619
+ 'od-pdf': function() {
620
+ console.log('[VAI ULTIMATE] Order detail PDF');
621
+ if (window.VAI_QR && typeof window.VAI_QR.exportPDF === 'function') {
622
+ window.VAI_QR.exportPDF();
623
+ }
624
+ },
625
+ 'od-gh-xl': function() {
626
+ console.log('[VAI ULTIMATE] Order detail GH Excel');
627
+ if (window.VAI_QR && typeof window.VAI_QR.exportDeliveryExcel === 'function') {
628
+ window.VAI_QR.exportDeliveryExcel();
629
+ }
630
+ },
631
+ 'od-gh': function() {
632
+ console.log('[VAI ULTIMATE] Order detail GH PDF');
633
+ if (window.VAI_QR && typeof window.VAI_QR.exportDeliveryPDF === 'function') {
634
+ window.VAI_QR.exportDeliveryPDF();
635
+ }
636
+ }
637
+ };
638
+
639
+ btnIds.forEach(function(id) {
640
+ var btn = document.getElementById(id);
641
+ if (btn) {
642
+ btn.onclick = function(e) {
643
+ e.preventDefault();
644
+ e.stopPropagation();
645
+ if (handlers[id]) handlers[id]();
646
+ return false;
647
+ };
648
+ }
649
+ });
650
+ }
651
+
652
+ // =============================================
653
+ // 13. INIT
654
+ // =============================================
655
+ function init() {
656
+ console.log('[VAI ULTIMATE] Initializing...');
657
+ patchAllExportButtons();
658
+ console.log('[VAI ULTIMATE] ✅ Patch buttons done');
659
+ }
660
+
661
+ // Start on DOM ready
662
+ if (document.readyState === 'loading') {
663
+ document.addEventListener('DOMContentLoaded', init);
664
+ } else {
665
+ init();
666
+ }
667
+
668
+ // =============================================
669
+ // 14. POLLING LOOP — keep everything working
670
+ // =============================================
671
+
672
+ // Re-inject re-download UI every 2s
673
+ setInterval(function() {
674
+ injectRedownloadUI();
675
+ }, 2000);
676
+
677
+ // Re-patch buttons every 3s (for dynamic buttons)
678
+ setInterval(function() {
679
+ patchAllExportButtons();
680
+ }, 3000);
681
+
682
+ // Re-patch order detail buttons every 2s
683
+ setInterval(function() {
684
+ patchOrderDetailButtons();
685
+ }, 2000);
686
+
687
+ console.log('[VAI ULTIMATE v1100] ✅ FULLY LOADED — multi-download working, all buttons bound');
688
+ })();