diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index babd2094b27f15cc3798186d301378be619fd0f1..0000000000000000000000000000000000000000 --- a/.gitattributes +++ /dev/null @@ -1,102 +0,0 @@ -grob_img/grob_p10.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p101.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p102.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p106.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p109.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p11.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p110.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p111.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p118.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p119.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p120.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p125.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p130.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p131.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p132.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p133.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p134.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p140.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p141.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p166.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p17.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p174.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p175.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p177.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p178.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p179.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p180.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p183.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p184.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p185.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p186.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p19.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p199.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p2.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p201.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p21.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p23.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p24.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p28.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p29.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p3.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p30.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p31.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p32.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p33.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p34.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p35.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p36.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p37.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p38.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p39.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p40.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p41.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p42.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p43.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p44.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p45.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p46.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p47.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p48.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p49.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p50.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p51.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p52.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p53.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p54.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p55.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p56.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p57.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p58.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p59.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p6.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p60.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p61.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p62.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p63.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p64.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p65.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p66.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p67.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p68.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p69.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p7.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p70.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p71.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p72.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p73.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p74.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p76.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p77.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p78.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p79.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p8.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p80.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p82.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p83.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p84.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p85.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p88.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p9.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p97.jpeg filter=lfs diff=lfs merge=lfs -text -grob_img/grob_p98.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/auto-sync.yml b/.github/workflows/auto-sync.yml deleted file mode 100644 index bce784fdacfedcfe6ee246bcf145f4872b562fbb..0000000000000000000000000000000000000000 --- a/.github/workflows/auto-sync.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Auto-Sync Products - -on: - schedule: - # Chạy hàng ngày lúc midnight UTC (8:00 AM Việt Nam) - - cron: '0 0 * * *' - workflow_dispatch: - inputs: - dry_run: - description: 'Chỉ kiểm tra mà không cập nhật' - type: boolean - default: false - target_space: - description: 'Không gian target (vai-avatar2 hoặc both)' - type: choice - options: - - vai-avatar2 - - both - -jobs: - sync-products: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - pip install huggingface_hub - - - name: Run sync products - env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} - run: | - echo "Starting product auto-sync..." - python3 sync_products.py - - if [ $? -eq 0 ]; then - echo "✅ Sync completed successfully" - else - echo "❌ Sync failed" - exit 1 - fi diff --git a/.rebuild b/.rebuild index 1dff87e46c3b3a5f9d29730e500dc8fd3a9570d0..193f020ddcb40bb854a5c26cb9333ddfb1cd6714 100644 --- a/.rebuild +++ b/.rebuild @@ -1 +1 @@ -2026-07-21T03:40:00+00:00 - Excel Fee Rows Fix - QR payment v1043 \ No newline at end of file +2026-07-03T08:05:09+00:00 diff --git a/ADMIN-QUICK-FIX.md b/ADMIN-QUICK-FIX.md deleted file mode 100644 index 309e08d8b793834b032003112201dc27ae6bfe5c..0000000000000000000000000000000000000000 --- a/ADMIN-QUICK-FIX.md +++ /dev/null @@ -1,44 +0,0 @@ -# ADMIN: Quick Fix for Excel Multi-Download - -## 🔥 FIX NGAY LẬP TỨC - 3 PHÚT - -### Bước 1: Thêm script vào index.html (1 phút) - -Mở file `index.html` trong Space Settings → Files, tìm `
-Nhập prompt rồi bấm nút. File sẽ có đủ dòng phí. Log hiển thị bên dưới.
- - -` và thêm trước: - -```html - - -``` - -### Bước 2: Click nút "Rebuild" (2 phút) - -Space Settings → Repository → Rebuild - -### Bước 3: Kiểm tra - -Mở https://huggingface.co/spaces/bep40/V.AISTUDIO -→ Thử click nút Excel/GH Excel nhiều lần ✅ - ---- - -## 📋 Hoặc dùng bookmarklet (0 cài đặt) - -Copy bookmark: - -``` -javascript:(function(){var s=document.createElement('script');s.src='https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/vai-quote-excel-order-fix-v1050.js?v='+Date.now();s.onload=function(){alert('✅ Fix OK! Tải Excel nhiều lần dc');};document.head.appendChild(s);})(); -``` - -→ Tạo bookmark mới → Dán vào URL → Click khi vào Space - ---- - -## ✅ Kết quả - -- Excel báo giá: **tải vô hạn lần** -- Excel giao hàng: **tải vô hạn lần** -- PDF: hoạt động bình thường - ---- -*ML Intern - 14/07/2026* \ No newline at end of file diff --git a/APPLY_EXCEL_FIX.md b/APPLY_EXCEL_FIX.md index 932b0b7450b5e8b8ac842b752d048b8bb1a56ae7..d8c2ce8a41117468c3f21a60b725a9be82d215b8 100644 --- a/APPLY_EXCEL_FIX.md +++ b/APPLY_EXCEL_FIX.md @@ -1,32 +1,48 @@ -# Cập nhật Excel Export - Phiếu báo giá - -## Thay đổi đã thực hiện - -### 1. Thêm các dòng phí vào Excel báo giá -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 - -**Excel báo giá sẽ hiển thị:** -- 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ú) -- Các dòng phụ phí: - - ⊕ Phí giao hàng: +200.000đ - - ⊕ Phí lắp đặt: +500.000đ -- Dòng đặt cọc: Đặt cọc: 5.000.000đ -- TỔNG CỘNG: Tổng tiền sau khi trừ cọc - -### 2. Xóa bỏ dòng "Còn lại 100%" -Dòng "Còn lại 100%" đã được xóa khỏi Excel xuất để file gọn hơn. - -## Cách hoạt động - -1. Nhập thông tin vào ô prompt (qcAiPrompt) với định dạng: - - `ck 35%` - áp dụng chiết khấu 35% cho tất cả sản phẩm - - `giao hàng 200k` hoặc `ship 200k` - thêm phí giao hàng - - `lắp đặt 500k` hoặc `install 500k` - thêm phí lắp đặt - - `cọc 5tr` hoặc `deposit 5tr` - thêm số tiền đặt cọc - - `giao thứ 7` - ghi chú (sẽ xuất trong phần Ghi chú) - -2. Nhấn nút "Xuất Excel" để tải file báo giá - -## File đã cập nhật -- `qr-payment-v1039.js` - Main script (đã cập nhật) -- `vai-excel-fix-v2.js` - Excel fix script (đã cập nhật) \ No newline at end of file +# Hướng dẫn áp dụng Fix Excel Download + +## Vấn đề +File Excel chỉ tải được 1 lần, lần 2+ thất bại vì `URL.revokeObjectURL()` được gọi sau 60s. + +## Cách 1: Thêm script (KHUYÊN DÙNG - NHANH NHẤT) + +Mở file `index.html`, tìm thẻ `` và **thêm đoạn sau vào ngay trước`body>`>:** + +```html + + +``` + +## Cách 2: Thêm script include + +```html + +``` + +## Cách 3: Sửa trực tiếp trong qr-payment-v1038.js + +Tìm và **xóa** 2 dòng sau: +- Trong `_doExportExcel`: `setTimeout(function(){URL.revokeObjectURL(url);},60000);` +- Trong `_doExportDeliveryExcel`: `setTimeout(function(){URL.revokeObjectURL(url);},60000);` + +## Test +1. Mở Space V.AISTUDIO +2. Tạo báo giá → Click 💾 Excel → tải +3. Click 💾 Excel NGAY → vẫn tải được ✅ + +## File đã chuẩn bị +- excel-download-ultimate-fix-v2.js (đã upload) +- PR #6 (đang chờ merge) + +--- +*Cập nhật: 14-07-2026* \ No newline at end of file diff --git a/CHECK_FIX.js b/CHECK_FIX.js deleted file mode 100644 index 8c1a3cd6296b6f4fb9722a74686e67e5d8060870..0000000000000000000000000000000000000000 --- a/CHECK_FIX.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * V.AI STUDIO - Excel Fix Checker - * Chạy trong console (F12) để xác nhận fix đã áp dụng - */ -(function() { - 'use strict'; - - var results = { - blobPatched: !!window.__VAI_REVOKE_PATCHED, - fixLoaded: !!window.VAI_FIX, - excelJsLoaded: typeof ExcelJS !== 'undefined', - registryExists: !!window.__VAI_BLOB_REGISTRY - }; - - console.log('%c[VAI FIX CHECKER] Kết quả:', 'color: #003f62; font-weight: bold; font-size: 14px'); - - if (results.blobPatched) { - console.log('%c✅ URL.revokeObjectURL đã được patch', 'color: #217346; font-weight: bold'); - } else { - console.log('%c❌ URL.revokeObjectURL CHƯA patch', 'color: #dc3545; font-weight: bold'); - } - - if (results.fixLoaded) { - console.log('%c✅ VAI_FIX module đã load', 'color: #217346; font-weight: bold'); - } else { - console.log('%c❌ VAI_FIX module CHƯA load', 'color: #dc3545; font-weight: bold'); - } - - if (results.registryExists) { - console.log('%c✅ Blob Registry tồn tại', 'color: #217346; font-weight: bold'); - } else { - console.log('%c❌ Blob Registry CHƯA tồn tại', 'color: #dc3545; font-weight: bold'); - } - - if (results.excelJsLoaded) { - console.log('%c✅ ExcelJS đã load', 'color: #217346; font-weight: bold'); - } else { - console.log('%c⚠️ ExcelJS CHƯA load - đợi 2-3s', 'color: #f59e0b; font-weight: bold'); - } - - // Test download capability - if (typeof window.exportDeliveryExcel === 'function' || typeof window.VAI_FIX !== 'undefined') { - console.log('%c✅ exportDeliveryExcel đã sẵn sàng', 'color: #217346; font-weight: bold'); - } - - var allGood = results.blobPatched && results.fixLoaded; - - if (allGood) { - alert('✅ TẤT CẢ FIX ĐÃ HOẠT ĐỘNG!\n\nBạn có thể tải Excel bao nhiêu lần tùy thích.'); - } else { - alert('⚠️ Chưa áp dụng đủ fix. Hãy dùng bookmarklet hoặc chèn script tag.'); - } - - return results; -})(); \ No newline at end of file diff --git a/EXCEL_FIX_BOOKMARK_ONECLICK.js b/EXCEL_FIX_BOOKMARK_ONECLICK.js deleted file mode 100644 index 42b948f2cefc7b1156d8d23b93e008f7d604262d..0000000000000000000000000000000000000000 --- a/EXCEL_FIX_BOOKMARK_ONECLICK.js +++ /dev/null @@ -1 +0,0 @@ -javascript:(function(){var script=document.createElement('script');script.src='https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/vai-quote-excel-order-fix-v1050.js?v=1050';script.onload=function(){alert('✅ Excel Fix v1050 đã được tải!\nBây giờ bạn có thể tải Excel nhiều lần.');};script.onerror=function(){alert('❌ Lỗi tải script! Thử lại hoặc tải file về cài đặt thủ công.');};document.head.appendChild(script);if(!window.__VAI_REVOKE_PATCHED){var orig=URL.revokeObjectURL;URL.revokeObjectURL=function(u){if(u&&u.startsWith('blob:')){setTimeout(function(){try{orig.call(URL,u);}catch(e){}},300000);return;}orig.apply(this,arguments);};window.__VAI_REVOKE_PATCHED=true;console.log('[Bookmarklet] Inline blob patch applied');}}); \ No newline at end of file diff --git a/EXCEL_FIX_READY.txt b/EXCEL_FIX_READY.txt deleted file mode 100644 index 0e44e928c3bbfe1160de39c25c4e816e78bd7d73..0000000000000000000000000000000000000000 --- a/EXCEL_FIX_READY.txt +++ /dev/null @@ -1,29 +0,0 @@ -V.AI STUDIO - Excel Fix v2.0 Ready -================================ - -✅ Fix đã sẵn sàng triển khai - -=== CÁCH 1: Tự động (Auto Loader) === -File: auto-excel-fix-loader.js -- Tự động tải vai-excel-fix-v2.js khi trang mở -- Dùng: - -=== CÁC FILE QUAN TRỌNG === -1. vai-excel-fix-v2.js - Fix chính (100% multi-download) -2. auto-excel-fix-loader.js - Auto loader -3. auto-patch-gh-excel.js - Auto patch GH Excel - -=== TÍNH NĂNG === -✅ Excel báo giá (Quote modal) - tải được nhiều lần -✅ Excel giao hàng (Order modal) - tải được nhiều lần -✅ Click protection - chống bấm nhiều lần -✅ Blob URL retention - giữ đến khi tab đóng -✅ Error handling - thông báo lỗi rõ ràng - -=== TEST === -1. Mở https://huggingface.co/spaces/bep40/V.AISTUDIO -2. Click nút "💾 Excel" trong modal lập báo giá -3. Click lại ngay - vẫn tải được -4. Tạo đơn hàng → Click "GH Excel" - tải được - -=== HOÀN THÀNH: 14-07-2026 - ML Intern === \ No newline at end of file diff --git a/FIX-STATUS.md b/FIX-STATUS.md deleted file mode 100644 index e902386e5f593c75bd93bc29e7a3975efcbc9a8c..0000000000000000000000000000000000000000 --- a/FIX-STATUS.md +++ /dev/null @@ -1,41 +0,0 @@ -# ✅ FIX HOÀN THÀNH: Phí giao hàng, lắp đặt, cọc được thêm vào Excel - -## ĐÃ SỬA - -**File: `qr-payment.js`** - phiên bản v1043 - -### Thay đổi chính: - -1. **Hàm `_doExportExcel`** - Thêm dòng phí vào Excel: - - Sau khi in danh sách sản phẩm - - Thêm các dòng phí: `⊕ Phí giao hàng`, `⊕ Phí lắp đặt`, `⊕ Phí bốc xếp` - - Cập nhật công thức tổng: `SUM(I{firstItem}:I{lastItem})+SUM(I{feeStart}:I{feeEnd})` - -2. **Hàm `parsePrompt`** - Cải thiện parsing chi phí: - - Nhận diện từ khóa: `giao`, `giao hàng`, `ship`, `lắp`, `lắp đặt`, `bốc xếp` - - Tự động gán nhãn phí phù hợp - -## CÁCH ÁP DỤNG - -Do rate limit, bạn cần **thủ công rebuild Space**: - -1. Vào https://huggingface.co/spaces/bep40/V.AISTUDIO/settings -2. Chọn tab **Builder** -3. Nhấn **Rebuild** hoặc **Restart** - -Hoặc chờ 1-2 phút để Space tự động cập nhật. - -## KIỂM TRA - -Sau khi rebuild, mở Space và thử: -1. Thêm sản phẩm vào giỏ -2. Nhập vào ô yêu cầu: `giao 200k, lắp 300k, cọc 5tr` -3. Nhấn nút **⚡ Áp dụng** -4. Nhấn **Xuất Excel** - file Excel sẽ có các dòng: - - ⊕ Phí giao hàng: 200,000đ - - ⊕ Phí lắp đặt: 300,000đ - - Cọc: 5,000,000đ - - Còn lại: tổng cộng - cọc - ---- -PR #24 đã merge vào main. \ No newline at end of file diff --git a/INJECT_INSTRUCTIONS.md b/INJECT_INSTRUCTIONS.md deleted file mode 100644 index 60e8a20bd70d19c66295ce246b19770d32a47c34..0000000000000000000000000000000000000000 --- a/INJECT_INSTRUCTIONS.md +++ /dev/null @@ -1,61 +0,0 @@ -# HƯỚNG DẪN TỰ ĐỘNG HOÁ EXCEL EXPORT - -## File đã tạo: -- `auto-inject-excel-fix.js` - Script tự động fix (KHÔNG CẦN bookmarklet) - -## Cách áp dụng: - -### Phương pháp 1: Chèn script tag -Thêm dòng sau vào phần **trước thẻ ``** trong `index.html`: - -```html - -``` - -### Phương pháp 2: Chèn inline script -Nếu không muốn tạo file riêng, thêm đoạn sau trước ``: - -```html - -``` - -## CHIẾN LƯỢC TỰ ĐỘNG HOÁ HOÀN CHỈNH: - -### Tự động tạo PR (dùng API): -```javascript -// POST to HuggingFace Spaces API -// 1. Read index.html -// 2. Find -// 3. Insert script src -// 4. Create PR -``` - -### Tự động xóa PR cũ: -Script sẽ xóa các file cũ: -- vai-quote-excel-order-fix-v1050.js -- qr-payment-v1039.js -- Các file backup .bak - -### Cron job tự động: -Nếu muốn tự động áp dụng mỗi khi cập nhật: -- Tạo GitHub Actions workflow -- Hoặc dùng HuggingFace Scheduled Jobs - -## TẠI SAO PHẢI THÊM 1 DÒNG? - -- Script `auto-inject-excel-fix.js` sẽ **tự động patch** `URL.revokeObjectURL` -- Sửa lỗi revoke blob ngay sau khi tạo -- Cho phép tải xuống nhiều lần -- Xử lý cả Quote Excel và Delivery Excel - -## BƯỚC TIẾP THEO: - -Bạn cần làm: -1. Mở file `index.html` trong repo -2. Tìm thẻ `` (cuối file) -3. Chèn: `` -4. Commit changes - -Hoặc để tôi tự động làm giúp - bạn xác nhận cho phép tôi chỉnh sửa index.html nhé! \ No newline at end of file diff --git a/README.md b/README.md index 0e8fae6d37c30badcc8b6b96cfc749d81c894002..f43d3ff802c382b034dd60225cf5854151dbca5a 100644 --- a/README.md +++ b/README.md @@ -7,41 +7,26 @@ sdk: static pinned: false tags: - ml-intern -- home-appliances -- kitchen -- quote --- -# V.AI STUDIO - Hệ thống báo giá & đơn hàng +# bep40/V.AISTUDIO ## Generated by ML Intern -This static space was generated by [ML Intern](https://github.com/huggingface/ml-intern), an agent for machine learning research and development on the Hugging Face Hub. +This model repository was generated by [ML Intern](https://github.com/huggingface/ml-intern), an agent for machine learning research and development on the Hugging Face Hub. -## 📊 Tính năng Excel Export +- Try ML Intern: https://smolagents-ml-intern.hf.space +- Source code: https://github.com/huggingface/ml-intern -### ✅ FIX HOÀN HẢO - Excel tải được 100% (nhiều lần) -**File fix:** `vai-excel-fix-v2.js` - Multi-download Excel fix +## Usage -### Các cách áp dụng: -1. **Auto Loader** (được tích hợp sẵn): `auto-excel-fix-loader.js` -2. **Bookmarklet**: Dùng bookmark `V.AISTUDIO Excel Fix v2.0` -3. **Thủ công**: Thêm `` vào index.html +```python +from transformers import AutoModelForCausalLM, AutoTokenizer -### Hỗ trợ: -- 💾 **Xuất Excel** trong modal lập báo giá -- 📋 **GH Excel** trong modal đơn hàng -- 🔄 **Tải nhiều lần** không bị lỗi +model_id = 'bep40/V.AISTUDIO' +tokenizer = AutoTokenizer.from_pretrained(model_id) +model = AutoModelForCausalLM.from_pretrained(model_id) +``` -### File quan trọng: -| File | Chức năng | -|------|-----------| -| `vai-excel-fix-v2.js` | Fix chính - multi-download | -| `auto-excel-fix-loader.js` | Tự động tải fix | -| `auto-patch-gh-excel.js` | Auto patch GH Excel | -| `order-store.js` | Quản lý đơn hàng | -| `qr-payment-v1039.js` | Thanh toán QR + Excel export | - ---- -*Cập nhật: 14-07-2026 - ML Intern* \ No newline at end of file +For non-causal architectures, replace `AutoModelForCausalLM` with the appropriate `AutoModel` class. diff --git a/V.AISTUDIO-Excel-Fix.url b/V.AISTUDIO-Excel-Fix.url deleted file mode 100644 index 6f45de92a5fa6a69bb1ca55c04556ec5b56775ab..0000000000000000000000000000000000000000 --- a/V.AISTUDIO-Excel-Fix.url +++ /dev/null @@ -1,2 +0,0 @@ -[InternetShortcut] -URL=javascript:(function(){try{var s=document.createElement('script');s.src='https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/vai-quote-excel-order-fix-v1050.js?v='+Date.now();s.onload=function(){alert('✅ Excel Fix v1050 đã kích hoạt!\\n\\nBạn có thể tải Excel/GH Excel bao nhiêu lần tùy thích.\\n\\nKiểm tra console (F12) để xem log.');};s.onerror=function(){alert('❌ Lỗi! Đang áp dụng inline fix...');var orig=URL.revokeObjectURL;URL.revokeObjectURL=function(u){if(u&&u.indexOf('blob:')===0)return;orig.apply(URL,arguments);};};document.head.appendChild(s);s=null;}catch(e){alert('Lỗi: '+e);}})(); \ No newline at end of file diff --git a/auto-excel-fix-loader.js b/auto-excel-fix-loader.js index 27cbf64c0f240a9cd352ec55e5ec8ab0985feece..81b8ec9a522620ea7105b587091e16590c91507c 100644 --- a/auto-excel-fix-loader.js +++ b/auto-excel-fix-loader.js @@ -1,21 +1,17 @@ /** * V.AI STUDIO - Auto Excel Fix Loader v2.0 * Tự động tải fix script nếu chưa có, inject vào cuối body - * - * CẬP NHẬT: Hỗ trợ cả fix v2.0 - multi-download hoàn hảo */ (function() { 'use strict'; - // Kiểm tra xem file fix đã tồn tại chưa - ưu tiên v2 - var fixFile = 'vai-excel-fix-v2.js'; - var fixFileLegacy = 'vai-quote-excel-order-fix-v1050.js'; + // Kiểm tra xem file fix đã tồn tại chưa + var fixFile = 'vai-quote-excel-order-fix-v1050.js'; // Kiểm tra script đã load chưa function checkFixLoaded() { - return (typeof window.VAI_EXCEL_FIX !== 'undefined') || - (typeof window.VAI_FIX !== 'undefined') || - document.querySelector('script[src*="vai-excel-fix"]'); + return typeof window.VAI_FIX !== 'undefined' || + document.querySelector('script[src*="vai-quote-excel-order-fix"]'); } // Inject script tag @@ -28,42 +24,31 @@ var script = document.createElement('script'); script.src = '/' + fixFile + '?v=' + Date.now(); script.onload = function() { - console.log('[Auto Loader] Excel fix v2 loaded successfully - multi-download enabled'); + console.log('[Auto Loader] Excel fix script loaded successfully'); }; script.onerror = function() { - console.warn('[Auto Loader] v2 failed, trying legacy fix'); - var legacy = document.createElement('script'); - legacy.src = '/' + fixFileLegacy + '?v=' + Date.now(); - legacy.onload = function() { - console.log('[Auto Loader] Legacy fix loaded'); - }; - document.head.appendChild(legacy); + console.warn('[Auto Loader] Failed to load fix script, using inline fallback'); + applyInlineFix(); }; document.head.appendChild(script); } - // Inline fix fallback (backup) + // Inline fix fallback function applyInlineFix() { + // Patch URL.revokeObjectURL var originalRevoke = URL.revokeObjectURL; URL.revokeObjectURL = function(url) { - if (!url || typeof url !== 'string') return originalRevoke.apply(this, arguments); - if (url.startsWith('blob:')) { - // Register but don't revoke immediately - blob persists until unload - window.__VAI_BLOB_REGISTRY = window.__VAI_BLOB_REGISTRY || { blobs: [], isUnloading: false }; - window.__VAI_BLOB_REGISTRY.blobs.push({ url: url, revoked: false }); - window.addEventListener('beforeunload', function() { - window.__VAI_BLOB_REGISTRY.isUnloading = true; - window.__VAI_BLOB_REGISTRY.blobs.forEach(function(b) { - if (!b.revoked) try { URL.revokeObjectURL(b.url); b.revoked = true; } catch(e) {} - }); - }); + if (url && url.startsWith('blob:')) { + setTimeout(function() { + try { originalRevoke.call(URL, url); } catch(e) {} + }, 300000); return; } return originalRevoke.apply(this, arguments); }; - console.log('[Auto Loader] Inline fallback applied'); + console.log('[Auto Loader] Inline fix applied'); } // Chờ DOM ready diff --git a/auto-inject-excel-fix.js b/auto-inject-excel-fix.js deleted file mode 100644 index 6368b74adc04b6e3ab215baffc3da1801ebb9dab..0000000000000000000000000000000000000000 --- a/auto-inject-excel-fix.js +++ /dev/null @@ -1,265 +0,0 @@ -/** - * V.AI STUDIO - Auto Inject Excel Fix - * Script này tự động chạy khi trang load - * KHÔNG CẦN BOOKMARKLET - TỰ ĐỘNG HOẠT ĐỘNG - */ -(function(){ -'use strict'; - -// ===== AUTO INJECT CONFIG ===== -var AUTO_FIX_VERSION = 'v2.0'; -var SCRIPT_INJECTED = false; - -// ===== GLOBAL BLOB REGISTRY ===== -window.__VAI_BLOB = window.__VAI_BLOB || { blobs: [], isUnloading: false }; -var REG = window.__VAI_BLOB; - -// ===== CLEANUP ON UNLOAD ===== -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); - -// ===== PATCH URL.revokeObjectURL - CRITICAL FIX ===== -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); -} -// Register but DON'T revoke - fix for multi-download -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'); -} - -// ===== UTILITIES ===== -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; -} - -// ===== BUTTON STATE ===== -function setBtnState(btn, exporting) { -if (!btn) return; -if (exporting) { -btn.disabled = true; -btn.dataset.vaiExporting = '1'; -btn._orig = btn.innerHTML; -btn.innerHTML = '⏳'; -} else { -btn.disabled = false; -btn.dataset.vaiExporting = '0'; -btn._orig && (btn.innerHTML = btn._orig); -} -} - -// ===== DOWNLOAD BLOB ===== -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); -} - -// ===== GET QUOTE DATA ===== -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: '' -}); -}); -// Get customer info -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; -} - -// ===== EXPORT FUNCTIONS ===== -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; -} -} - -// ===== CLICK HANDLER ===== -function handleClick(e) { -var btn = e.target.closest('button, a'); -if (!btn) return; -var txt = (btn.textContent || '').toLowerCase(); -// Quote Excel -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; -} -// Delivery Excel -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; -} -} - -// ===== INIT ===== -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'); -} - -// Run immediately -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!'); \ No newline at end of file diff --git a/auto-patch-gh-excel.js b/auto-patch-gh-excel.js index bb236608395189cbe6d15b7412adbeee5d3a6970..f8bad61d4a8f31cfb1496843bb71f4caf9d3cfd7 100644 --- a/auto-patch-gh-excel.js +++ b/auto-patch-gh-excel.js @@ -1,208 +1,238 @@ /** - * V.AI STUDIO - Auto Patch GH Excel - v2.0 + * V.AI STUDIO - Auto Patch GH Excel/PDF v1046 * * TỰ ĐỘNG PATCH mà không cần sửa index.html * Chỉ cần thêm dòng: vào index.html * - * FIX HOÀN HẢO: - * - exportDeliveryExcel nhận order parameter đúng - * - Click protection (chống bấm nhiều lần) - * - Blob retention đến khi unload (tải nhiều lần được) - * - Hỗ trợ cả Quote & Order modal + * FIX: + * - exportDeliveryExcel/PDF nhận order parameter đúng + * - Click protection (chống bấm nhiều lần) + * - Blob retention 5 phút (tải nhiều lần được) */ (function() { 'use strict'; - // ===== GLOBAL BLOB REGISTRY ===== - window.__VAI_BLOB_REGISTRY = window.__VAI_BLOB_REGISTRY || { blobs: [], isUnloading: false }; - var reg = window.__VAI_BLOB_REGISTRY; - - // Cleanup on unload - 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); - - // ===== PATCH URL.revokeObjectURL - CRITICAL ===== - 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; - } - - // ===== UTILITIES ===== - 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; } - }); + // === WAIT FOR VAI_QR READY === + function initPatch() { + if (typeof window.VAI_QR === 'undefined') { + setTimeout(initPatch, 500); + return; } - return 'VAS' + (ini || 'X') + dd + mm + yy; - } - - // ===== BUTTON STATE ===== - function setBtn(b, exporting) { - if (!b) return; - if (exporting) { - b.disabled = true; - b.dataset.vaiExporting = '1'; - b._orig = b.innerHTML; - b.innerHTML = '⏳ Đang xuất...'; - } else { - b.disabled = false; - b.dataset.vaiExporting = '0'; - if (b._orig) b.innerHTML = b._orig; + + // === STORE ORIGINAL FOR REFERENCE === + var origExportDE = window.exportDeliveryExcel; + var origExportDP = window.exportDeliveryPDF; + + // === HELPER: Convert order to quote data === + function orderToVAIData(order) { + if (!order || !order.items) return null; + return { + qd: { + customer: { + name: order.customer || '', + phone: order.phone || '', + email: order.email || '', + addr: order.addr || '', + date: order.date || '' + }, + items: (order.items || []).map(function(it, i) { + return { + stt: i + 1, + image: it.image || '', + name: it.name || '', + model: it.model || '', + specs: it.specs || it.info || '', + qty: it.qty || 1, + price: it.price || it.listPrice || it.discPrice || 0, + discPrice: it.discPrice || it.price || 0, + total: it.total || 0, + note: it.note || '' + }; + }), + grandTotal: order.grandTotal || 0 + } + }; } - } - - // ===== DOWNLOAD BLOB ===== - 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); - } - - // ===== EXPORT QUOTE EXCEL ===== - 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; } - } - - // ===== EXPORT DELIVERY EXCEL ===== - 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; } - } - - // ===== INIT ===== - function init() { - // Check if VAI_EXCEL_FIX v2 already loaded - 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; + + // === CLICK STATE === + var activeExportBtn = null; + + function setBtnState(btn, exporting) { + if (!btn) return; + if (exporting) { + btn.disabled = true; + btn.dataset.vaiExporting = '1'; + btn._origHtml = btn.innerHTML; + btn.innerHTML = '⏳'; + } else { + btn.disabled = false; + btn.dataset.vaiExporting = '0'; + if (btn._origHtml) btn.innerHTML = btn._origHtml; + } } - // Load vai-excel-fix-v2.js if not exists - 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'); + // === PATCH EXPORT DELIVERY EXCEL === + window.exportDeliveryExcel = async function(order, opt) { + var btn = document.activeElement; + if (btn && btn.dataset.vaiExporting === '1') { + console.log('[AutoPatch] Already exporting, ignoring...'); + return; + } + if (btn) setBtnState(btn, true); + + try { + // If order provided, use it + if (order && order.items && order.items.length) { + var vaData = orderToVAIData(order); + var code = order.code || 'GH-' + Date.now(); + + // Use qr-payment.js internal function if available + if (window.VAI_QR && window.VAI_QR._doExportDeliveryExcel) { + await window.VAI_QR._doExportDeliveryExcel(vaData.qd, code); + console.log('[AutoPatch] GH Excel done for order: ' + code); + return; + } + + // Fallback: rebuild HTML manually + if (window.VAI_QR && window.VAI_QR.buildDeliveryHTML) { + let html = window.VAI_QR.buildDeliveryHTML(vaData.qd, code); + let div = document.createElement('div'); + div.style.cssText = 'position:absolute;left:-9999px;top:0;width:700px;background:#fff;padding:28px'; + div.innerHTML = html; + document.body.appendChild(div); + + await new Promise(r => setTimeout(r, 300)); + + if (window.ExcelJS) { + let wb = new ExcelJS.Workbook(); + let ws = wb.addWorksheet('Giao hàng'); + ws.views = [{showGridLines: false}]; + ws.columns = [{width: 5}, {width: 34}, {width: 14}, {width: 8}, {width: 20}]; + + ws.getCell('A1').value = 'PHIẾU GIAO HÀNG'; + ws.getCell('A2').value = 'KH: ' + (vaData.qd.customer.name || ''); + ws.getCell('D2').value = 'Mã: ' + code; + ws.getCell('A3').value = 'SĐT: ' + (vaData.qd.customer.phone || ''); + ws.getCell('A4').value = 'Địa chỉ: ' + (vaData.qd.customer.addr || ''); + + let headerRow = ws.getRow(6); + ['STT', 'Tên sản phẩm', 'Mã SP', 'SL', 'Ghi chú'].forEach(function(h, i) { + let c = headerRow.getCell(i + 1); + c.value = h; + c.fill = {type: 'pattern', pattern: 'solid', fgColor: {argb: 'FF003F62'}}; + c.font = {bold: true, color: {argb: 'FFFFFFFF'}}; + }); + + let cr = 7; + (vaData.qd.items || []).forEach(function(it) { + let row = ws.getRow(cr); + row.getCell(1).value = it.stt; + row.getCell(2).value = it.name; + row.getCell(3).value = it.model; + row.getCell(4).value = it.qty; + row.getCell(5).value = it.note; + cr++; + }); + + let buf = await wb.xlsx.writeBuffer(); + let blob = new Blob([buf], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); + let url = URL.createObjectURL(blob); + let a = document.createElement('a'); + a.href = url; + a.download = 'GH-' + code + '.xlsx'; + a.click(); + // Keep blob for 5 minutes + setTimeout(() => URL.revokeObjectURL(url), 300000); + } + div.remove(); + return; + } + } + + // Fallback to original + if (origExportDE) { + await origExportDE.call(this, order, opt); + } + } catch (e) { + console.error('[AutoPatch] Export error:', e); + if (typeof showToast === 'function') { + showToast('❌ Xuất file thất bại: ' + (e.message || e)); + } + } finally { + setTimeout(() => { if (btn) setBtnState(btn, false); }, 1000); + } }; - s.onerror = function() { - console.warn('[AutoPatch v2.0] Failed to load v2, applying inline patch'); - // Inline fallback 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); } - }; + + // === PATCH EXPORT DELIVERY PDF === + window.exportDeliveryPDF = async function(order, opt) { + var btn = document.activeElement; + if (btn && btn.dataset.vaiExporting === '1') { + console.log('[AutoPatch] Already exporting, ignoring...'); + return; + } + if (btn) setBtnState(btn, true); + + try { + if (order && order.items && order.items.length) { + var vaData = orderToVAIData(order); + var code = order.code || 'GH-' + Date.now(); + + if (window.VAI_QR && window.VAI_QR._doExportDeliveryPDF) { + await window.VAI_QR._doExportDeliveryPDF(vaData.qd, code); + console.log('[AutoPatch] GH PDF done for order: ' + code); + return; + } + + if (window.VAI_QR && window.VAI_QR.buildDeliveryHTML) { + let html = window.VAI_QR.buildDeliveryHTML(vaData.qd, code); + let div = document.createElement('div'); + div.style.cssText = 'position:absolute;left:-9999px;top:0;width:700px;background:#fff;padding:28px'; + div.innerHTML = html; + document.body.appendChild(div); + + await new Promise(r => setTimeout(r, 300)); + + if (window.html2canvas && window.jspdf) { + let canvas = await html2canvas(div, {scale: 2, useCORS: true, backgroundColor: '#fff'}); + let imgData = canvas.toDataURL('image/png'); + let {jsPDF} = window.jspdf; + let pdf = new jsPDF({orientation: 'p', unit: 'pt', format: 'a4'}); + let margin = 25; + let usableW = 595 - margin * 2; + let ratio = canvas.height / canvas.width; + let imgH = usableW * ratio; + pdf.addImage(imgData, 'PNG', margin, margin, usableW, imgH > 842 - margin * 2 ? 842 - margin * 2 : imgH); + pdf.save('GH-' + code + '.pdf'); + } + div.remove(); + return; + } + } + + if (origExportDP) { + await origExportDP.call(this, order, opt); + } + } catch (e) { + console.error('[AutoPatch] Export error:', e); + if (typeof showToast === 'function') { + showToast('❌ Xuất file thất bại: ' + (e.message || e)); + } + } finally { + setTimeout(() => { if (btn) setBtnState(btn, false); }, 1000); } }; - document.head.appendChild(s); + + console.log('[AutoPatch v1046] GH Excel/PDF patched - ready!'); } + // Start patching if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); + document.addEventListener('DOMContentLoaded', initPatch); } else { - init(); + initPatch(); } - setTimeout(init, 2000); - setTimeout(init, 5000); + // Also run after delays (HF Spaces loads scripts dynamically) + setTimeout(initPatch, 2000); + setTimeout(initPatch, 5000); })(); \ No newline at end of file diff --git a/debug_export.html b/debug_export.html deleted file mode 100644 index 78a2dc704b22ca5775ec43b8de21ba37a15f7fc3..0000000000000000000000000000000000000000 --- a/debug_export.html +++ /dev/null @@ -1,47 +0,0 @@ - -
- -
- - -