Khôi phục về commit dee59dc37cb80dffb88b8e5fa3c9a896173119d9

#21
by bep40 - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
.gitattributes DELETED
@@ -1,102 +0,0 @@
1
- grob_img/grob_p10.jpeg filter=lfs diff=lfs merge=lfs -text
2
- grob_img/grob_p101.jpeg filter=lfs diff=lfs merge=lfs -text
3
- grob_img/grob_p102.jpeg filter=lfs diff=lfs merge=lfs -text
4
- grob_img/grob_p106.jpeg filter=lfs diff=lfs merge=lfs -text
5
- grob_img/grob_p109.jpeg filter=lfs diff=lfs merge=lfs -text
6
- grob_img/grob_p11.jpeg filter=lfs diff=lfs merge=lfs -text
7
- grob_img/grob_p110.jpeg filter=lfs diff=lfs merge=lfs -text
8
- grob_img/grob_p111.jpeg filter=lfs diff=lfs merge=lfs -text
9
- grob_img/grob_p118.jpeg filter=lfs diff=lfs merge=lfs -text
10
- grob_img/grob_p119.jpeg filter=lfs diff=lfs merge=lfs -text
11
- grob_img/grob_p120.jpeg filter=lfs diff=lfs merge=lfs -text
12
- grob_img/grob_p125.jpeg filter=lfs diff=lfs merge=lfs -text
13
- grob_img/grob_p130.jpeg filter=lfs diff=lfs merge=lfs -text
14
- grob_img/grob_p131.jpeg filter=lfs diff=lfs merge=lfs -text
15
- grob_img/grob_p132.jpeg filter=lfs diff=lfs merge=lfs -text
16
- grob_img/grob_p133.jpeg filter=lfs diff=lfs merge=lfs -text
17
- grob_img/grob_p134.jpeg filter=lfs diff=lfs merge=lfs -text
18
- grob_img/grob_p140.jpeg filter=lfs diff=lfs merge=lfs -text
19
- grob_img/grob_p141.jpeg filter=lfs diff=lfs merge=lfs -text
20
- grob_img/grob_p166.jpeg filter=lfs diff=lfs merge=lfs -text
21
- grob_img/grob_p17.jpeg filter=lfs diff=lfs merge=lfs -text
22
- grob_img/grob_p174.jpeg filter=lfs diff=lfs merge=lfs -text
23
- grob_img/grob_p175.jpeg filter=lfs diff=lfs merge=lfs -text
24
- grob_img/grob_p177.jpeg filter=lfs diff=lfs merge=lfs -text
25
- grob_img/grob_p178.jpeg filter=lfs diff=lfs merge=lfs -text
26
- grob_img/grob_p179.jpeg filter=lfs diff=lfs merge=lfs -text
27
- grob_img/grob_p180.jpeg filter=lfs diff=lfs merge=lfs -text
28
- grob_img/grob_p183.jpeg filter=lfs diff=lfs merge=lfs -text
29
- grob_img/grob_p184.jpeg filter=lfs diff=lfs merge=lfs -text
30
- grob_img/grob_p185.jpeg filter=lfs diff=lfs merge=lfs -text
31
- grob_img/grob_p186.jpeg filter=lfs diff=lfs merge=lfs -text
32
- grob_img/grob_p19.jpeg filter=lfs diff=lfs merge=lfs -text
33
- grob_img/grob_p199.jpeg filter=lfs diff=lfs merge=lfs -text
34
- grob_img/grob_p2.jpeg filter=lfs diff=lfs merge=lfs -text
35
- grob_img/grob_p201.jpeg filter=lfs diff=lfs merge=lfs -text
36
- grob_img/grob_p21.jpeg filter=lfs diff=lfs merge=lfs -text
37
- grob_img/grob_p23.jpeg filter=lfs diff=lfs merge=lfs -text
38
- grob_img/grob_p24.jpeg filter=lfs diff=lfs merge=lfs -text
39
- grob_img/grob_p28.jpeg filter=lfs diff=lfs merge=lfs -text
40
- grob_img/grob_p29.jpeg filter=lfs diff=lfs merge=lfs -text
41
- grob_img/grob_p3.jpeg filter=lfs diff=lfs merge=lfs -text
42
- grob_img/grob_p30.jpeg filter=lfs diff=lfs merge=lfs -text
43
- grob_img/grob_p31.jpeg filter=lfs diff=lfs merge=lfs -text
44
- grob_img/grob_p32.jpeg filter=lfs diff=lfs merge=lfs -text
45
- grob_img/grob_p33.jpeg filter=lfs diff=lfs merge=lfs -text
46
- grob_img/grob_p34.jpeg filter=lfs diff=lfs merge=lfs -text
47
- grob_img/grob_p35.jpeg filter=lfs diff=lfs merge=lfs -text
48
- grob_img/grob_p36.jpeg filter=lfs diff=lfs merge=lfs -text
49
- grob_img/grob_p37.jpeg filter=lfs diff=lfs merge=lfs -text
50
- grob_img/grob_p38.jpeg filter=lfs diff=lfs merge=lfs -text
51
- grob_img/grob_p39.jpeg filter=lfs diff=lfs merge=lfs -text
52
- grob_img/grob_p40.jpeg filter=lfs diff=lfs merge=lfs -text
53
- grob_img/grob_p41.jpeg filter=lfs diff=lfs merge=lfs -text
54
- grob_img/grob_p42.jpeg filter=lfs diff=lfs merge=lfs -text
55
- grob_img/grob_p43.jpeg filter=lfs diff=lfs merge=lfs -text
56
- grob_img/grob_p44.jpeg filter=lfs diff=lfs merge=lfs -text
57
- grob_img/grob_p45.jpeg filter=lfs diff=lfs merge=lfs -text
58
- grob_img/grob_p46.jpeg filter=lfs diff=lfs merge=lfs -text
59
- grob_img/grob_p47.jpeg filter=lfs diff=lfs merge=lfs -text
60
- grob_img/grob_p48.jpeg filter=lfs diff=lfs merge=lfs -text
61
- grob_img/grob_p49.jpeg filter=lfs diff=lfs merge=lfs -text
62
- grob_img/grob_p50.jpeg filter=lfs diff=lfs merge=lfs -text
63
- grob_img/grob_p51.jpeg filter=lfs diff=lfs merge=lfs -text
64
- grob_img/grob_p52.jpeg filter=lfs diff=lfs merge=lfs -text
65
- grob_img/grob_p53.jpeg filter=lfs diff=lfs merge=lfs -text
66
- grob_img/grob_p54.jpeg filter=lfs diff=lfs merge=lfs -text
67
- grob_img/grob_p55.jpeg filter=lfs diff=lfs merge=lfs -text
68
- grob_img/grob_p56.jpeg filter=lfs diff=lfs merge=lfs -text
69
- grob_img/grob_p57.jpeg filter=lfs diff=lfs merge=lfs -text
70
- grob_img/grob_p58.jpeg filter=lfs diff=lfs merge=lfs -text
71
- grob_img/grob_p59.jpeg filter=lfs diff=lfs merge=lfs -text
72
- grob_img/grob_p6.jpeg filter=lfs diff=lfs merge=lfs -text
73
- grob_img/grob_p60.jpeg filter=lfs diff=lfs merge=lfs -text
74
- grob_img/grob_p61.jpeg filter=lfs diff=lfs merge=lfs -text
75
- grob_img/grob_p62.jpeg filter=lfs diff=lfs merge=lfs -text
76
- grob_img/grob_p63.jpeg filter=lfs diff=lfs merge=lfs -text
77
- grob_img/grob_p64.jpeg filter=lfs diff=lfs merge=lfs -text
78
- grob_img/grob_p65.jpeg filter=lfs diff=lfs merge=lfs -text
79
- grob_img/grob_p66.jpeg filter=lfs diff=lfs merge=lfs -text
80
- grob_img/grob_p67.jpeg filter=lfs diff=lfs merge=lfs -text
81
- grob_img/grob_p68.jpeg filter=lfs diff=lfs merge=lfs -text
82
- grob_img/grob_p69.jpeg filter=lfs diff=lfs merge=lfs -text
83
- grob_img/grob_p7.jpeg filter=lfs diff=lfs merge=lfs -text
84
- grob_img/grob_p70.jpeg filter=lfs diff=lfs merge=lfs -text
85
- grob_img/grob_p71.jpeg filter=lfs diff=lfs merge=lfs -text
86
- grob_img/grob_p72.jpeg filter=lfs diff=lfs merge=lfs -text
87
- grob_img/grob_p73.jpeg filter=lfs diff=lfs merge=lfs -text
88
- grob_img/grob_p74.jpeg filter=lfs diff=lfs merge=lfs -text
89
- grob_img/grob_p76.jpeg filter=lfs diff=lfs merge=lfs -text
90
- grob_img/grob_p77.jpeg filter=lfs diff=lfs merge=lfs -text
91
- grob_img/grob_p78.jpeg filter=lfs diff=lfs merge=lfs -text
92
- grob_img/grob_p79.jpeg filter=lfs diff=lfs merge=lfs -text
93
- grob_img/grob_p8.jpeg filter=lfs diff=lfs merge=lfs -text
94
- grob_img/grob_p80.jpeg filter=lfs diff=lfs merge=lfs -text
95
- grob_img/grob_p82.jpeg filter=lfs diff=lfs merge=lfs -text
96
- grob_img/grob_p83.jpeg filter=lfs diff=lfs merge=lfs -text
97
- grob_img/grob_p84.jpeg filter=lfs diff=lfs merge=lfs -text
98
- grob_img/grob_p85.jpeg filter=lfs diff=lfs merge=lfs -text
99
- grob_img/grob_p88.jpeg filter=lfs diff=lfs merge=lfs -text
100
- grob_img/grob_p9.jpeg filter=lfs diff=lfs merge=lfs -text
101
- grob_img/grob_p97.jpeg filter=lfs diff=lfs merge=lfs -text
102
- grob_img/grob_p98.jpeg filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/workflows/auto-sync.yml DELETED
@@ -1,48 +0,0 @@
1
- name: Auto-Sync Products
2
-
3
- on:
4
- schedule:
5
- # Chạy hàng ngày lúc midnight UTC (8:00 AM Việt Nam)
6
- - cron: '0 0 * * *'
7
- workflow_dispatch:
8
- inputs:
9
- dry_run:
10
- description: 'Chỉ kiểm tra mà không cập nhật'
11
- type: boolean
12
- default: false
13
- target_space:
14
- description: 'Không gian target (vai-avatar2 hoặc both)'
15
- type: choice
16
- options:
17
- - vai-avatar2
18
- - both
19
-
20
- jobs:
21
- sync-products:
22
- runs-on: ubuntu-latest
23
- steps:
24
- - name: Checkout code
25
- uses: actions/checkout@v4
26
-
27
- - name: Setup Python
28
- uses: actions/setup-python@v5
29
- with:
30
- python-version: '3.11'
31
-
32
- - name: Install dependencies
33
- run: |
34
- pip install huggingface_hub
35
-
36
- - name: Run sync products
37
- env:
38
- HF_TOKEN: ${{ secrets.HF_TOKEN }}
39
- run: |
40
- echo "Starting product auto-sync..."
41
- python3 sync_products.py
42
-
43
- if [ $? -eq 0 ]; then
44
- echo "✅ Sync completed successfully"
45
- else
46
- echo "❌ Sync failed"
47
- exit 1
48
- fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.rebuild CHANGED
@@ -1 +1 @@
1
- 2026-07-21T03:40:00+00:00 - Excel Fee Rows Fix - QR payment v1043
 
1
+ 2026-07-03T08:05:09+00:00
APPLY_EXCEL_FIX.md CHANGED
@@ -1,32 +1,51 @@
1
- # Cập nhật Excel Export - Phiếu báo giá
2
-
3
- ## Thay đổi đã thực hiện
4
-
5
- ### 1. Thêm các dòng phí vào Excel báo giá
6
- Khi nhập vào ô ck: Malloca ck 35%, giao hàng 200k, lắp đặt 500k, cọc 5tr, ghi chú: giao thứ 7
7
-
8
- **Excel báo giá sẽ hiển thị:**
9
- - Bảng sản phẩm (STT, Hình, Tên SP, Mã, Thông tin, SL, Đơn giá, Giá CK, Thành tiền, Ghi chú)
10
- - Các dòng phụ phí:
11
- - Phí giao hàng: +200.000đ
12
- - ⊕ Phí lắp đặt: +500.000đ
13
- - Dòng đặt cọc: Đặt cọc: 5.000.000đ
14
- - TỔNG CỘNG: Tổng tiền sau khi trừ cọc
15
-
16
- ### 2. Xóa bỏ dòng "Còn lại 100%"
17
- Dòng "Còn lại 100%" đã được xóa khỏi Excel xuất để file gọn hơn.
18
-
19
- ## Cách hoạt động
20
-
21
- 1. Nhập thông tin vào ô prompt (qcAiPrompt) với định dạng:
22
- - `ck 35%` - áp dụng chiết khấu 35% cho tất cả sản phẩm
23
- - `giao hàng 200k` hoặc `ship 200k` - thêm phí giao hàng
24
- - `lắp đặt 500k` hoặc `install 500k` - thêm phí lắp đặt
25
- - `cọc 5tr` hoặc `deposit 5tr` - thêm số tiền đặt cọc
26
- - `giao thứ 7` - ghi chú (sẽ xuất trong phần Ghi chú)
27
-
28
- 2. Nhấn nút "Xuất Excel" để tải file báo giá
29
-
30
- ## File đã cập nhật
31
- - `qr-payment-v1039.js` - Main script (đã cập nhật)
32
- - `vai-excel-fix-v2.js` - Excel fix script (đã cập nhật)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hướng dẫn áp dụng Fix Excel Download - CẬP NHẬT 2026
2
+
3
+ ## Vấn đề
4
+ File Excel chỉ tải được 1 lần, lần 2+ thất bại vì `URL.revokeObjectURL()` được gọi sau 60s.
5
+
6
+ ## 🎯 GIẢI PHÁP HOÀN HẢO - Fix v2.0
7
+
8
+ ### Cách 1: Auto Loader (TỰ ĐỘNG) - KHÔNG CẦN LÀM GÌ CẢ
9
+ - `auto-excel-fix-loader.js` tự động tải `vai-excel-fix-v2.js` khi trang mở
10
+ - Script đã được tích hợp sẵn trong index.html
11
+ - Chỉ cần mở lại trang là được áp dụng tự động ✅
12
+
13
+ ### Cách 2: Bookmarklet - KHÔNG CẦN CHỈNH SỬA CODE
14
+ Tạo bookmark mới với URL:
15
+ ```javascript
16
+ javascript:(function(){var s=document.createElement('script');s.src='/vai-excel-fix-v2.js?v='+Date.now();s.onload=function(){alert('✅ Excel Fix v2.0 đã tải!\nBạn có thể tải Excel bao nhiêu lần tùy thích!');};document.head.appendChild(s);})();
17
+ ```
18
+
19
+ ### Cách 3: Thêm script tag vào index.html (TỰ ĐỘNG)
20
+ Thêm đoạn sau vào trước thẻ `</body>`:
21
+ ```html
22
+ <!-- V.AI STUDIO Excel Multi-Download Fix v2.0 -->
23
+ <script src="vai-excel-fix-v2.js?v=2026"></script>
24
+ ```
25
+
26
+ ## Test
27
+ 1. Mở Space V.AISTUDIO
28
+ 2. Tạo báo giá (lập báo giá) Click 💾 Excel → tải ✅
29
+ 3. Click 💾 Excel NGAY → tải tiếp tục ✅
30
+ 4. Click nhiều lần nữa → luôn tải được ✅
31
+ 5. Quay lại tạo đơn hàng mới → GH Excel → tải ✅
32
+
33
+ ## File đã chuẩn
34
+ - ✅ `vai-excel-fix-v2.js` - Script fix chính (mới nhất)
35
+ - ✅ `auto-excel-fix-loader.js` - Tự động tải fix
36
+ - ✅ `vai-quote-excel-order-fix-v1050.js` - Backup (nếu v2 không tải được)
37
+
38
+ ## Điều kiện áp dụng
39
+ - Cần có thư viện ExcelJS (đã có trong index.html):
40
+ ```html
41
+ <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
42
+ ```
43
+
44
+ ## Changelog v2.0
45
+ - Thay đổi cách quản lý blob URL: registry toàn cục
46
+ - Patch URL.revokeObjectURL: không revoke khi chưa unload
47
+ - Hỗ trợ cả Quote modal và Order modal
48
+ - Toast thông báo khi xuất thành công/thất bại
49
+
50
+ ---
51
+ *Cập nhật: 14-07-2026 - ML Intern - Fix Excel multi-download hoàn hảo*
FIX-STATUS.md DELETED
@@ -1,41 +0,0 @@
1
- # ✅ FIX HOÀN THÀNH: Phí giao hàng, lắp đặt, cọc được thêm vào Excel
2
-
3
- ## ĐÃ SỬA
4
-
5
- **File: `qr-payment.js`** - phiên bản v1043
6
-
7
- ### Thay đổi chính:
8
-
9
- 1. **Hàm `_doExportExcel`** - Thêm dòng phí vào Excel:
10
- - Sau khi in danh sách sản phẩm
11
- - Thêm các dòng phí: `⊕ Phí giao hàng`, `⊕ Phí lắp đặt`, `⊕ Phí bốc xếp`
12
- - Cập nhật công thức tổng: `SUM(I{firstItem}:I{lastItem})+SUM(I{feeStart}:I{feeEnd})`
13
-
14
- 2. **Hàm `parsePrompt`** - Cải thiện parsing chi phí:
15
- - Nhận diện từ khóa: `giao`, `giao hàng`, `ship`, `lắp`, `lắp đặt`, `bốc xếp`
16
- - Tự động gán nhãn phí phù hợp
17
-
18
- ## CÁCH ÁP DỤNG
19
-
20
- Do rate limit, bạn cần **thủ công rebuild Space**:
21
-
22
- 1. Vào https://huggingface.co/spaces/bep40/V.AISTUDIO/settings
23
- 2. Chọn tab **Builder**
24
- 3. Nhấn **Rebuild** hoặc **Restart**
25
-
26
- Hoặc chờ 1-2 phút để Space tự động cập nhật.
27
-
28
- ## KIỂM TRA
29
-
30
- Sau khi rebuild, mở Space và thử:
31
- 1. Thêm sản phẩm vào giỏ
32
- 2. Nhập vào ô yêu cầu: `giao 200k, lắp 300k, cọc 5tr`
33
- 3. Nhấn nút **⚡ Áp dụng**
34
- 4. Nhấn **Xuất Excel** - file Excel sẽ có các dòng:
35
- - ⊕ Phí giao hàng: 200,000đ
36
- - ⊕ Phí lắp đặt: 300,000đ
37
- - Cọc: 5,000,000đ
38
- - Còn lại: tổng cộng - cọc
39
-
40
- ---
41
- PR #24 đã merge vào main.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
debug_export.html DELETED
@@ -1,47 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="vi"><head><meta charset="utf-8"/>
3
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
4
- <title>VAI Export Debug</title>
5
- <script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
6
- <style>body{font-family:system-ui;padding:16px;background:#f5f7fa}textarea{width:100%;height:120px;font-size:16px;padding:10px;border:2px solid #003f62;border-radius:8px}button{margin-top:12px;width:100%;padding:14px;font-size:18px;background:#003f62;color:#fff;border:0;border-radius:10px;font-weight:700}#log{margin-top:16px;white-space:pre-wrap;background:#fff;border:1px solid #ccc;border-radius:8px;padding:12px;font-size:13px;max-height:40vh;overflow:auto}</style>
7
- </head><body>
8
- <h2>VAI Export Debug</h2>
9
- <p>Nhập prompt rồi bấm nút. File sẽ có đủ dòng phí. Log hiển thị bên dưới.</p>
10
- <textarea id="prompt">Malloca ck 35%, giao hàng 200k, lắp đặt 500k, cọc 5tr, ghi chú: giao thứ 7</textarea>
11
- <button id="go">Xuất Excel Test</button>
12
- <div id="log">Log sẽ hiện ở đây...</div>
13
- <script>
14
- function L(s){var el=document.getElementById('log');el.textContent+=("\n"+s);el.scrollTop=el.scrollHeight;console.log(s);}
15
- document.getElementById('go').addEventListener('click',async function(){
16
- L("ExcelJS loaded: "+(typeof ExcelJS!=='undefined'));
17
- var txt=document.getElementById('prompt').value.trim();
18
- L("Prompt: "+txt);
19
- var fees=[];var deposit=0;var note='';
20
- var m1=txt.match(/giao hàng\s*([\d.,]+)\s*(k|tr|triệu|đ|nghìn)?/i);
21
- if(m1){var v=parseFloat(m1[1].replace(/[.,]/g,''));var u=(m1[2]||'').toLowerCase();if(u==='k')v*=1000;if(u==='tr'||u==='triệu')v*=1e6;fees.push({label:'Phí giao hàng',amount:v});}
22
- var m2=txt.match(/lắp đặt\s*([\d.,]+)\s*(k|tr|triệu|đ|nghìn)?/i);
23
- if(m2){var v=parseFloat(m2[1].replace(/[.,]/g,''));var u=(m2[2]||'').toLowerCase();if(u==='k')v*=1000;if(u==='tr'||u==='triệu')v*=1e6;fees.push({label:'Phí lắp đặt',amount:v});}
24
- var m3=txt.match(/cọc\s*([\d.,]+)\s*(k|tr|triệu|đ|nghìn)?/i);
25
- if(m3){var v=parseFloat(m3[1].replace(/[.,]/g,''));var u=(m3[2]||'').toLowerCase();if(u==='k')v*=1000;if(u==='tr'||u==='triệu')v*=1e6;deposit=v;}
26
- var mn=txt.match(/ghi chú\s*[::]?\s*(.+)$/i);
27
- if(mn)note=mn[1].trim();
28
- L("Parsed fees: "+JSON.stringify(fees)+" deposit="+deposit+" note="+note);
29
- if(typeof ExcelJS==='undefined'){L("ERROR: ExcelJS not loaded - check network/CDN");return;}
30
- try{
31
- var wb=new ExcelJS.Workbook();var ws=wb.addWorksheet('BaoGia');
32
- ws.addRow(['BẢNG BÁO GIÁ']);
33
- ws.addRow([]);
34
- ws.addRow(['1','SP Test','MH800TC',1,7350000,4777500,4777500]);
35
- fees.forEach(function(f){ws.addRow([' '+f.label, f.amount]);L("ROW: "+f.label+" "+f.amount);});
36
- var total=4777500+fees.reduce(function(a,f){return a+f.amount;},0);
37
- ws.addRow(['TỔNG CỘNG', total]);
38
- if(deposit>0){ws.addRow(['Đã cọc',deposit]);ws.addRow(['CÒN LẠI',total-deposit]);}
39
- if(note)ws.addRow(['Ghi chú: '+note]);
40
- var buf=await wb.xlsx.writeBuffer();
41
- var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
42
- var a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='VAI_debug.xlsx';a.click();
43
- L("FILE DOWNLOADED OK. Dòng phí: "+(fees.length>0?'CÓ':'KHÔNG'));
44
- }catch(e){L("EXPORT ERROR: "+e.message);}
45
- });
46
- </script>
47
- </body></html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
excel-fix-100px.js DELETED
@@ -1,149 +0,0 @@
1
- /**
2
- * V.AI STUDIO - Excel Export v1102
3
- * FIX: Hình ảnh 100x100px trong Excel
4
- */
5
- (function(){
6
- 'use strict';
7
-
8
- function fmt(n){
9
- if(n==null||n==='')return'0đ';
10
- var num=typeof n==='string'?parseFloat(n.replace(/[^\d.-]/g,'')):Number(n);
11
- if(isNaN(num))return'0đ';
12
- return num.toLocaleString('vi-VN')+'đ';
13
- }
14
-
15
- async function fetchImageArrayBuffer(url){
16
- if(!url||!url.startsWith('http')) return null;
17
- try{
18
- var r = await fetch(url,{mode:'cors'});
19
- if(r.ok) return await r.arrayBuffer();
20
- }catch(e){}
21
- try{
22
- var r2 = await fetch('https://api.allorigins.win/raw?url=' + encodeURIComponent(url));
23
- if(r2.ok) return await r2.arrayBuffer();
24
- }catch(e){}
25
- return null;
26
- }
27
-
28
- function getOrderCode(){
29
- var n = document.getElementById('quoteName')?.value || '';
30
- var h = window.location.hash;
31
- var today = new Date(), dd=today.getDate().toString().padStart(2,'0'), mm=(today.getMonth()+1).toString().padStart(2,'0'), yy=today.getFullYear().toString().slice(-2);
32
- var ini='';
33
- if(n && n.trim()){
34
- n.trim().split(/\s+/).forEach(function(w){
35
- if(w && /[A-Z]/.test(w.charAt(0).toUpperCase())) ini += w.charAt(0).toUpperCase();
36
- });
37
- }
38
- return 'VAS'+(ini||'X')+dd+mm+yy;
39
- }
40
-
41
- window.exportExcel = async function(){
42
- var cart = window.cart || [];
43
- if(!cart.length){alert('Giỏ trống!');return;}
44
-
45
- if(typeof ExcelJS === 'undefined'){
46
- var s = document.createElement('script');
47
- s.src = 'https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js';
48
- document.head.appendChild(s);
49
- await new Promise(function(r){s.onload=r});
50
- }
51
-
52
- try{
53
- var code = getOrderCode();
54
- var wb = new ExcelJS.Workbook(), ws = wb.addWorksheet('Báo giá');
55
-
56
- // Column 2 width 15 for 100px image
57
- ws.columns = [
58
- {width:6},{width:15},{width:30},{width:15},
59
- {width:22},{width:8},{width:15},{width:15},{width:18},{width:16}
60
- ];
61
-
62
- // Header title
63
- ws.mergeCells('A1:J1');
64
- ws.getCell('A1').value = 'BẢNG BÁO GIÁ';
65
- ws.getCell('A1').font = {bold:true, size:18, color:{argb:'FFDB9815'}};
66
- ws.getCell('A1').alignment = {horizontal:'center'};
67
- ws.getRow(1).height = 30;
68
-
69
- // Table header
70
- var hr = ws.getRow(3);
71
- ['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){
72
- var c = hr.getCell(i+1);
73
- c.value = h;
74
- c.font = {bold:true, color:{argb:'FFFFFFFF'}, size:9};
75
- c.fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF003F62'}};
76
- c.alignment = {horizontal:'center', vertical:'middle'};
77
- });
78
- hr.height = 20;
79
-
80
- // Items with 100x100px images
81
- var firstRow = 5;
82
- await Promise.all(cart.map(async function(c, i){
83
- var p = window.D && D[c.idx] ? D[c.idx] : null;
84
- var row = ws.getRow(firstRow + i);
85
- row.height = 120; // 100px + padding
86
-
87
- var bgColor = i % 2 === 0 ? 'FFF8FAFC' : 'FFFFFFFF';
88
-
89
- row.getCell(1).value = i+1;
90
- row.getCell(1).alignment = {horizontal:'center', vertical:'middle'};
91
-
92
- // IMAGE 100x100px
93
- if((p && p.image) || c.image){
94
- var imgUrl = p?.image || c.image;
95
- var imgBuf = await fetchImageArrayBuffer(imgUrl);
96
- if(imgBuf){
97
- try{
98
- wb.addImage(wb.addImage({buffer:imgBuf, extension:'png'}),
99
- {tl:{col:1, row:firstRow+i-1}, ext:{width:100, height:100}});
100
- }catch(e){}
101
- }
102
- }
103
-
104
- row.getCell(3).value = c.name || (p?p.name||p.n:'') || '';
105
- row.getCell(3).font = {bold:true, size:9};
106
- row.getCell(4).value = c.sku || (p?p.sku||p.model:'') || '';
107
- row.getCell(6).value = Number(c.qty||1);
108
- row.getCell(7).value = Number(c.priceNum||(p?p.priceNum:0)||0);
109
- row.getCell(7).numFmt = '#,##0"đ"';
110
- row.getCell(8).value = Number(c.priceNum||(p?p.priceNum:0)||0);
111
- row.getCell(8).numFmt = '#,##0"đ"';
112
- row.getCell(9).value = Number(c.priceNum||(p?p.priceNum:0)||0) * Number(c.qty||1);
113
- row.getCell(9).numFmt = '#,##0"đ"';
114
-
115
- for(var ci=1; ci<=10; ci++){
116
- row.getCell(ci).fill = {type:'pattern', pattern:'solid', fgColor:{argb:bgColor}};
117
- }
118
- }));
119
-
120
- var lastRow = firstRow + cart.length - 1;
121
- // Total row
122
- ws.getCell('A'+(lastRow+2)).value = 'TỔNG CỘNG';
123
- ws.getCell('A'+(lastRow+2)).font = {bold:true, size:13, color:{argb:'FFFFFFFF'}};
124
- ws.getCell('A'+(lastRow+2)).fill = {type:'pattern', pattern:'solid', fgColor:{argb:'FF003F62'}};
125
- ws.getCell('I'+(lastRow+2)).value = {formula:'SUM(I'+firstRow+':I'+lastRow+')'};
126
- ws.getCell('I'+(lastRow+2)).numFmt = '#,##0"đ"';
127
- ws.getCell('I'+(lastRow+2)).font = {bold:true, size:14, color:{argb:'FFF0B840'}};
128
-
129
- wb.calcProperties.fullCalcOnLoad = true;
130
- var buf = await wb.xlsx.writeBuffer();
131
- var blob = new Blob([buf], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
132
- var url = URL.createObjectURL(blob);
133
- var a = document.createElement('a');
134
- a.href = url;
135
- a.download = code + '.xlsx';
136
- document.body.appendChild(a);
137
- a.click();
138
- setTimeout(function(){ document.body.removeChild(a); URL.revokeObjectURL(url); }, 30000);
139
-
140
- console.log('[VAI] Excel export OK - hình ảnh 100px');
141
- if(typeof showToast === 'function') showToast('✅ Excel OK - Hình ảnh 100px');
142
-
143
- }catch(e){
144
- console.error('[VAI] Excel error:', e);
145
- alert('Lỗi: '+e.message);
146
- }
147
- };
148
-
149
- console.log('[VAI v1102] Excel fix loaded - 100px images ready');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
grob-price-fix.js DELETED
@@ -1,87 +0,0 @@
1
- /**
2
- * V.AI STUDIO — Grob Price Fix
3
- * ==============================
4
- * Fixes incorrect prices from the catalogue-based PDF price list.
5
- * Fixed as of July 2026 per Grob Vietnam catalogue.
6
- */
7
- (function() {
8
- 'use strict';
9
- if (window.__GROB_PRICE_FIX__) return;
10
- window.__GROB_PRICE_FIX__ = true;
11
-
12
- const PRICE_FIXES = {
13
- // 0203 | Grob | Bản lề inox - cánh/phủ bì 203mm: 0đ -> 67.000đ
14
- '0203': { p: '67.000đ', pn: 67000 },
15
- // 0280 | Grob | Khay chia thìa dĩa nhựa - cánh/phủ bì 280mm: 885.000đ -> 965.000đ
16
- '0280': { p: '965.000đ', pn: 965000 },
17
- // 0290 | Grob | Khay chia thìa dĩa nhựa - cánh/phủ bì 290mm: 965.000đ -> 1.018.000đ
18
- '0290': { p: '1.018.000đ', pn: 1018000 },
19
- };
20
-
21
- function patchProduct(prod) {
22
- if (!prod) return;
23
- var name = prod.n || prod.name || '';
24
- for (var code in PRICE_FIXES) {
25
- if (name.indexOf(code) === 0 && name.indexOf('Grob') !== -1) {
26
- var fix = PRICE_FIXES[code];
27
- if (prod.p) prod.p = fix.p;
28
- if (prod.pn !== undefined) prod.pn = fix.pn;
29
- if (prod.price) prod.price = fix.p;
30
- if (prod.priceNum !== undefined) prod.priceNum = fix.pn;
31
- return true;
32
- }
33
- }
34
- return false;
35
- }
36
-
37
- // Patch after D is populated
38
- function patchD() {
39
- if (typeof window.D !== 'undefined' && Array.isArray(window.D)) {
40
- var patched = 0;
41
- window.D.forEach(function(p) {
42
- if (patchProduct(p)) patched++;
43
- });
44
- if (patched > 0) {
45
- console.log('[Grob Price Fix] Patched ' + patched + ' product(s) in D');
46
- // Also sync prices for items already in cart
47
- patchCart();
48
- if (typeof window.refreshUI === 'function') window.refreshUI();
49
- }
50
- }
51
- }
52
-
53
- // Sync Grob prices in cart (localStorage)
54
- function patchCart() {
55
- try {
56
- var cart = JSON.parse(localStorage.getItem('malloca_cart') || '[]');
57
- if (!Array.isArray(cart) || !cart.length) return;
58
- var changed = false;
59
- cart.forEach(function(item) {
60
- if (patchProduct(item)) changed = true;
61
- });
62
- if (changed) {
63
- localStorage.setItem('malloca_cart', JSON.stringify(cart));
64
- window.cart = cart;
65
- console.log('[Grob Price Fix] Synced prices in cart');
66
- }
67
- } catch(e) {}
68
- }
69
-
70
- // Try patching at various intervals as data loads
71
- function onReady() {
72
- setTimeout(patchD, 200);
73
- setTimeout(patchD, 500);
74
- setTimeout(patchD, 1000);
75
- setTimeout(patchD, 2000);
76
- setTimeout(patchD, 3000);
77
- setTimeout(patchD, 5000);
78
- }
79
-
80
- if (document.readyState === 'loading') {
81
- document.addEventListener('DOMContentLoaded', onReady);
82
- } else {
83
- onReady();
84
- }
85
-
86
- console.log('[Grob Price Fix] Loaded: 02.03=67k, 02.80=965k, 02.90=1,018k');
87
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
grob_img/grob_p1.jpeg DELETED
Binary file (80 kB)
 
grob_img/grob_p10.jpeg DELETED

Git LFS Details

  • SHA256: 3cf047359715058a5f1e2ae91a8a2f1163539f4712de1db357ae12e677cac638
  • Pointer size: 131 Bytes
  • Size of remote file: 222 kB
grob_img/grob_p100.jpeg DELETED
Binary file (51.6 kB)
 
grob_img/grob_p101.jpeg DELETED

Git LFS Details

  • SHA256: 5f695be82687bb3a52248ff537adb6950f73b64c21f483ff22b9e81213b1db61
  • Pointer size: 131 Bytes
  • Size of remote file: 126 kB
grob_img/grob_p102.jpeg DELETED

Git LFS Details

  • SHA256: c9c705dbb20655e160ad96fd17c5c33f505b7702f0b2c031cf49ffcf239d4e80
  • Pointer size: 131 Bytes
  • Size of remote file: 394 kB
grob_img/grob_p103.jpeg DELETED
Binary file (42 kB)
 
grob_img/grob_p104.jpeg DELETED
Binary file (20 kB)
 
grob_img/grob_p105.jpeg DELETED
Binary file (56.2 kB)
 
grob_img/grob_p106.jpeg DELETED

Git LFS Details

  • SHA256: 62afdde0435420e325e86f5162f0d637a1c53f90ca9ba3b1fb72f900086fb6a9
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
grob_img/grob_p107.jpeg DELETED
Binary file (96 kB)
 
grob_img/grob_p108.jpeg DELETED
Binary file (86.7 kB)
 
grob_img/grob_p109.jpeg DELETED

Git LFS Details

  • SHA256: 3d45a5d7fbe20e41f181eb81adf669d9263daddb1787e9dc47bed68355637314
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
grob_img/grob_p11.jpeg DELETED

Git LFS Details

  • SHA256: bc47b6543988f6a4074ce37aa20ea13aa03d6f2f806a823869dec4fba362be7f
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
grob_img/grob_p110.jpeg DELETED

Git LFS Details

  • SHA256: a84e2440229ca6da6a9d924f027a39e17176fc495c74d18a0c8f8307b2d93f2c
  • Pointer size: 131 Bytes
  • Size of remote file: 101 kB
grob_img/grob_p111.jpeg DELETED

Git LFS Details

  • SHA256: 4a8c39cc23363b072d590675666c9e8b202c3030b0ced467bad694a19b197ded
  • Pointer size: 131 Bytes
  • Size of remote file: 286 kB
grob_img/grob_p112.jpeg DELETED
Binary file (54 kB)
 
grob_img/grob_p113.jpeg DELETED
Binary file (76.2 kB)
 
grob_img/grob_p114.jpeg DELETED
Binary file (92.9 kB)
 
grob_img/grob_p115.jpeg DELETED
Binary file (92.9 kB)
 
grob_img/grob_p116.jpeg DELETED
Binary file (40.1 kB)
 
grob_img/grob_p117.jpeg DELETED
Binary file (21.9 kB)
 
grob_img/grob_p118.jpeg DELETED

Git LFS Details

  • SHA256: 269778d257f719f470c89f27e83ef12c4913cc4ed7adb4a1aa181494f74c63f3
  • Pointer size: 131 Bytes
  • Size of remote file: 552 kB
grob_img/grob_p119.jpeg DELETED

Git LFS Details

  • SHA256: 269778d257f719f470c89f27e83ef12c4913cc4ed7adb4a1aa181494f74c63f3
  • Pointer size: 131 Bytes
  • Size of remote file: 552 kB
grob_img/grob_p12.jpeg DELETED
Binary file (89.4 kB)
 
grob_img/grob_p120.jpeg DELETED

Git LFS Details

  • SHA256: 74e912df9328009cdd1a040974cec3309795335536dd1c89b4d80f138f28f7a9
  • Pointer size: 131 Bytes
  • Size of remote file: 489 kB
grob_img/grob_p121.jpeg DELETED
Binary file (61.4 kB)
 
grob_img/grob_p122.jpeg DELETED
Binary file (58.4 kB)
 
grob_img/grob_p123.jpeg DELETED
Binary file (59.5 kB)
 
grob_img/grob_p124.jpeg DELETED
Binary file (59.5 kB)
 
grob_img/grob_p125.jpeg DELETED

Git LFS Details

  • SHA256: eadac8ca424066b951c6ede4fda5d234144f00f470dbf7564422f3744ae09c81
  • Pointer size: 131 Bytes
  • Size of remote file: 305 kB
grob_img/grob_p126.jpeg DELETED
Binary file (33.6 kB)
 
grob_img/grob_p127.jpeg DELETED
Binary file (64.2 kB)
 
grob_img/grob_p128.jpeg DELETED
Binary file (41.2 kB)
 
grob_img/grob_p129.jpeg DELETED
Binary file (51.6 kB)
 
grob_img/grob_p13.jpeg DELETED
Binary file (98 kB)
 
grob_img/grob_p130.jpeg DELETED

Git LFS Details

  • SHA256: 96bef100ad82f1bf948a6559608d84429e17c04086d893ca83835e7d9d2e71c3
  • Pointer size: 131 Bytes
  • Size of remote file: 382 kB
grob_img/grob_p131.jpeg DELETED

Git LFS Details

  • SHA256: 5a452d634674cdd034b4965382fd9b5b0cb59e6bf0ef156d2d043d3595e0debf
  • Pointer size: 131 Bytes
  • Size of remote file: 407 kB
grob_img/grob_p132.jpeg DELETED

Git LFS Details

  • SHA256: 7254e22d6463e5163ab1587467e6dc8a57f67a7747d5c5a5411a63d59ae6f1ee
  • Pointer size: 131 Bytes
  • Size of remote file: 264 kB
grob_img/grob_p133.jpeg DELETED

Git LFS Details

  • SHA256: 7254e22d6463e5163ab1587467e6dc8a57f67a7747d5c5a5411a63d59ae6f1ee
  • Pointer size: 131 Bytes
  • Size of remote file: 264 kB
grob_img/grob_p134.jpeg DELETED

Git LFS Details

  • SHA256: 5a452d634674cdd034b4965382fd9b5b0cb59e6bf0ef156d2d043d3595e0debf
  • Pointer size: 131 Bytes
  • Size of remote file: 407 kB
grob_img/grob_p135.jpeg DELETED
Binary file (48.5 kB)
 
grob_img/grob_p136.jpeg DELETED
Binary file (67.4 kB)