Delete *.js, *.json, *.css, *.html
Browse files- ai-search-fix.js +0 -248
- ai-search.js +0 -138
- aistudio2.js +0 -0
- export-functions.js +0 -149
- grob-img-fix.js +0 -76
- grob-price-fix.js +0 -68
- index-updated.html +0 -56
- index.html +0 -0
- order-store.js +0 -346
- products_konox.json +0 -4
- products_with_slugs.json +0 -17
- qr-payment-v1040.js +0 -118
- quote-ui.js +0 -149
- san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx.html +0 -49
- san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx/index.html +0 -29
- san-pham/malloca-bep-tu-doi-750h/index.html +0 -22
- show-order-picker-v1046.js +0 -35
- test-quote.html +0 -80
- vai-complete-quote.js +0 -278
- vai-fix-order-download-v1042.js +0 -163
- vai-fix.js +0 -335
- vai-patch-v1047.js +0 -205
ai-search-fix.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* FIX: AI Search Button - doAISearch()
|
| 3 |
-
* =====================================
|
| 4 |
-
* Root cause: doAISearch() in index.html depended on window._vaiSearchContext
|
| 5 |
-
* from search-plus-boot.js?v=27 which DOES NOT EXIST in the repo (404).
|
| 6 |
-
*
|
| 7 |
-
* Fix: Rewrite doAISearch() to use window.aiSearch() from ai-search.js which is
|
| 8 |
-
* properly loaded and contains full NL search logic.
|
| 9 |
-
*
|
| 10 |
-
* Additional fix: Handle multi-product search (comma/semicolon separated codes).
|
| 11 |
-
* Critical fix: All references to D must use window.D since let D in index.html
|
| 12 |
-
* does NOT create a global property on window object.
|
| 13 |
-
*/
|
| 14 |
-
|
| 15 |
-
window.doAISearch = function() {
|
| 16 |
-
var input = document.getElementById('aiSearch');
|
| 17 |
-
var resultsDiv = document.getElementById('aiResults');
|
| 18 |
-
if (!input || !resultsDiv) return;
|
| 19 |
-
|
| 20 |
-
var query = input.value.trim();
|
| 21 |
-
if (!query || query.length < 2) {
|
| 22 |
-
resultsDiv.style.display = 'block';
|
| 23 |
-
resultsDiv.innerHTML = '💡 Nhập mã SP hoặc mô tả: "bếp từ Grob dưới 10tr", "máy hút mùi Malloca"';
|
| 24 |
-
return;
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
resultsDiv.style.display = 'block';
|
| 28 |
-
resultsDiv.innerHTML = '⏳ Đang tìm...';
|
| 29 |
-
|
| 30 |
-
// Wait for data to be ready
|
| 31 |
-
function getD() { return (typeof window.D !== 'undefined' && window.D.length > 0) ? window.D : null; }
|
| 32 |
-
|
| 33 |
-
// Multi-product search: if query contains comma/semicolon, search by product codes
|
| 34 |
-
var queries = query.split(/[,;]+/).map(function(c) { return c.trim(); }).filter(function(c) { return c.length >= 2; });
|
| 35 |
-
|
| 36 |
-
var D = getD();
|
| 37 |
-
|
| 38 |
-
if (queries.length > 1 && D) {
|
| 39 |
-
// Multi-code search mode
|
| 40 |
-
var allResults = [];
|
| 41 |
-
var normSimple = function(s) { return String(s || '').toLowerCase().replace(/[.\/\-]/g, ''); };
|
| 42 |
-
|
| 43 |
-
queries.forEach(function(q) {
|
| 44 |
-
var qNorm = normSimple(q);
|
| 45 |
-
for (var i = 0; i < D.length && allResults.length < 30; i++) {
|
| 46 |
-
var p = D[i];
|
| 47 |
-
if (!p) continue;
|
| 48 |
-
var nameField = (p.name || '').toLowerCase();
|
| 49 |
-
var modField = (p.mod || '').toLowerCase();
|
| 50 |
-
var skuField = normSimple(p.sku || '');
|
| 51 |
-
var modelField = (p.model || '').toLowerCase();
|
| 52 |
-
var slugField = (p.slug || '').toLowerCase();
|
| 53 |
-
|
| 54 |
-
if (skuField === qNorm ||
|
| 55 |
-
modField.replace(/[.\/\- ]/g, '').includes(qNorm) ||
|
| 56 |
-
nameField.replace(/[.\/\- ]/g, '').includes(qNorm) ||
|
| 57 |
-
slugField.replace(/[.\/\- ]/g, '').includes(qNorm)) {
|
| 58 |
-
allResults.push(p);
|
| 59 |
-
}
|
| 60 |
-
}
|
| 61 |
-
});
|
| 62 |
-
|
| 63 |
-
if (allResults.length > 0) {
|
| 64 |
-
_renderMultiCodeResults(allResults, resultsDiv, query);
|
| 65 |
-
return;
|
| 66 |
-
}
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
if (typeof window.aiSearch === 'function' && D) {
|
| 70 |
-
var aiToken = (window.huggingface && window.huggingface.variables && window.huggingface.variables.VAISTUDIO) || '';
|
| 71 |
-
window.aiSearch(query, D, aiToken).then(function(aiResult) {
|
| 72 |
-
if (!aiResult || (!aiResult.results.length && !aiResult.aiAnswer)) {
|
| 73 |
-
_fallbackAISearch(query, resultsDiv);
|
| 74 |
-
return;
|
| 75 |
-
}
|
| 76 |
-
_renderAIResultsToDiv(aiResult, resultsDiv, query);
|
| 77 |
-
}).catch(function(err) {
|
| 78 |
-
_fallbackAISearch(query, resultsDiv);
|
| 79 |
-
console.error('AI Search error:', err);
|
| 80 |
-
});
|
| 81 |
-
} else {
|
| 82 |
-
_fallbackAISearch(query, resultsDiv);
|
| 83 |
-
}
|
| 84 |
-
};
|
| 85 |
-
|
| 86 |
-
// Render results for multi-product code search
|
| 87 |
-
function _renderMultiCodeResults(products, container, query) {
|
| 88 |
-
var html = '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><i class="fas fa-search" style="margin-right:4px"></i><strong>' + products.length + '</strong> sản phẩm tìm được cho "<strong>' + query + '</strong>"</div>';
|
| 89 |
-
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">';
|
| 90 |
-
|
| 91 |
-
products.forEach(function(p, i) {
|
| 92 |
-
var idx = window.D ? window.D.indexOf(p) : -1;
|
| 93 |
-
var brandColor = p.brand === 'Eurogold' ? '#c8102e' : (p.brand === 'Grob' ? '#2e7d32' : '#003f62');
|
| 94 |
-
var imgSrc = p.image || p.i || '';
|
| 95 |
-
var priceStr = p.price || (p.pn ? p.pn : 'Liên hệ');
|
| 96 |
-
var catStr = p.cat || '';
|
| 97 |
-
var catIcon = p.catIcon || 'fa-tag';
|
| 98 |
-
|
| 99 |
-
html += '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer;transition-delay:' + Math.min(i * 20, 300) + 'ms">' +
|
| 100 |
-
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy" onerror="this.style.display=\'none\'">' +
|
| 101 |
-
'<span class="pi-badge"><i class="fas ' + catIcon + '"></i> ' + catStr + '</span>' +
|
| 102 |
-
'<span style="position:absolute;top:8px;right:8px;background:' + brandColor + ';color:#fff;padding:3px 7px;border-radius:5px;font-size:.55rem;font-weight:700">' + (p.brand || '') + '</span></div>' +
|
| 103 |
-
'<div class="pb" style="padding:10px"><div style="font-size:.62rem;font-weight:600;color:' + brandColor + ';letter-spacing:.5px;margin-bottom:3px">' + (p.sku || p.mod || p.model || '') + '</div>' +
|
| 104 |
-
'<div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 105 |
-
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span><i class="fas fa-chevron-right"></i></div></div></div>';
|
| 106 |
-
});
|
| 107 |
-
|
| 108 |
-
html += '</div>';
|
| 109 |
-
container.innerHTML = html;
|
| 110 |
-
container.style.display = 'block';
|
| 111 |
-
|
| 112 |
-
if (window.requestAnimationFrame) {
|
| 113 |
-
requestAnimationFrame(function() {
|
| 114 |
-
var fadeEls = container.querySelectorAll('.fade');
|
| 115 |
-
for (var j = 0; j < fadeEls.length; j++) {
|
| 116 |
-
fadeEls[j].classList.add('vis');
|
| 117 |
-
}
|
| 118 |
-
});
|
| 119 |
-
}
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
function _renderAIResultsToDiv(aiResult, container, query) {
|
| 123 |
-
var badges = '';
|
| 124 |
-
if (aiResult.categories && aiResult.categories.length > 0) {
|
| 125 |
-
badges += '<span style="display:inline-block;background:rgba(0,63,98,.08);color:#003f62;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-layer-group" style="margin-right:4px"></i>' + aiResult.categories.map(function(c) { return c.replace(/_/g, ' '); }).join(', ') + '</span>';
|
| 126 |
-
}
|
| 127 |
-
if (aiResult.constraints && aiResult.constraints.length > 0) {
|
| 128 |
-
aiResult.constraints.forEach(function(c) {
|
| 129 |
-
badges += '<span style="display:inline-block;background:rgba(219,152,21,.15);color:#8a6d0e;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-sliders" style="margin-right:4px"></i>' + c.label + '</span>';
|
| 130 |
-
});
|
| 131 |
-
}
|
| 132 |
-
if (aiResult.budget && aiResult.budget > 0) {
|
| 133 |
-
badges += '<span style="display:inline-block;background:rgba(46,125,50,.1);color:#2e7d32;padding:4px 10px;border-radius:20px;font-size:.7rem;font-weight:600;margin-right:6px;margin-bottom:6px"><i class="fas fa-wallet" style="margin-right:4px"></i>Tối đa ' + (aiResult.budget / 1000000).toFixed(0) + ' triệu</span>';
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
var aiHtml = '';
|
| 137 |
-
if (aiResult.aiAnswer) {
|
| 138 |
-
aiHtml = '<div style="background:linear-gradient(135deg,rgba(0,63,98,.04),rgba(219,152,21,.06));border-radius:16px;padding:16px;margin-bottom:16px;border:2px solid #e2e8f0"><div style="display:flex;align-items:center;gap:8px;margin-bottom:10px"><div style="width:32px;height:32px;border-radius:50%;background:#003f62;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.8rem"><i class="fas fa-robot"></i></div><span style="font-weight:700;color:#003f62;font-size:.85rem">V.AI STUDIO trả lời</span></div><div style="font-size:.85rem;color:#0f172a;line-height:1.7">' + aiResult.aiAnswer + '</div></div>';
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
var comboHtml = '';
|
| 142 |
-
if (aiResult.combos && aiResult.combos.length > 0) {
|
| 143 |
-
comboHtml = '<div style="background:linear-gradient(135deg,rgba(219,152,21,.06),rgba(46,125,50,.04));border-radius:16px;padding:16px;margin-bottom:16px;border:2px solid rgba(219,152,21,.2)"><div style="display:flex;align-items:center;gap:8px;margin-bottom:12px"><div style="width:32px;height:32px;border-radius:50%;background:#db9815;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.8rem"><i class="fas fa-gift"></i></div><span style="font-weight:700;color:#0f172a;font-size:.85rem">Gợi ý combo trong ngân sách</span></div>';
|
| 144 |
-
aiResult.combos.forEach(function(combo, i) {
|
| 145 |
-
var totalStr = combo.total > 0 ? (Number(combo.total).toLocaleString('vi-VN').replace(/,/g, '.') + 'đ') : 'Liên hệ';
|
| 146 |
-
comboHtml += '<div style="background:#fff;border-radius:12px;padding:12px;margin-bottom:8px;border:1px solid #e2e8f0"><div style="font-weight:600;font-size:.8rem;color:#003f62;margin-bottom:6px">Combo ' + (i + 1) + ' — Tổng: ' + totalStr + '</div>';
|
| 147 |
-
combo.items.forEach(function(item) {
|
| 148 |
-
var p = item.p;
|
| 149 |
-
comboHtml += '<div style="display:flex;align-items:center;gap:8px;padding:5px 0;font-size:.76rem;color:#64748b"><i class="fas fa-check-circle" style="color:#2e7d32;font-size:.6rem"></i>' + p.name + ' <span style="color:#003f62;font-weight:600;margin-left:auto">' + (p.price || 'LH') + '</span></div>';
|
| 150 |
-
});
|
| 151 |
-
comboHtml += '</div>';
|
| 152 |
-
});
|
| 153 |
-
comboHtml += '</div>';
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
var items = aiResult.results || [];
|
| 157 |
-
var html = '<div style="margin-bottom:12px">' + badges + '</div>' + aiHtml + comboHtml;
|
| 158 |
-
|
| 159 |
-
if (items.length > 0) {
|
| 160 |
-
html += '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><i class="fas fa-search" style="margin-right:4px"></i><strong>' + items.length + '</strong> kết quả cho "<strong>' + query + '</strong>"</div>';
|
| 161 |
-
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">';
|
| 162 |
-
html += items.map(function(r, i) {
|
| 163 |
-
var p = r.p;
|
| 164 |
-
var idx = (r.idx !== undefined && r.idx >= 0) ? r.idx : (window.D ? window.D.indexOf(p) : -1);
|
| 165 |
-
var brandColor = p.brand === 'Eurogold' ? '#c8102e' : (p.brand === 'Grob' ? '#2e7d32' : '#003f62');
|
| 166 |
-
var labels = (r.labels || []).map(function(l) {
|
| 167 |
-
return '<span style="display:inline-block;background:rgba(0,63,98,.06);padding:2px 6px;border-radius:4px;font-size:.58rem;color:#003f62;margin-right:3px;margin-bottom:3px">' + l + '</span>';
|
| 168 |
-
}).join('');
|
| 169 |
-
var imgSrc = p.image || p.i || '';
|
| 170 |
-
var priceStr = p.price || (window.formatPrice ? window.formatPrice(p.priceNum || p.pn) : (p.pn ? p.pn : 'Liên hệ'));
|
| 171 |
-
var catStr = p.cat || '';
|
| 172 |
-
var catIcon = p.catIcon || 'fa-tag';
|
| 173 |
-
|
| 174 |
-
return '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer;transition-delay:' + Math.min(i * 20, 300) + 'ms">' +
|
| 175 |
-
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy" onerror="this.style.display=\'none\'">' +
|
| 176 |
-
'<span class="pi-badge"><i class="fas ' + catIcon + '"></i> ' + catStr + '</span>' +
|
| 177 |
-
'<span style="position:absolute;top:8px;right:8px;background:' + brandColor + ';color:#fff;padding:3px 7px;border-radius:5px;font-size:.55rem;font-weight:700">' + (p.brand || '') + '</span></div>' +
|
| 178 |
-
'<div class="pb" style="padding:10px"><div style="font-size:.62rem;font-weight:600;color:' + brandColor + ';letter-spacing:.5px;margin-bottom:3px">' + (p.model || p.brand || '') + '</div>' +
|
| 179 |
-
'<div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 180 |
-
(labels ? '<div style="margin-top:4px">' + labels + '</div>' : '') +
|
| 181 |
-
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span><i class="fas fa-chevron-right"></i></div></div></div>';
|
| 182 |
-
}).join('');
|
| 183 |
-
html += '</div>';
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
container.innerHTML = html;
|
| 187 |
-
container.style.display = 'block';
|
| 188 |
-
|
| 189 |
-
if (window.requestAnimationFrame) {
|
| 190 |
-
requestAnimationFrame(function() {
|
| 191 |
-
var fadeEls = container.querySelectorAll('.fade');
|
| 192 |
-
for (var j = 0; j < fadeEls.length; j++) {
|
| 193 |
-
fadeEls[j].classList.add('vis');
|
| 194 |
-
}
|
| 195 |
-
});
|
| 196 |
-
}
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
function _fallbackAISearch(query, container) {
|
| 200 |
-
var D = (typeof window.D !== 'undefined') ? window.D : null;
|
| 201 |
-
if (!D || !D.length) {
|
| 202 |
-
container.innerHTML = '⏳ Đang tải dữ liệu sản phẩm... Vui lòng chờ.';
|
| 203 |
-
return;
|
| 204 |
-
}
|
| 205 |
-
var qNorm = query.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
| 206 |
-
var results = [];
|
| 207 |
-
for (var i = 0; i < D.length && results.length < 20; i++) {
|
| 208 |
-
var p = D[i];
|
| 209 |
-
if (!p) continue;
|
| 210 |
-
var nameNorm = (p.name || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
| 211 |
-
var skuNorm = (p.sku || p.mod || '').toLowerCase();
|
| 212 |
-
var catNorm = (p.cat || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
| 213 |
-
var brandNorm = (p.brand || '').toLowerCase();
|
| 214 |
-
if (nameNorm.indexOf(qNorm) !== -1 ||
|
| 215 |
-
(skuNorm && skuNorm.indexOf(qNorm) !== -1) ||
|
| 216 |
-
(catNorm && catNorm.indexOf(qNorm) !== -1) ||
|
| 217 |
-
(brandNorm && brandNorm.indexOf(qNorm) !== -1) ||
|
| 218 |
-
(p._idx && typeof p._idx === 'string' && p._idx.toLowerCase().indexOf(qNorm) !== -1)) {
|
| 219 |
-
results.push(p);
|
| 220 |
-
}
|
| 221 |
-
}
|
| 222 |
-
if (!results.length) {
|
| 223 |
-
container.innerHTML = '❌ Không tìm thấy "' + query + '". Thử: "bếp từ", "máy hút mùi", "Malloca", "Eurogold"';
|
| 224 |
-
return;
|
| 225 |
-
}
|
| 226 |
-
container.innerHTML = '<div style="font-size:.78rem;color:#64748b;margin-bottom:10px"><strong>' + results.length + '</strong> kết quả</div>' +
|
| 227 |
-
'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px">' +
|
| 228 |
-
results.map(function(p) {
|
| 229 |
-
var idx = window.D ? window.D.indexOf(p) : -1;
|
| 230 |
-
var brandColor = p.brand === 'Eurogold' ? '#c8102e' : (p.brand === 'Grob' ? '#2e7d32' : '#003f62');
|
| 231 |
-
var imgSrc = p.image || p.i || '';
|
| 232 |
-
var priceStr = p.price || (p.pn ? p.pn : 'Liên hệ');
|
| 233 |
-
return '<div class="pc fade" onclick="showDetail(' + idx + ')" style="cursor:pointer">' +
|
| 234 |
-
'<div class="pi" style="height:150px"><img src="' + imgSrc + '" alt="' + p.name + '" loading="lazy" onerror="this.style.display=\'none\'"></div>' +
|
| 235 |
-
'<div class="pb" style="padding:10px"><div style="font-size:.62rem;font-weight:600;color:' + brandColor + ';margin-bottom:3px">' + (p.brand || '') + '</div>' +
|
| 236 |
-
'<div class="pn" style="font-size:.76rem">' + p.name + '</div>' +
|
| 237 |
-
'<div class="pf" style="margin-top:6px"><span class="pp" style="font-size:.92rem">' + priceStr + '</span></div></div></div>';
|
| 238 |
-
}).join('') + '</div>';
|
| 239 |
-
|
| 240 |
-
if (window.requestAnimationFrame) {
|
| 241 |
-
requestAnimationFrame(function() {
|
| 242 |
-
var fadeEls = container.querySelectorAll('.fade');
|
| 243 |
-
for (var j = 0; j < fadeEls.length; j++) {
|
| 244 |
-
fadeEls[j].classList.add('vis');
|
| 245 |
-
}
|
| 246 |
-
});
|
| 247 |
-
}
|
| 248 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ai-search.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* AI Search - Semantic search with budget extraction
|
| 3 |
-
* Provides natural language product search for Vietnamese queries
|
| 4 |
-
*/
|
| 5 |
-
|
| 6 |
-
window.aiSearch = async function(query, products, token) {
|
| 7 |
-
if (!products || !products.length) {
|
| 8 |
-
return { results: [], aiAnswer: null, categories: [], constraints: [], budget: 0, combos: [] };
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
const norm = (s) => String(s || '').toLowerCase()
|
| 12 |
-
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
| 13 |
-
.replace(/[đĐ]/g, 'd');
|
| 14 |
-
|
| 15 |
-
const q = norm(query);
|
| 16 |
-
let budget = 0;
|
| 17 |
-
let categories = [];
|
| 18 |
-
let constraints = [];
|
| 19 |
-
let combos = [];
|
| 20 |
-
|
| 21 |
-
// Extract budget constraint (triệu/nghìn/k/đồng)
|
| 22 |
-
const budgetPatterns = [
|
| 23 |
-
/(dưới|duoi)\s*([\d,.]+)\s*(triệu|nghin|nghìn|k|đồng)/gi,
|
| 24 |
-
/(tối đa)\s*([\d,.]+)\s*(triệu|nghin|nghìn|k|đồng)/gi,
|
| 25 |
-
/([\d,.]+)\s*(triệu|nghin|nghìn|k|đồng)/gi
|
| 26 |
-
];
|
| 27 |
-
|
| 28 |
-
for (const pattern of budgetPatterns) {
|
| 29 |
-
const match = query.replace(/[đĐ]/g, 'd').match(pattern);
|
| 30 |
-
if (match) {
|
| 31 |
-
const amt = parseFloat(match[0].replace(/[^\d,.]/g, '').replace(',', '.'));
|
| 32 |
-
const unit = match[0].toLowerCase().match(/(triệu|nghin|nghìn|k|đồng)/g);
|
| 33 |
-
if (unit) {
|
| 34 |
-
if (unit[0] === 'triệu') budget = amt * 1000000;
|
| 35 |
-
else if (unit[0] === 'nghìn' || unit[0] === 'nghin' || unit[0] === 'k') budget = amt * 1000;
|
| 36 |
-
else budget = amt;
|
| 37 |
-
}
|
| 38 |
-
constraints.push({ label: 'Ngân sách: ' + match[0].trim() });
|
| 39 |
-
break;
|
| 40 |
-
}
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
// Category keywords
|
| 44 |
-
const catMap = {
|
| 45 |
-
'bep': 'Bếp',
|
| 46 |
-
'bep-tu': 'Bếp từ',
|
| 47 |
-
'bep-gas': 'Bếp gas',
|
| 48 |
-
'hut-mui': 'Máy hút mùi',
|
| 49 |
-
'hut-khoi': 'Máy hút khói',
|
| 50 |
-
'chau-rua': 'Chậu rửa',
|
| 51 |
-
'voi-rua': 'Vòi rửa',
|
| 52 |
-
'lo-nuong': 'Lò nướng',
|
| 53 |
-
'tu-lanh': 'Tủ lạnh',
|
| 54 |
-
'may-rua-chen': 'Máy rửa chén',
|
| 55 |
-
'may-lam-sach': 'Máy làm sạch',
|
| 56 |
-
'gia-bo': 'Kệ bếp',
|
| 57 |
-
'gia-dung': 'Gia đụng'
|
| 58 |
-
};
|
| 59 |
-
|
| 60 |
-
Object.keys(catMap).forEach(key => {
|
| 61 |
-
if (q.includes(key) || query.toLowerCase().includes(key)) {
|
| 62 |
-
categories.push(catMap[key]);
|
| 63 |
-
}
|
| 64 |
-
});
|
| 65 |
-
|
| 66 |
-
// Search logic with scoring
|
| 67 |
-
let results = [];
|
| 68 |
-
for (let i = 0; i < products.length && results.length < 30; i++) {
|
| 69 |
-
const p = products[i];
|
| 70 |
-
let score = 0;
|
| 71 |
-
const name = norm(p.name || '');
|
| 72 |
-
const sku = norm(p.sku || p.mod || '');
|
| 73 |
-
const brand = norm(p.brand || '');
|
| 74 |
-
const cat = norm(p.cat || '');
|
| 75 |
-
const summary = norm(p.summary || '');
|
| 76 |
-
|
| 77 |
-
// Exact/partial matches
|
| 78 |
-
if (name.includes(q)) score += 10;
|
| 79 |
-
if (sku.includes(q)) score += 8;
|
| 80 |
-
if (brand.includes(q)) score += 6;
|
| 81 |
-
if (cat.includes(q)) score += 4;
|
| 82 |
-
if (summary.includes(q)) score += 3;
|
| 83 |
-
|
| 84 |
-
// Budget filter
|
| 85 |
-
if (budget > 0 && p.priceNum && p.priceNum > budget) {
|
| 86 |
-
score = 0;
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
// Word-by-word matching
|
| 90 |
-
const words = q.split(/\s+/).filter(w => w.length > 1);
|
| 91 |
-
words.forEach(w => {
|
| 92 |
-
if (name.includes(w)) score += 2;
|
| 93 |
-
if (sku.includes(w)) score += 1;
|
| 94 |
-
if (brand.includes(w)) score += 1;
|
| 95 |
-
if (cat.includes(w)) score += 1;
|
| 96 |
-
});
|
| 97 |
-
|
| 98 |
-
if (score > 0) {
|
| 99 |
-
const labels = [];
|
| 100 |
-
if (p.priceNum && budget > 0 && p.priceNum <= budget) {
|
| 101 |
-
labels.push('Trong ngân sách');
|
| 102 |
-
}
|
| 103 |
-
results.push({ p, idx: i, score, labels });
|
| 104 |
-
}
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
// Sort by score desc
|
| 108 |
-
results.sort((a, b) => b.score - a.score);
|
| 109 |
-
|
| 110 |
-
// Generate combos based on budget
|
| 111 |
-
if (budget > 0 && results.length >= 2) {
|
| 112 |
-
let comboTotal = 0;
|
| 113 |
-
let comboItems = [];
|
| 114 |
-
for (let i = 0; i < results.length && comboTotal <= budget; i++) {
|
| 115 |
-
comboItems.push({ p: results[i].p, idx: results[i].idx });
|
| 116 |
-
}
|
| 117 |
-
if (comboItems.length >= 2) {
|
| 118 |
-
combos.push({
|
| 119 |
-
items: comboItems,
|
| 120 |
-
total: comboItems.reduce((s, it) => s + (it.p.priceNum || 0), 0)
|
| 121 |
-
});
|
| 122 |
-
}
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
// Generate AI response
|
| 126 |
-
let aiAnswer = '';
|
| 127 |
-
if (results.length > 0) {
|
| 128 |
-
aiAnswer = `Tìm thấy ${results.length} sản phẩm phù hợp.`;
|
| 129 |
-
if (categories.length) aiAnswer += ` Danh mục: ${categories.join(', ')}.`;
|
| 130 |
-
if (budget > 0) aiAnswer += ` Ngân sách tối đa: ${(budget/1000000).toFixed(1)} triệu.`;
|
| 131 |
-
} else {
|
| 132 |
-
aiAnswer = 'Không tìm thấy sản phẩm nào. Vui lòng thử từ khóa khác như: "bếp từ", "máy hút mùi", "Malloca", "Eurogold".';
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
return { results, aiAnswer, categories, constraints, budget, combos };
|
| 136 |
-
};
|
| 137 |
-
|
| 138 |
-
console.log('AI Search loaded');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
aistudio2.js
DELETED
|
File without changes
|
export-functions.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
// Export functions for quote system
|
| 2 |
-
// These functions are required by quote-ui.js
|
| 3 |
-
|
| 4 |
-
// --- Delivery Export Functions ---
|
| 5 |
-
function exportDeliveryExcel() {
|
| 6 |
-
console.log('[V.AI STUDIO] Exporting delivery to Excel...');
|
| 7 |
-
if (!window.cartItems || !window.cartItems.length) {
|
| 8 |
-
alert('❌ Giỏ hàng trống!');
|
| 9 |
-
return;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
// Create CSV content
|
| 13 |
-
let csv = 'STT,Tên sản phẩm,Mã SP,Số lượng,Đơn giá,Thành tiền,Ghi chú\n';
|
| 14 |
-
let total = 0;
|
| 15 |
-
window.cartItems.forEach((item, idx) => {
|
| 16 |
-
let price = item.priceNum || 0;
|
| 17 |
-
let qty = item.qty || 1;
|
| 18 |
-
let subtotal = price * qty;
|
| 19 |
-
total += subtotal;
|
| 20 |
-
csv += `${idx + 1},"${item.name}","${item.sku || ''}",${qty},${price},${subtotal},"${item.note || ''}"\n`;
|
| 21 |
-
});
|
| 22 |
-
csv += `,,,,,,"${total}",Tổng cộng\n`;
|
| 23 |
-
|
| 24 |
-
// Download
|
| 25 |
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
| 26 |
-
const url = URL.createObjectURL(blob);
|
| 27 |
-
const a = document.createElement('a');
|
| 28 |
-
a.href = url;
|
| 29 |
-
a.download = `giao-hang-${new Date().toISOString().slice(0,10)}.csv`;
|
| 30 |
-
a.click();
|
| 31 |
-
URL.revokeObjectURL(url);
|
| 32 |
-
alert('✅ Đã xuất file Excel (CSV) thành công!');
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
function exportDeliveryPDF() {
|
| 36 |
-
console.log('[V.AI STUDIO] Exporting delivery to PDF...');
|
| 37 |
-
if (!window.cartItems || !window.cartItems.length) {
|
| 38 |
-
alert('❌ Giỏ hàng trống!');
|
| 39 |
-
return;
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
// Simple PDF export using jsPDF if available
|
| 43 |
-
if (typeof jsPDF !== 'undefined') {
|
| 44 |
-
const doc = new jsPDF();
|
| 45 |
-
doc.setFontSize(16);
|
| 46 |
-
doc.text('PHIẾU GIAO HÀNG', 14, 20);
|
| 47 |
-
doc.setFontSize(10);
|
| 48 |
-
doc.text(`Ngày: ${new Date().toLocaleDateString('vi-VN')}`, 14, 30);
|
| 49 |
-
doc.text(`Tổng số: ${window.cartItems.length} sản phẩm`, 14, 36);
|
| 50 |
-
|
| 51 |
-
let y = 50;
|
| 52 |
-
window.cartItems.forEach((item, idx) => {
|
| 53 |
-
if (y > 280) { doc.addPage(); y = 20; }
|
| 54 |
-
doc.text(`${idx + 1}. ${item.name}`, 14, y);
|
| 55 |
-
y += 7;
|
| 56 |
-
});
|
| 57 |
-
|
| 58 |
-
doc.save(`giao-hang-${new Date().toISOString().slice(0,10)}.pdf`);
|
| 59 |
-
alert('✅ Đã xuất file PDF thành công!');
|
| 60 |
-
} else {
|
| 61 |
-
alert('⚠️ Thư viện jsPDF chưa được tải. Vui lòng thử xuất Excel.');
|
| 62 |
-
}
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
// --- Quote Export Functions ---
|
| 66 |
-
function exportExcel() {
|
| 67 |
-
console.log('[V.AI STUDIO] Exporting quote to Excel...');
|
| 68 |
-
const table = document.getElementById('quoteTableBody');
|
| 69 |
-
if (!table || !table.rows.length) {
|
| 70 |
-
alert('❌ Báo giá trống!');
|
| 71 |
-
return;
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
let csv = 'STT,Hình ảnh,Tên sản phẩm,Mã SP,Thông tin,SL,Đơn giá,Đơn giá CK,Thành tiền,Ghi chú\n';
|
| 75 |
-
// Implementation here...
|
| 76 |
-
alert('✅ Đã xuất báo giá Excel (đang triển khai...)');
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
function exportPDF() {
|
| 80 |
-
console.log('[V.AI STUDIO] Exporting quote to PDF...');
|
| 81 |
-
alert('✅ Đã xuất báo giá PDF (đang triển khai...)');
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
function printQuotation() {
|
| 85 |
-
console.log('[V.AI STUDIO] Printing quotation...');
|
| 86 |
-
window.print();
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
function shareQuoteImage() {
|
| 90 |
-
console.log('[V.AI STUDIO] Sharing quote as image...');
|
| 91 |
-
alert('✅ Chia sẻ ảnh báo giá (đang triển khai...)');
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
function shareQuoteLink() {
|
| 95 |
-
console.log('[V.AI STUDIO] Sharing quote link...');
|
| 96 |
-
const url = window.location.href;
|
| 97 |
-
if (navigator.share) {
|
| 98 |
-
navigator.share({ title: 'Báo giá V.AI STUDIO', url: url });
|
| 99 |
-
} else {
|
| 100 |
-
prompt('Copy link báo giá:', url);
|
| 101 |
-
}
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
// --- VAI_ORDERS Module ---
|
| 105 |
-
window.VAI_ORDERS = window.VAI_ORDERS || {
|
| 106 |
-
save: function() {
|
| 107 |
-
console.log('[V.AI STUDIO] Saving order...');
|
| 108 |
-
const orderData = {
|
| 109 |
-
date: new Date().toISOString(),
|
| 110 |
-
customer: {
|
| 111 |
-
name: document.getElementById('qcName')?.value || '',
|
| 112 |
-
phone: document.getElementById('qcPhone')?.value || '',
|
| 113 |
-
email: document.getElementById('qcEmail')?.value || '',
|
| 114 |
-
address: document.getElementById('qcAddr')?.value || ''
|
| 115 |
-
},
|
| 116 |
-
items: window.cartItems || [],
|
| 117 |
-
total: window.cartTotal || 0
|
| 118 |
-
};
|
| 119 |
-
|
| 120 |
-
// Save to localStorage
|
| 121 |
-
let orders = JSON.parse(localStorage.getItem('VAI_ORDERS') || '[]');
|
| 122 |
-
orders.push(orderData);
|
| 123 |
-
localStorage.setItem('VAI_ORDERS', JSON.stringify(orders));
|
| 124 |
-
|
| 125 |
-
alert('✅ Đã lưu đơn hàng thành công!');
|
| 126 |
-
console.log('[V.AI STUDIO] Order saved:', orderData);
|
| 127 |
-
},
|
| 128 |
-
|
| 129 |
-
openSearch: function() {
|
| 130 |
-
console.log('[V.AI STUDIO] Opening order search...');
|
| 131 |
-
const orders = JSON.parse(localStorage.getItem('VAI_ORDERS') || '[]');
|
| 132 |
-
if (!orders.length) {
|
| 133 |
-
alert('📋 Chưa có đơn hàng nào được lưu.');
|
| 134 |
-
return;
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
let msg = `📋 Tìm thấy ${orders.length} đơn hàng:\n\n`;
|
| 138 |
-
orders.slice(-5).forEach((order, idx) => {
|
| 139 |
-
msg += `${idx + 1}. ${order.customer.name || 'Không tên'} - ${order.customer.phone || ''}\n`;
|
| 140 |
-
});
|
| 141 |
-
alert(msg);
|
| 142 |
-
}
|
| 143 |
-
};
|
| 144 |
-
|
| 145 |
-
// --- Initialize cart items if not exists ---
|
| 146 |
-
window.cartItems = window.cartItems || [];
|
| 147 |
-
window.cartTotal = window.cartTotal || 0;
|
| 148 |
-
|
| 149 |
-
console.log('[V.AI STUDIO] Export functions loaded successfully!');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
grob-img-fix.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* V.AI STUDIO — Grob Image Fix
|
| 3 |
-
* ==============================
|
| 4 |
-
* Fixes Grob product images: imgs[0] (grob_products_img/) are 404.
|
| 5 |
-
* Swaps the primary image (p.i) to imgs[1] and removes imgs[0].
|
| 6 |
-
* Applied to all Grob brand products with >= 2 gallery images.
|
| 7 |
-
*/
|
| 8 |
-
(function() {
|
| 9 |
-
'use strict';
|
| 10 |
-
if (window.__GROB_IMG_FIX__) return;
|
| 11 |
-
window.__GROB_IMG_FIX__ = true;
|
| 12 |
-
|
| 13 |
-
function fixGrobImages() {
|
| 14 |
-
if (typeof window.D === 'undefined' || !Array.isArray(window.D) || !window.D.length) return;
|
| 15 |
-
|
| 16 |
-
var fixed = 0;
|
| 17 |
-
var skipped = 0;
|
| 18 |
-
|
| 19 |
-
for (var i = 0; i < window.D.length; i++) {
|
| 20 |
-
var p = window.D[i];
|
| 21 |
-
if (!p) continue;
|
| 22 |
-
|
| 23 |
-
// Only process Grob brand products
|
| 24 |
-
if (p.brand !== 'Grob') continue;
|
| 25 |
-
|
| 26 |
-
var imgs = p.images || [];
|
| 27 |
-
if (imgs.length >= 2) {
|
| 28 |
-
// imgs[0] is broken (grob_products_img/ -> 404)
|
| 29 |
-
// imgs[1] works (malloca-website/grob_img/)
|
| 30 |
-
|
| 31 |
-
// Set primary image to imgs[1]
|
| 32 |
-
p.image = imgs[1];
|
| 33 |
-
|
| 34 |
-
// Remove imgs[0], keep rest as gallery
|
| 35 |
-
p.images = imgs.slice(1);
|
| 36 |
-
|
| 37 |
-
fixed++;
|
| 38 |
-
} else {
|
| 39 |
-
skipped++;
|
| 40 |
-
}
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
-
if (fixed > 0) {
|
| 44 |
-
console.log('[Grob Img Fix] Fixed ' + fixed + ' product(s) in D (swapped imgs[0]->broken with imgs[1]->ok). Skipped ' + skipped + ' (only 1 image).');
|
| 45 |
-
|
| 46 |
-
// Trigger UI refresh if available
|
| 47 |
-
if (typeof window.refreshUI === 'function') {
|
| 48 |
-
window.refreshUI();
|
| 49 |
-
}
|
| 50 |
-
}
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
// Patch after D is populated
|
| 54 |
-
function patchD() {
|
| 55 |
-
fixGrobImages();
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
// Try patching at various intervals as data loads
|
| 59 |
-
function onReady() {
|
| 60 |
-
setTimeout(patchD, 200);
|
| 61 |
-
setTimeout(patchD, 500);
|
| 62 |
-
setTimeout(patchD, 1000);
|
| 63 |
-
setTimeout(patchD, 2000);
|
| 64 |
-
setTimeout(patchD, 3000);
|
| 65 |
-
setTimeout(patchD, 5000);
|
| 66 |
-
setTimeout(patchD, 8000);
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
if (document.readyState === 'loading') {
|
| 70 |
-
document.addEventListener('DOMContentLoaded', onReady);
|
| 71 |
-
} else {
|
| 72 |
-
onReady();
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
console.log('[Grob Img Fix] Loaded: ready to fix 471 Grob product images (imgs[0] -> imgs[1])');
|
| 76 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
grob-price-fix.js
DELETED
|
@@ -1,68 +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 |
-
if (typeof window.refreshUI === 'function') window.refreshUI();
|
| 47 |
-
}
|
| 48 |
-
}
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
// Try patching at various intervals as data loads
|
| 52 |
-
function onReady() {
|
| 53 |
-
setTimeout(patchD, 200);
|
| 54 |
-
setTimeout(patchD, 500);
|
| 55 |
-
setTimeout(patchD, 1000);
|
| 56 |
-
setTimeout(patchD, 2000);
|
| 57 |
-
setTimeout(patchD, 3000);
|
| 58 |
-
setTimeout(patchD, 5000);
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
if (document.readyState === 'loading') {
|
| 62 |
-
document.addEventListener('DOMContentLoaded', onReady);
|
| 63 |
-
} else {
|
| 64 |
-
onReady();
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
console.log('[Grob Price Fix] Loaded: 02.03=67k, 02.80=965k, 02.90=1,018k');
|
| 68 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index-updated.html
DELETED
|
@@ -1,56 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
<head>
|
| 4 |
-
<!-- V.AI STUDIO - Updated with 100% Quote Features -->
|
| 5 |
-
<script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"661b9191e7b0ab12bceb66f3","VAISTUDIO":"HF_TOKEN_REDACTED","REBUILD_TRIGGER":"2"}};</script>
|
| 6 |
-
<meta charset="UTF-8">
|
| 7 |
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 8 |
-
<title>V.AI STUDIO | Niềm tin khách hàng là tài sản của chúng tôi</title>
|
| 9 |
-
|
| 10 |
-
<!-- Load Quote System Scripts -->
|
| 11 |
-
<script src="quote-ui.js"></script>
|
| 12 |
-
<script src="vai-complete-quote.js"></script>
|
| 13 |
-
<script src="export-functions.js"></script>
|
| 14 |
-
|
| 15 |
-
<!-- ExcelJS for export -->
|
| 16 |
-
<script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
|
| 17 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
| 18 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 19 |
-
|
| 20 |
-
</head>
|
| 21 |
-
<body>
|
| 22 |
-
<div style="padding:40px;text-align:center;font-family:Arial;">
|
| 23 |
-
<h1>✅ V.AI STUDIO - 100% Quote Features Activated</h1>
|
| 24 |
-
<p>All quote buttons are now working:</p>
|
| 25 |
-
<ul style="text-align:left;display:inline-block;margin-top:20px;">
|
| 26 |
-
<li>💾 Lưu (always visible)</li>
|
| 27 |
-
<li>📋 Đơn hàng (needs V.AISTUDIO code)</li>
|
| 28 |
-
<li>📦 GH Excel (needs V.AISTUDIO code)</li>
|
| 29 |
-
<li>📦 GH PDF (needs V.AISTUDIO code)</li>
|
| 30 |
-
<li>📄 Excel export</li>
|
| 31 |
-
<li>📄 PDF export</li>
|
| 32 |
-
<li>🖨️ Print</li>
|
| 33 |
-
<li>🔗 Share</li>
|
| 34 |
-
</ul>
|
| 35 |
-
<button onclick="testQuoteButtons()" style="padding:15px 30px;background:#003f62;color:#fff;border:none;border-radius:10px;font-size:16px;cursor:pointer;margin-top:20px;">
|
| 36 |
-
Test Quote Buttons
|
| 37 |
-
</button>
|
| 38 |
-
</div>
|
| 39 |
-
|
| 40 |
-
<script>
|
| 41 |
-
function testQuoteButtons() {
|
| 42 |
-
alert('Testing quote buttons...\n\n' +
|
| 43 |
-
'✅ VAI_ORDERS.save() = ' + (typeof VAI_ORDERS !== 'undefined') + '\n' +
|
| 44 |
-
'✅ exportDeliveryExcel() = ' + (typeof exportDeliveryExcel === 'function') + '\n' +
|
| 45 |
-
'✅ exportDeliveryPDF() = ' + (typeof exportDeliveryPDF === 'function') + '\n' +
|
| 46 |
-
'✅ exportExcel() = ' + (typeof exportExcel === 'function') + '\n' +
|
| 47 |
-
'✅ exportPDF() = ' + (typeof exportPDF === 'function') + '\n' +
|
| 48 |
-
'✅ printQuotation() = ' + (typeof printQuotation === 'function') + '\n' +
|
| 49 |
-
'✅ applyAiDiscount() = ' + (typeof applyAiDiscount === 'function'));
|
| 50 |
-
|
| 51 |
-
console.log('VAI_ORDERS:', window.VAI_ORDERS);
|
| 52 |
-
console.log('All export functions are ready!');
|
| 53 |
-
}
|
| 54 |
-
</script>
|
| 55 |
-
</body>
|
| 56 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
order-store.js
DELETED
|
@@ -1,346 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* V.AI STUDIO — Order Store v19
|
| 3 |
-
*
|
| 4 |
-
* CHANGES from v18:
|
| 5 |
-
* - ADD PRODUCT TO ORDER: Nút "➕ Thêm SP" trong modal sửa đơn
|
| 6 |
-
* → Hiện thanh tìm kiếm SP → Chọn SP → Tự động thêm vào đơn + lưu
|
| 7 |
-
*/
|
| 8 |
-
(function(){
|
| 9 |
-
'use strict';
|
| 10 |
-
var STORE_KEY='vai_orders';
|
| 11 |
-
var QUEUE_KEY='vai_sync_queue';
|
| 12 |
-
var KETOAN_API='https://bep40-vaistudio-ketoan-bot.hf.space';
|
| 13 |
-
var KH_LIST=[];var KH_LOADED=false;
|
| 14 |
-
|
| 15 |
-
// ===== SYNC FROM SERVER (NEW in v17) =====
|
| 16 |
-
|
| 17 |
-
function resolveOrderItemImage(it){
|
| 18 |
-
var img=it.image||it.img||'';if(img)return img;
|
| 19 |
-
try{var data=(typeof D!=='undefined'&&D)||window._vaiGetD&&window._vaiGetD()||[];var code=(it.ma||it.model||'').toString().toLowerCase().replace(/[^a-z0-9]/g,'');var name=(it.ten||it.name||'').toString().toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[^a-z0-9]/g,'');for(var i=0;i<data.length;i++){var p=data[i]||{};var pc=((p.sku||p.mod||p.model||'')+' '+(p.slug||'')).toLowerCase().replace(/[^a-z0-9]/g,'');var pn=(p.name||p.n||'').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[^a-z0-9]/g,'');if((code&&pc.indexOf(code)!==-1)||(name&&pn.indexOf(name.slice(0,20))!==-1))return p.image||p.img||p.i||'';}}
|
| 20 |
-
catch(e){}
|
| 21 |
-
return '';
|
| 22 |
-
}
|
| 23 |
-
function syncFromServer(){
|
| 24 |
-
fetch(KETOAN_API+'/orders',{method:'GET'}).then(function(r){return r.json();}).then(function(d){
|
| 25 |
-
var serverOrders=d.data||[];
|
| 26 |
-
if(!serverOrders.length)return;
|
| 27 |
-
var localOrders=getOrders();
|
| 28 |
-
var localCodes={};
|
| 29 |
-
localOrders.forEach(function(o){if(o.code)localCodes[o.code]=true;});
|
| 30 |
-
var added=0;
|
| 31 |
-
serverOrders.forEach(function(so){
|
| 32 |
-
var code=so.ma_don||so.code||'';
|
| 33 |
-
if(!code)return;
|
| 34 |
-
var existingIdx=localOrders.findIndex(function(o){return o.code===code;});
|
| 35 |
-
if(existingIdx>=0 && !so.overwrite && !so.savedAt)return;
|
| 36 |
-
// Convert server format to local format
|
| 37 |
-
var order={
|
| 38 |
-
code:code,
|
| 39 |
-
customer:so.khach||so.customer||'',
|
| 40 |
-
phone:so.dien_thoai||so.phone||'',
|
| 41 |
-
email:so.email||'',
|
| 42 |
-
addr:so.dia_chi||so.addr||'',
|
| 43 |
-
date:so.date||so.ngay||'',
|
| 44 |
-
items:(so.items||[]).map(function(it){return{name:it.ten||it.name||'',model:it.ma||it.model||'',brand:it.brand||'',image:resolveOrderItemImage(it),specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),note:it.note||'',qty:it.sl||it.qty||1,price:it.listPrice||it.originalPrice||it.price||it.gia_goc||it.gia_niem_yet||it.gia_ban||0,discPrice:it.discPrice||it.gia_ban||it.price||0,total:(it.gia_ban||it.price||0)*(it.sl||it.qty||1)};}),
|
| 45 |
-
fees:so.fees||[],
|
| 46 |
-
deposit:so.deposit||0,
|
| 47 |
-
discountPercent:so.discountPercent||0,
|
| 48 |
-
grandTotal:so.grandTotal||0,
|
| 49 |
-
remaining:so.remaining||0,
|
| 50 |
-
status:so.status||(so.confirmedAt?'confirmed':'pending'),
|
| 51 |
-
savedAt:so.savedAt||so.confirmedAt||'',
|
| 52 |
-
confirmedAt:so.confirmedAt||'',
|
| 53 |
-
ma_kh:so.ma_kh||''
|
| 54 |
-
};
|
| 55 |
-
if(existingIdx>=0)localOrders[existingIdx]=order;else localOrders.unshift(order);
|
| 56 |
-
localCodes[code]=true;
|
| 57 |
-
added++;
|
| 58 |
-
});
|
| 59 |
-
if(added>0){
|
| 60 |
-
saveOrders(localOrders);
|
| 61 |
-
console.log('[OS v19] ☁️ Synced '+added+' orders from server. Total local: '+localOrders.length);
|
| 62 |
-
}
|
| 63 |
-
}).catch(function(e){
|
| 64 |
-
console.log('[OS v19] Server sync skip (bot may be sleeping)');
|
| 65 |
-
});
|
| 66 |
-
}
|
| 67 |
-
// Sync from server 3s after page load (give bot time to wake up)
|
| 68 |
-
setTimeout(syncFromServer,3000);
|
| 69 |
-
// Also sync every 60s
|
| 70 |
-
setInterval(syncFromServer,60000);
|
| 71 |
-
|
| 72 |
-
// ===== RETRY QUEUE =====
|
| 73 |
-
function getQueue(){try{return JSON.parse(localStorage.getItem(QUEUE_KEY)||'[]');}catch(e){return[];}}
|
| 74 |
-
function saveQueue(q){localStorage.setItem(QUEUE_KEY,JSON.stringify(q));}
|
| 75 |
-
function addToQueue(endpoint,payload){var q=getQueue();q.push({endpoint:endpoint,payload:payload,attempts:0,addedAt:Date.now()});saveQueue(q);}
|
| 76 |
-
function processQueue(){
|
| 77 |
-
var q=getQueue();if(!q.length)return;
|
| 78 |
-
var item=q[0];
|
| 79 |
-
fetch(KETOAN_API+item.endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(item.payload)})
|
| 80 |
-
.then(function(r){if(r.ok){q.shift();saveQueue(q);console.log('[OS v19] Queue sync OK: '+item.endpoint+' ('+q.length+' remaining)');if(q.length)setTimeout(processQueue,2000);}else throw new Error('HTTP '+r.status);})
|
| 81 |
-
.catch(function(){item.attempts=(item.attempts||0)+1;if(item.attempts>50){q.shift();}saveQueue(q);});
|
| 82 |
-
}
|
| 83 |
-
setInterval(processQueue,30000);
|
| 84 |
-
setTimeout(processQueue,5000);
|
| 85 |
-
|
| 86 |
-
function syncOrderToBot(order){
|
| 87 |
-
var payload={ma_don:order.code,khach:order.customer||'',dien_thoai:order.phone||'',dia_chi:order.addr||'',items:(order.items||[]).map(function(it){return{ma:it.model||'',model:it.model||'',ten:it.name||'',name:it.name||'',brand:it.brand||'',image:resolveOrderItemImage(it),img:it.image||it.img||'',specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),sl:it.qty||1,qty:it.qty||1,gia_ban:it.discPrice||it.price||0,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0};}),fees:order.fees||[],grandTotal:order.grandTotal||0,deposit:order.deposit||0,remaining:order.remaining||0,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},email:order.email||'',date:order.date||'',status:order.status||'pending',savedAt:order.savedAt||new Date().toISOString()};
|
| 88 |
-
if(order.confirmedAt)payload.confirmedAt=order.confirmedAt;
|
| 89 |
-
fetch(KETOAN_API+'/order-saved',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 90 |
-
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
| 91 |
-
.catch(function(){addToQueue('/order-saved',payload);});
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
function sendToKetoanBot(order){
|
| 95 |
-
var payload={ma_don:order.code,khach:order.customer||'',dien_thoai:order.phone||'',dia_chi:order.addr||'',items:(order.items||[]).map(function(it){return{ma:it.model||'',model:it.model||'',ten:it.name||'',name:it.name||'',brand:it.brand||'',image:resolveOrderItemImage(it),img:it.image||it.img||'',specs:orderItemInfo(it),info:orderItemInfo(it),dim_info:orderItemInfo(it),sl:it.qty||1,qty:it.qty||1,gia_ban:it.discPrice||it.price||0,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0};}),fees:order.fees||[],grandTotal:order.grandTotal||0,deposit:order.deposit||0,remaining:order.remaining||0,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},confirmedAt:order.confirmedAt||new Date().toISOString(),email:order.email||'',date:order.date||''};
|
| 96 |
-
fetch(KETOAN_API+'/order-confirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
|
| 97 |
-
.then(function(r){if(!r.ok)throw new Error();return r.json();})
|
| 98 |
-
.then(function(d){if(d.ok&&typeof showToast==='function')showToast('📨 Đã gửi kế toán!');})
|
| 99 |
-
.catch(function(){addToQueue('/order-confirmed',payload);if(typeof showToast==='function')showToast('⏳ Bot đang khởi động, sẽ tự gửi lại...');});
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
function loadKhachHang(){if(KH_LOADED)return Promise.resolve(KH_LIST);return fetch(KETOAN_API+'/khachhang',{method:'GET'}).then(function(r){return r.json();}).then(function(d){KH_LIST=d.data||[];KH_LOADED=true;return KH_LIST;}).catch(function(){KH_LOADED=true;return KH_LIST;});}
|
| 103 |
-
setTimeout(loadKhachHang,1000);
|
| 104 |
-
function findKH(query){if(!query||query.length<1)return[];var q=query.toLowerCase();return KH_LIST.filter(function(kh){return(kh.ma_kh||'').toLowerCase().includes(q)||(kh.ten||'').toLowerCase().includes(q)||(kh.sdt||'').includes(q);});}
|
| 105 |
-
function getKHCKRate(kh,brand){if(!kh||!brand)return null;var b=brand.toLowerCase().replace(/\s/g,'');var codes=kh.ck_codes||[];for(var i=0;i<codes.length;i++){var c=codes[i].toLowerCase().replace(/\s/g,'');if(c.includes(b)){var m=c.match(/ck(\d+)/);if(m)return parseInt(m[1]);}}return null;}
|
| 106 |
-
|
| 107 |
-
function getOrders(){try{return JSON.parse(localStorage.getItem(STORE_KEY)||'[]');}catch(e){return[];}}
|
| 108 |
-
function saveOrders(orders){localStorage.setItem(STORE_KEY,JSON.stringify(orders));}
|
| 109 |
-
function addOrder(order){var orders=getOrders();var idx=orders.findIndex(function(o){return o.code===order.code;});if(idx>=0)orders[idx]=order;else orders.unshift(order);saveOrders(orders);return orders;}
|
| 110 |
-
function deleteOrder(code){saveOrders(getOrders().filter(function(o){return o.code!==code;}));}
|
| 111 |
-
function searchOrders(q,statusFilter){var all=getOrders();if(statusFilter&&statusFilter!=='all')all=all.filter(function(o){return(o.status||'pending')===statusFilter;});if(!q||!q.trim())return all;var s=q.toLowerCase().trim();return all.filter(function(o){return(o.code||'').toLowerCase().includes(s)||(o.customer||'').toLowerCase().includes(s)||(o.phone||'').includes(s);});}
|
| 112 |
-
function fmt(n){if(!n||isNaN(n))return'0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
|
| 113 |
-
|
| 114 |
-
function formatInfoVal(v){
|
| 115 |
-
if(v==null||v==='')return '';
|
| 116 |
-
if(typeof v==='string'){
|
| 117 |
-
var t=v.trim();
|
| 118 |
-
if(!t||t==='{}'||t==='[]')return '';
|
| 119 |
-
if((t[0]==='{'&&t[t.length-1]==='}')||(t[0]==='['&&t[t.length-1]===']')){try{return formatInfoVal(JSON.parse(t));}catch(e){return t;}}
|
| 120 |
-
return t;
|
| 121 |
-
}
|
| 122 |
-
if(Array.isArray(v))return v.map(formatInfoVal).filter(Boolean).join('; ');
|
| 123 |
-
if(typeof v==='object'){
|
| 124 |
-
var out=[];Object.keys(v).forEach(function(k){var val=v[k];if(val==null||val===''||(typeof val==='object'&&!Array.isArray(val)&&!Object.keys(val).length))return;out.push(k+': '+formatInfoVal(val));});
|
| 125 |
-
return out.join('; ');
|
| 126 |
-
}
|
| 127 |
-
return String(v);
|
| 128 |
-
}
|
| 129 |
-
function orderItemInfo(it){it=it||{};return formatInfoVal(it.info||it.thong_tin||it.dim_info||it.dimension_info||it.specs||it.summary||it.desc||'');}
|
| 130 |
-
|
| 131 |
-
function compactKey(s){return (s||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d').toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 132 |
-
function itemKeys(it){var arr=[];['model','sku','code','ma','name'].forEach(function(k){if(it&&it[k])arr.push(compactKey(it[k]));});if(it&&it.name){String(it.name).split('|').forEach(function(x){arr.push(compactKey(x));});}return arr.filter(function(x){return x&&x.length>=3;});}
|
| 133 |
-
function matchItemDiscount(it,itemDiscounts){itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);for(var i=0;i<keys.length;i++){if(itemDiscounts[keys[i]])return itemDiscounts[keys[i]];}for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var k=keys[j];if(code.length>=3&&k.length>=3&&(k.indexOf(code)!==-1||code.indexOf(k)!==-1))return itemDiscounts[code];}}return 0;}
|
| 134 |
-
function parseItemDiscountLine(line,map){var lo=(line||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d').toLowerCase();var re1=/\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*(?:[:=\-]|\s+)?\s*(?:ck|chiet\s*khau|giam|discount)\s*([\d.,]+)\s*%?/gi;var re2=/(?:ck|chiet\s*khau|giam|discount)\s*\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*([\d.,]+)\s*%?/gi;var m;while((m=re1.exec(lo))!==null){var v=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v>0&&v<=100)map[compactKey(m[1])]=v;}while((m=re2.exec(lo))!==null){var v2=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v2>0&&v<=100)map[compactKey(m[1])]=v2;}}
|
| 135 |
-
function esc(s){return(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
| 136 |
-
function parsePrompt(text){
|
| 137 |
-
var fees=[],deposit=0,discountPercent=0,note='',itemDiscounts={};
|
| 138 |
-
if(!text)return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
|
| 139 |
-
text.split(/[,;\n]+/).forEach(function(line){
|
| 140 |
-
line=line.trim();if(!line)return;
|
| 141 |
-
parseItemDiscountLine(line,itemDiscounts);
|
| 142 |
-
if(Object.keys(itemDiscounts).length&&line.match(/\b[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d[A-Za-z0-9._\-\/]*\b/i)&&line.match(/\b(ck|chiết khấu|chiet khau|giảm|giam|discount)\b/i))return;
|
| 143 |
-
var lo=line.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d');
|
| 144 |
-
// Ghi chú
|
| 145 |
-
if(lo.match(/^(ghi chu|note|luu y)/)){note=line.replace(/^(ghi chú|ghi chu|note|lưu ý|luu y)[:\s]*/i,'').trim();return;}
|
| 146 |
-
// CK
|
| 147 |
-
var ck=lo.match(/^(ck|chiet khau|giam|discount)\s*([\d.,]+)\s*(%|k|tr)?/);
|
| 148 |
-
if(ck){var v=parseFloat(ck[2].replace(/\./g,'').replace(',','.'));if((!ck[3]||ck[3]===''||ck[3]==='%')&&v>0&&v<=100)discountPercent=v;return;}
|
| 149 |
-
// Cọc
|
| 150 |
-
if(lo.match(/coc|dat coc|deposit/)){var dm=lo.match(/([\d.,]+)\s*(k|tr|trieu|%)?/);if(dm){var a=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var u2=(dm[2]||'').toLowerCase();if(u2==='%'){deposit=a;/* percent handled separately */}else{if(u2==='k')a*=1000;if(u2==='tr'||u2==='trieu')a*=1000000;deposit=a;}}return;}
|
| 151 |
-
// Phụ phí: giao hàng, lắp đặt, vận chuyển, phí khác
|
| 152 |
-
var feeMatch=lo.match(/^(giao hang|giao|lap dat|lap|van chuyen|ship|phi giao|phi lap|phi khac|phu phi|phi)\s*([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 153 |
-
if(feeMatch){
|
| 154 |
-
var amt=parseFloat(feeMatch[2].replace(/\./g,'').replace(',','.'));
|
| 155 |
-
var u=(feeMatch[3]||'').toLowerCase();
|
| 156 |
-
if(u==='k'||u==='nghin')amt*=1000;else if(u==='tr'||u==='trieu'||u==='m')amt*=1000000;else if(amt>0&&amt<500)amt*=1000;
|
| 157 |
-
var labelMap={'giao hang':'Phí giao hàng','giao':'Phí giao hàng','lap dat':'Phí lắp đặt','lap':'Phí lắp đặt','van chuyen':'Phí vận chuyển','ship':'Phí vận chuyển','phi giao':'Phí giao hàng','phi lap':'Phí lắp đặt','phi khac':'Phí khác','phu phi':'Phụ phí','phi':'Phụ phí'};
|
| 158 |
-
fees.push({label:labelMap[feeMatch[1]]||'Phụ phí',amount:amt});
|
| 159 |
-
return;
|
| 160 |
-
}
|
| 161 |
-
// Fallback: line with number
|
| 162 |
-
var m=lo.match(/([\d.,]+)\s*(k|tr|trieu|m|nghin)?/);
|
| 163 |
-
if(m){var amt2=parseFloat(m[1].replace(/\./g,'').replace(',','.'));var u3=(m[2]||'').toLowerCase();if(u3==='k'||u3==='nghin')amt2*=1000;if(u3==='tr'||u3==='trieu'||u3==='m')amt2*=1000000;if(amt2>0){var lb=line.replace(m[0],'').trim();if(!lb||lb.length<2)lb='Phụ phí';fees.push({label:lb,amount:amt2});}}
|
| 164 |
-
});
|
| 165 |
-
return{fees:fees,deposit:deposit,discountPercent:discountPercent,note:note,itemDiscounts:itemDiscounts};
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
function applyDiscount(order){var pct=order.discountPercent||0;var itemDiscounts=order.itemDiscounts||{};(order.items||[]).forEach(function(it){var base=it.price||0;var ip=matchItemDiscount(it,itemDiscounts);if(ip>0)it.discPrice=Math.round(base*(1-ip/100));else if(pct>0)it.discPrice=Math.round(base*(1-pct/100));if(!it.discPrice)it.discPrice=base;it.total=(it.discPrice||base)*(it.qty||1);});}
|
| 169 |
-
function recalcOrder(order){applyDiscount(order);var pt=0;(order.items||[]).forEach(function(it){pt+=it.total||0;});var sc=0;(order.fees||[]).forEach(function(f){sc+=(f.amount||0);});order.grandTotal=pt+sc;order.remaining=order.grandTotal-(order.deposit||0);if(order.remaining<0)order.remaining=0;}
|
| 170 |
-
|
| 171 |
-
function saveCurrentOrder(){if(!window.VAI_QR||!window.VAI_QR.getData)return;var d=window.VAI_QR.getData();var qd=d.qd;var code=window.VAI_QR.getEffectiveOrderCode();var order={code:code,customer:qd.customer.name||'',phone:qd.customer.phone||'',email:qd.customer.email||'',addr:qd.customer.addr||'',date:qd.customer.date||new Date().toLocaleDateString('vi-VN'),items:qd.items||[],fees:d.fees||[],deposit:d.deposit||0,discountPercent:d.discountPercent||0,itemDiscounts:d.itemDiscounts||{},notes:d.notes||[],grandTotal:d.grandTotal||0,remaining:d.remaining||0,promptText:window.VAI_QR.getPromptText?window.VAI_QR.getPromptText():'',status:'pending',savedAt:new Date().toISOString()};recalcOrder(order);addOrder(order);syncOrderToBot(order);if(typeof showToast==='function')showToast('✅ Đã lưu đơn '+code);}
|
| 172 |
-
function confirmOrder(order){order.status='confirmed';order.confirmedAt=new Date().toISOString();addOrder(order);sendToKetoanBot(order);}
|
| 173 |
-
function unconfirmOrder(order){order.status='pending';delete order.confirmedAt;addOrder(order);fetch(KETOAN_API+'/order-unconfirmed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ma_don:order.code})}).catch(function(){});}
|
| 174 |
-
|
| 175 |
-
function orderToVAIData(order){recalcOrder(order);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:orderItemInfo(it),qty:it.qty||1,price:it.listPrice||it.originalPrice||it.price||0,discPrice:it.discPrice||it.price||0,total:it.total||0,note:it.note||''};}),grandTotal:order.grandTotal||0},fees:order.fees||[],notes:order.notes||[],discount:null,discountPercent:order.discountPercent||0,itemDiscounts:order.itemDiscounts||{},deposit:order.deposit||0,productTotal:order.grandTotal-(order.fees||[]).reduce(function(s,f){return s+(f.amount||0);},0),grandTotal:order.grandTotal||0,remaining:order.remaining||0};}
|
| 176 |
-
function _orderBW(opt){try{return !!(opt&&opt.bw)||!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)||!!(document.getElementById('vai-bw-export')&&document.getElementById('vai-bw-export').checked);}catch(e){return!!(opt&&opt.bw);}}
|
| 177 |
-
async function exportPDF(order,opt){if(window.VAI_QR&&window.VAI_QR.exportPDF){var d=orderToVAIData(order);await window.VAI_QR.exportPDF(d,order.code,_orderBW(opt));}}
|
| 178 |
-
async function exportExcel(order,opt){if(typeof ExcelJS==='undefined')return;if(window.VAI_QR&&window.VAI_QR.exportExcel){var d=orderToVAIData(order),qd=d.qd,qrUrl=window.VAI_QR.getQRUrl(order.deposit>0?order.remaining:order.grandTotal,order.code);await window.VAI_QR.exportExcel(d,qd,order.code,qrUrl,_orderBW(opt));}}
|
| 179 |
-
async function exportDeliveryPDF(order,opt){if(window.VAI_QR&&window.VAI_QR.exportDeliveryPDF){var qd=orderToVAIData(order).qd;await window.VAI_QR.exportDeliveryPDF(qd,order.code,_orderBW(opt));}}
|
| 180 |
-
async function exportDeliveryExcel(order,opt){if(typeof ExcelJS==='undefined')return;if(window.VAI_QR&&window.VAI_QR.exportDeliveryExcel){var qd=orderToVAIData(order).qd;await window.VAI_QR.exportDeliveryExcel(qd,order.code,_orderBW(opt));}}
|
| 181 |
-
|
| 182 |
-
function openSearchModal(){var old=document.getElementById('vai-order-search-modal');if(old)old.remove();var orders=getOrders();var pC=orders.filter(function(o){return(o.status||'pending')==='pending';}).length;var cC=orders.filter(function(o){return o.status==='confirmed';}).length;var cf='all';var ov=document.createElement('div');ov.id='vai-order-search-modal';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;display:flex;align-items:flex-start;justify-content:center;padding:40px 16px;overflow-y:auto';ov.onclick=function(e){if(e.target===ov)ov.remove();};ov.innerHTML='<div style="background:#fff;border-radius:16px;max-width:750px;width:100%;max-height:85vh;overflow:hidden;display:flex;flex-direction:column"><div style="padding:14px 18px;background:#003f62;display:flex;align-items:center"><div style="font-size:16px;font-weight:800;color:#fff;flex:1">📋 Đơn hàng ('+orders.length+')</div><button id="vai-os-refresh" style="background:rgba(255,255,255,.2);border:none;color:#fff;padding:4px 10px;border-radius:6px;cursor:pointer;font-size:11px;margin-right:8px">🔄</button><button id="vai-os-close" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer">✕</button></div><div style="padding:10px 18px;border-bottom:1px solid #e2e8f0"><div style="display:flex;gap:6px;margin-bottom:8px"><button class="os-filter" data-f="all" style="padding:5px 12px;border-radius:20px;border:2px solid #003f62;background:#003f62;color:#fff;font-size:11px;font-weight:700;cursor:pointer">Tất cả ('+orders.length+')</button><button class="os-filter" data-f="pending" style="padding:5px 12px;border-radius:20px;border:2px solid #f59e0b;background:#fffbeb;color:#92400e;font-size:11px;font-weight:700;cursor:pointer">⏳ ('+pC+')</button><button class="os-filter" data-f="confirmed" style="padding:5px 12px;border-radius:20px;border:2px solid #16a34a;background:#f0fdf4;color:#166534;font-size:11px;font-weight:700;cursor:pointer">✅ ('+cC+')</button></div><input id="vai-os-q" placeholder="Tìm mã đơn, tên KH, SĐT..." style="width:100%;padding:9px;border:2px solid #e2e8f0;border-radius:8px;font-size:12px;box-sizing:border-box"></div><div id="vai-os-r" style="flex:1;overflow-y:auto;padding:10px 18px"></div></div>';document.body.appendChild(ov);var re=document.getElementById('vai-os-r');function rl(ls){if(!ls.length){re.innerHTML='<div style="text-align:center;padding:30px;color:#94a3b8">Trống</div>';return;}re.innerHTML=ls.map(function(o,i){var isC=o.status==='confirmed';return'<div class="oi" data-i="'+i+'" style="padding:10px;border:1px solid #e2e8f0;border-radius:8px;margin-bottom:6px;cursor:pointer;border-left:3px solid '+(isC?'#16a34a':'#f59e0b')+'"><div style="display:flex;justify-content:space-between"><div><b style="color:#003f62">'+o.code+'</b> '+(isC?'✅':'⏳')+'</div><button class="od" data-c="'+esc(o.code)+'" style="background:#fee2e2;border:none;color:#dc2626;padding:2px 6px;border-radius:4px;cursor:pointer;font-size:10px">🗑</button></div><div style="font-size:11px;color:#64748b;margin-top:3px">'+esc(o.customer)+' • '+fmt(o.grandTotal)+'</div></div>';}).join('');re.onclick=function(e){var d=e.target.closest('.od');if(d){e.stopPropagation();if(confirm('Xóa?')){deleteOrder(d.dataset.c);df();}return;}var it=e.target.closest('.oi');if(it){ov.remove();openOrderDetail(ls[parseInt(it.dataset.i)]);}};}function df(){rl(searchOrders(document.getElementById('vai-os-q').value,cf));}rl(orders);document.getElementById('vai-os-close').onclick=function(){ov.remove();};document.getElementById('vai-os-q').oninput=df;
|
| 183 |
-
// Refresh button — force sync from server
|
| 184 |
-
document.getElementById('vai-os-refresh').onclick=function(){
|
| 185 |
-
this.textContent='⏳';var self=this;
|
| 186 |
-
syncFromServer();
|
| 187 |
-
setTimeout(function(){self.textContent='🔄';ov.remove();openSearchModal();},2000);
|
| 188 |
-
};
|
| 189 |
-
ov.querySelectorAll('.os-filter').forEach(function(b){b.onclick=function(e){e.stopPropagation();cf=this.dataset.f;ov.querySelectorAll('.os-filter').forEach(function(x){x.style.background='#fff';x.style.color='#003f62';});this.style.background='#003f62';this.style.color='#fff';df();};});}
|
| 190 |
-
|
| 191 |
-
function openOrderDetail(order){var old=document.getElementById('vai-order-detail-modal');if(old)old.remove();order=JSON.parse(JSON.stringify(order));var ov=document.createElement('div');ov.id='vai-order-detail-modal';ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:10000;display:flex;align-items:flex-start;justify-content:center;padding:16px;overflow-y:auto';ov.onclick=function(e){if(e.target===ov)ov.remove();};document.body.appendChild(ov);var modal=document.createElement('div');modal.style.cssText='background:#fff;border-radius:16px;max-width:750px;width:100%;max-height:92vh;overflow-y:auto';ov.appendChild(modal);
|
| 192 |
-
var _searchOpen=false;
|
| 193 |
-
function addProductToOrder(product){
|
| 194 |
-
var pn=product.priceNum||product.pn||0;
|
| 195 |
-
var item={name:product.name||product.n||'',model:product.sku||product.model||product.mod||'',brand:product.brand||'',image:product.image||product.img||product.i||'',specs:product.specs||'',info:product.info||product.thong_tin||'',dim_info:product.dim_info||product.dimension_info||'',qty:1,price:pn,discPrice:pn,total:pn,note:''};
|
| 196 |
-
order.items.push(item);
|
| 197 |
-
recalcOrder(order);
|
| 198 |
-
order.savedAt=new Date().toISOString();
|
| 199 |
-
addOrder(order);
|
| 200 |
-
syncOrderToBot(order);
|
| 201 |
-
if(typeof showToast==='function')showToast('✅ Đã thêm "'+((product.name||product.n||'').substring(0,25))+'"');
|
| 202 |
-
_searchOpen=false;
|
| 203 |
-
render();
|
| 204 |
-
}
|
| 205 |
-
function nrm(s){return(s||'').toString().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[đĐ]/g,'d').toLowerCase();}
|
| 206 |
-
function searchProducts(q){
|
| 207 |
-
if(!q||q.length<1)return[];
|
| 208 |
-
var db=[];if(typeof D!=='undefined'&&Array.isArray(D))db=D;if(typeof VAI_URL_ADDED_PRODUCTS!=='undefined'&&Array.isArray(VAI_URL_ADDED_PRODUCTS))db=db.concat(VAI_URL_ADDED_PRODUCTS);
|
| 209 |
-
var nq=nrm(q);var terms=nq.split(/\s+/).filter(function(t){return t.length>1;});
|
| 210 |
-
var scored=[];
|
| 211 |
-
for(var i=0;i<db.length;i++){
|
| 212 |
-
var p=db[i];if(!p)continue;
|
| 213 |
-
var name=nrm(p.name||p.n||'');var sku=nrm(p.sku||p.model||p.mod||'');var brand=nrm(p.brand||'');
|
| 214 |
-
var score=0;
|
| 215 |
-
for(var j=0;j<terms.length;j++){
|
| 216 |
-
var t=terms[j];
|
| 217 |
-
if(name.indexOf(t)!==-1)score+=3;
|
| 218 |
-
if(sku.indexOf(t)!==-1)score+=5;
|
| 219 |
-
if(brand.indexOf(t)!==-1)score+=2;
|
| 220 |
-
if(name===t||sku===t)score+=10;
|
| 221 |
-
}
|
| 222 |
-
if(score>0)scored.push({p:p,s:score});
|
| 223 |
-
}
|
| 224 |
-
scored.sort(function(a,b){return b.s-a.s;});
|
| 225 |
-
return scored.slice(0,20).map(function(x){return x.p;});
|
| 226 |
-
}
|
| 227 |
-
function toggleSearchPanel(){
|
| 228 |
-
_searchOpen=!_searchOpen;
|
| 229 |
-
render();
|
| 230 |
-
if(_searchOpen){
|
| 231 |
-
setTimeout(function(){
|
| 232 |
-
var inp=document.getElementById('od-sp-search');
|
| 233 |
-
if(inp)inp.focus();
|
| 234 |
-
},50);
|
| 235 |
-
}
|
| 236 |
-
}
|
| 237 |
-
function renderSearchPanel(){
|
| 238 |
-
if(!_searchOpen)return '';
|
| 239 |
-
return '<div id="od-sp-panel" style="margin-top:8px;padding:10px;background:#f8fafc;border-radius:8px;border:2px solid #003f62">'
|
| 240 |
-
+'<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px">'
|
| 241 |
-
+'<span style="font-size:12px;font-weight:700;color:#003f62">🔍 Thêm sản phẩm</span>'
|
| 242 |
-
+'<button id="od-sp-close" style="margin-left:auto;background:none;border:none;color:#64748b;font-size:16px;cursor:pointer;padding:0 4px">✕</button></div>'
|
| 243 |
-
+'<input id="od-sp-search" placeholder="Tìm tên SP, mã SP, thương hiệu..." style="width:100%;padding:8px 10px;border:2px solid #003f62;border-radius:8px;font-size:12px;box-sizing:border-box;outline:none" onfocus="this.style.borderColor=\'#005a8c\'" onblur="this.style.borderColor=\'#003f62\'">'
|
| 244 |
-
+'<div id="od-sp-results" style="max-height:260px;overflow-y:auto;margin-top:6px"></div></div>';
|
| 245 |
-
}
|
| 246 |
-
function renderSearchResults(){
|
| 247 |
-
if(!_searchOpen)return;
|
| 248 |
-
var q=document.getElementById('od-sp-search');
|
| 249 |
-
if(!q)return;
|
| 250 |
-
var results=searchProducts(q.value);
|
| 251 |
-
var el=document.getElementById('od-sp-results');
|
| 252 |
-
if(!el)return;
|
| 253 |
-
if(!q.value.trim()){el.innerHTML='<div style="text-align:center;padding:16px;color:#94a3b8;font-size:11px">Nhập từ khóa để tìm sản phẩm</div>';return;}
|
| 254 |
-
if(!results.length){el.innerHTML='<div style="text-align:center;padding:16px;color:#94a3b8;font-size:11px">Không tìm thấy sản phẩm</div>';return;}
|
| 255 |
-
el.innerHTML=results.map(function(p){
|
| 256 |
-
var nm=p.name||p.n||'';
|
| 257 |
-
var sku=p.sku||p.model||p.mod||'';
|
| 258 |
-
var br=p.brand||'';
|
| 259 |
-
var img=p.image||p.img||p.i||'';
|
| 260 |
-
var price=p.priceNum||p.pn||0;
|
| 261 |
-
return '<div class="od-sp-item" style="display:flex;align-items:center;gap:8px;padding:6px;border:1px solid #e2e8f0;border-radius:6px;margin-bottom:4px;cursor:pointer;background:#fff" onmouseover="this.style.background=\'#eff6ff\';this.style.borderColor=\'#93c5fd\'" onmouseout="this.style.background=\'#fff\';this.style.borderColor=\'#e2e8f0\'">'
|
| 262 |
-
+'<div style="width:44px;height:44px;flex-shrink:0;background:#f1f5f9;border-radius:4px;display:flex;align-items:center;justify-content:center;overflow:hidden">'+(img?'<img src="'+img+'" style="max-width:40px;max-height:40px;object-fit:contain" referrerpolicy="no-referrer" onerror="this.style.display=\'none\'">':'<span style="font-size:16px;color:#94a3b8">📦</span>')+'</div>'
|
| 263 |
-
+'<div style="flex:1;min-width:0"><div style="font-size:11px;font-weight:700;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(nm)+'</div><div style="font-size:9px;color:#64748b">'+esc(sku)+(br?' • '+esc(br):'')+'</div></div>'
|
| 264 |
-
+'<div style="font-size:11px;font-weight:700;color:#003f62;white-space:nowrap">'+fmt(price)+'</div>'
|
| 265 |
-
+'<button class="od-sp-add" data-name="'+esc(nm)+'" style="padding:4px 8px;background:#003f62;color:#fff;border:none;border-radius:5px;font-size:10px;font-weight:700;cursor:pointer;white-space:nowrap">+ Thêm</button></div>';
|
| 266 |
-
}).join('');
|
| 267 |
-
// Bind click events
|
| 268 |
-
el.querySelectorAll('.od-sp-item').forEach(function(item){
|
| 269 |
-
item.onclick=function(e){
|
| 270 |
-
if(e.target.closest('.od-sp-add'))return;
|
| 271 |
-
var nm=this.querySelector('.od-sp-add').dataset.name;
|
| 272 |
-
var idx=results.findIndex(function(p){return(p.name||p.n||'')===nm;});
|
| 273 |
-
if(idx>=0)addProductToOrder(results[idx]);
|
| 274 |
-
};
|
| 275 |
-
});
|
| 276 |
-
el.querySelectorAll('.od-sp-add').forEach(function(btn){
|
| 277 |
-
btn.onclick=function(e){
|
| 278 |
-
e.stopPropagation();
|
| 279 |
-
var nm=this.dataset.name;
|
| 280 |
-
var idx=results.findIndex(function(p){return(p.name||p.n||'')===nm;});
|
| 281 |
-
if(idx>=0)addProductToOrder(results[idx]);
|
| 282 |
-
};
|
| 283 |
-
});
|
| 284 |
-
}
|
| 285 |
-
function render(){recalcOrder(order);var isC=order.status==='confirmed';
|
| 286 |
-
var h='<div style="padding:12px 18px;background:#003f62;border-radius:16px 16px 0 0;display:flex;justify-content:space-between;align-items:center"><div><b style="color:#fff">📄 '+esc(order.code)+'</b> '+(isC?'<span style="background:#dcfce7;color:#16a34a;padding:2px 8px;border-radius:6px;font-size:10px;margin-left:8px">✅ Đã chốt</span>':'<span style="background:#fef9c3;color:#92400e;padding:2px 8px;border-radius:6px;font-size:10px;margin-left:8px">⏳</span>')+'</div><button id="xc" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer">✕</button></div><div style="padding:14px 18px">'
|
| 287 |
-
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:11px;margin-bottom:10px;padding:10px;background:#f8fafc;border-radius:8px"><div><b>KH:</b> <input id="od-name" value="'+esc(order.customer||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:55%;font-size:11px"></div><div><b>SĐT:</b> <input id="od-phone" value="'+esc(order.phone||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:50%;font-size:11px"></div><div style="grid-column:1/-1"><b>ĐC:</b> <input id="od-addr" value="'+esc(order.addr||'')+'" style="border:1px solid #ddd;padding:3px 6px;border-radius:4px;width:85%;font-size:11px"></div></div>'
|
| 288 |
-
+'<div style="margin-bottom:10px;padding:8px 10px;background:#eff6ff;border-radius:8px;border:1px solid #bfdbfe;position:relative"><div style="font-size:10px;font-weight:700;color:#1e40af;margin-bottom:4px">👤 Mã KH</div><input id="od-makh" value="'+esc(order.ma_kh||'')+'" placeholder="Mã KH hoặc tên..." style="width:100%;padding:6px 10px;border:1px solid #93c5fd;border-radius:6px;font-size:11px;box-sizing:border-box"><div id="od-makh-suggest" style="position:absolute;left:10px;right:10px;top:58px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;max-height:100px;overflow-y:auto;display:none;z-index:10;box-shadow:0 4px 12px rgba(0,0,0,.1)"></div><div id="od-makh-info" style="font-size:9px;margin-top:3px;color:#64748b">'+(order.ma_kh?'✅ '+order.ma_kh:'')+'</div></div>'
|
| 289 |
-
+'<table style="width:100%;border-collapse:collapse;font-size:11px;margin-bottom:8px"><thead><tr style="background:#003f62;color:#fff"><th style="padding:5px">#</th><th style="padding:5px">Ảnh</th><th style="padding:5px;text-align:left">SP</th><th style="padding:5px;text-align:left">Thông tin</th><th style="padding:5px">SL</th><th style="padding:5px;text-align:right">Giá niêm yết</th><th style="padding:5px;text-align:right">Giá CK</th><th style="padding:5px;text-align:right">TT</th><th></th></tr></thead><tbody>';
|
| 290 |
-
(order.items||[]).forEach(function(it,i){var ck=it.discPrice&&it.price&&it.discPrice<it.price;h+='<tr style="border-bottom:1px solid #f1f5f9"><td style="padding:3px;text-align:center">'+(i+1)+'</td><td style="padding:3px">'+(it.image?'<img src="'+it.image+'" style="width:40px;height:40px;object-fit:contain;border-radius:4px" referrerpolicy="no-referrer" onerror="this.style.display=\'none\'">':'')+'</td><td style="padding:3px"><b style="font-size:10px">'+esc((it.name||'').substring(0,28))+'</b><br><span style="font-size:9px;color:#64748b">'+esc(it.model||'')+'</span></td><td style="padding:3px;font-size:9px;color:#64748b;max-width:150px;white-space:normal">'+esc(orderItemInfo(it)).substring(0,260)+'</td><td style="padding:3px;text-align:center">'+(it.qty||1)+'</td><td style="padding:3px;text-align:right">'+fmt(it.price)+'</td><td style="padding:3px;text-align:right;'+(ck?'color:#dc3545;font-weight:700':'')+'">'+fmt(it.discPrice||it.price)+'</td><td style="padding:3px;text-align:right;font-weight:700">'+fmt(it.total)+'</td><td style="padding:3px"><button class="xd" data-i="'+i+'" style="background:#fee2e2;border:none;color:#dc2626;padding:1px 4px;border-radius:3px;cursor:pointer;font-size:9px">✕</button></td></tr>';});
|
| 291 |
-
h+='</tbody></table>';
|
| 292 |
-
if(order.fees&&order.fees.length){h+='<div style="padding:4px 10px;background:#fffbeb;border-radius:6px;margin-bottom:6px;font-size:10px">';order.fees.forEach(function(f){h+='<div style="display:flex;justify-content:space-between"><span style="color:#92400e">'+esc(f.label)+'</span><b>'+fmt(f.amount)+'</b></div>';});h+='</div>';}
|
| 293 |
-
h+='<div style="padding:8px 12px;background:#003f62;border-radius:6px;display:flex;justify-content:space-between;margin-bottom:6px"><span style="color:#fff;font-weight:800">TỔNG</span><span style="color:#f0b840;font-weight:900;font-size:15px">'+fmt(order.grandTotal)+'</span></div>';
|
| 294 |
-
if(order.deposit>0)h+='<div style="padding:4px 10px;background:#f0fdf4;border-radius:6px;font-size:10px;margin-bottom:6px"><span>Cọc: <b>'+fmt(order.deposit)+'</b></span> | <span>Còn: <b style="color:#dc2626">'+fmt(order.remaining)+'</b></span></div>';
|
| 295 |
-
h+='<div style="margin:8px 0;padding:8px;background:#f0f9ff;border-radius:8px;border:1px solid #bae6fd"><input id="od-prompt" value="'+esc(order.promptText||'')+'" placeholder="mh70btc ck 30%, CK 10, giao 200k, lắp 300k, cọc 5tr" style="width:100%;padding:6px;border:1px solid #e2e8f0;border-radius:6px;font-size:11px;box-sizing:border-box"><button id="od-apply" style="margin-top:4px;padding:4px 10px;background:#0369a1;color:#fff;border:none;border-radius:5px;font-weight:700;cursor:pointer;font-size:10px">⚡ Áp dụng</button></div>'
|
| 296 |
-
+'<label style="display:inline-flex;align-items:center;gap:5px;margin-top:8px;padding:6px 9px;background:#fff;color:#111;border:1px solid #111;border-radius:8px;font-weight:700;font-size:10px"><input id="od-bw" type="checkbox"> Trắng đen</label>'
|
| 297 |
-
+'<div style="display:flex;gap:5px;margin-top:8px;flex-wrap:wrap"><button id="od-add-sp" style="padding:7px 12px;background:#0891b2;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">➕ Thêm SP</button><button id="od-save" style="padding:7px 12px;background:#7c3aed;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">💾 Lưu</button><button id="od-pdf" style="padding:7px 10px;background:#db9815;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">PDF</button><button id="od-xl" style="padding:7px 10px;background:#b45309;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">Excel</button><button id="od-gh" style="padding:7px 10px;background:#0d9488;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">GH PDF</button><button id="od-gh-xl" style="padding:7px 10px;background:#059669;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px">GH Excel</button>';
|
| 298 |
-
if(!isC)h+='<button id="od-confirm" style="padding:7px 14px;background:#16a34a;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-size:11px;margin-left:auto">✅ CHỐT</button>';
|
| 299 |
-
else h+='<button id="od-unconfirm" style="padding:7px 10px;background:#fef2f2;color:#dc2626;border:2px solid #fca5a5;border-radius:8px;font-weight:700;cursor:pointer;font-size:10px;margin-left:auto">↩️ Bỏ chốt</button>';
|
| 300 |
-
h+='</div>';
|
| 301 |
-
h+=renderSearchPanel();
|
| 302 |
-
h+='</div>';modal.innerHTML=h;
|
| 303 |
-
document.getElementById('xc').onclick=function(){ov.remove();};
|
| 304 |
-
modal.querySelectorAll('.xd').forEach(function(b){b.onclick=function(e){e.stopPropagation();order.items.splice(parseInt(this.dataset.i),1);render();};});
|
| 305 |
-
// Add SP button
|
| 306 |
-
var addSpBtn=document.getElementById('od-add-sp');
|
| 307 |
-
if(addSpBtn){addSpBtn.onclick=toggleSearchPanel;}
|
| 308 |
-
// Close search panel
|
| 309 |
-
var closeSpBtn=document.getElementById('od-sp-close');
|
| 310 |
-
if(closeSpBtn){closeSpBtn.onclick=function(){_searchOpen=false;render();};}
|
| 311 |
-
// Search input
|
| 312 |
-
var searchInput=document.getElementById('od-sp-search');
|
| 313 |
-
if(searchInput){searchInput.oninput=function(){renderSearchResults();};searchInput.onkeydown=function(e){if(e.key==='Escape'){_searchOpen=false;render();}};}
|
| 314 |
-
// Render search results if panel is open
|
| 315 |
-
if(_searchOpen){renderSearchResults();}
|
| 316 |
-
function cc(){order.customer=document.getElementById('od-name').value;order.phone=document.getElementById('od-phone').value;order.addr=document.getElementById('od-addr').value;}
|
| 317 |
-
var mI=document.getElementById('od-makh'),sB=document.getElementById('od-makh-suggest'),mInfo=document.getElementById('od-makh-info');
|
| 318 |
-
if(mI){mI.oninput=function(){var q=this.value.trim();if(q.length<1){sB.style.display='none';mInfo.textContent='';order.ma_kh='';return;}var ms=findKH(q);if(!ms.length){sB.style.display='none';mInfo.textContent='⚠️ Không tìm';order.ma_kh='';return;}var ex=ms.find(function(k){return k.ma_kh.toLowerCase()===q.toLowerCase();});if(ex){applyKH(ex);sB.style.display='none';return;}sB.style.display='block';sB.innerHTML=ms.slice(0,4).map(function(k){return'<div class="kh-opt" data-ma="'+esc(k.ma_kh)+'" style="padding:5px 8px;cursor:pointer;border-bottom:1px solid #f1f5f9;font-size:10px"><b>'+esc(k.ma_kh)+'</b> '+esc(k.ten)+'</div>';}).join('');sB.querySelectorAll('.kh-opt').forEach(function(o){o.onmousedown=function(e){e.preventDefault();var k=KH_LIST.find(function(x){return x.ma_kh===this.dataset.ma;}.bind(this));if(k){mI.value=k.ma_kh;applyKH(k);sB.style.display='none';}};});};mI.onblur=function(){setTimeout(function(){sB.style.display='none';},200);};}
|
| 319 |
-
function applyKH(kh){order.ma_kh=kh.ma_kh;var ap=[];(order.items||[]).forEach(function(it){var br=it.brand||(it.name||'').match(/(malloca|eurogold|grob|garis)/i);if(br){var bn=typeof br==='string'?br:br[1];var rate=getKHCKRate(kh,bn);if(rate!==null){it.discPrice=Math.round((it.price||0)*(1-rate/100));it.total=it.discPrice*(it.qty||1);ap.push(bn+' '+rate+'%');}}});order.discountPercent=0;recalcOrder(order);mInfo.innerHTML='✅ '+esc(kh.ma_kh)+' — '+esc(kh.ten)+(ap.length?' | '+ap.join(', '):'');render();}
|
| 320 |
-
document.getElementById('od-apply').onclick=function(){cc();var p=document.getElementById('od-prompt').value;order.promptText=p;var ps=parsePrompt(p);order.fees=ps.fees.length?ps.fees:(p.trim()?order.fees:[]);order.deposit=ps.deposit||0;if(ps.discountPercent)order.discountPercent=ps.discountPercent;order.itemDiscounts=ps.itemDiscounts||{};order.notes=ps.notes||[];recalcOrder(order);render();};
|
| 321 |
-
document.getElementById('od-save').onclick=function(){cc();var p=document.getElementById('od-prompt').value;if(p&&p!==order.promptText){order.promptText=p;var ps=parsePrompt(p);order.fees=ps.fees.length?ps.fees:[];order.deposit=ps.deposit||0;if(ps.discountPercent)order.discountPercent=ps.discountPercent;order.itemDiscounts=ps.itemDiscounts||{};order.notes=ps.notes||[];recalcOrder(order);}else{recalcOrder(order);}order.savedAt=new Date().toISOString();addOrder(order);syncOrderToBot(order);alert('✅ Lưu!');};
|
| 322 |
-
document.getElementById('od-pdf').onclick=function(){cc();recalcOrder(order);exportPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 323 |
-
document.getElementById('od-xl').onclick=function(){cc();recalcOrder(order);exportExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 324 |
-
document.getElementById('od-gh').onclick=function(){cc();recalcOrder(order);exportDeliveryPDF(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 325 |
-
document.getElementById('od-gh-xl').onclick=function(){cc();recalcOrder(order);exportDeliveryExcel(order,{bw:!!(document.getElementById('od-bw')&&document.getElementById('od-bw').checked)});};
|
| 326 |
-
var cB=document.getElementById('od-confirm');if(cB)cB.onclick=function(){cc();recalcOrder(order);if(!confirm('Chốt đơn '+order.code+'?'))return;confirmOrder(order);if(typeof showToast==='function')showToast('✅ Đã chốt '+order.code);render();};
|
| 327 |
-
var uB=document.getElementById('od-unconfirm');if(uB)uB.onclick=function(){if(!confirm('Bỏ chốt?'))return;unconfirmOrder(order);render();};
|
| 328 |
-
}render();
|
| 329 |
-
}
|
| 330 |
-
|
| 331 |
-
function injectMaKHToQuote(){var modal=document.querySelector('.quote-overlay.open,.quote-modal.open,.quote-overlay[style*="block"],.quote-modal[style*="block"],[class*="quote"][class*="open"],[class*="quote"][style*="block"]');if(!modal)return;if(modal.querySelector('#vai-makh-section'))return;var pI=null;modal.querySelectorAll('input,textarea').forEach(function(el){var ph=(el.placeholder||'').toLowerCase();if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('chiet khau'))pI=el;});if(!pI)return;
|
| 332 |
-
var sec=document.createElement('div');sec.id='vai-makh-section';sec.style.cssText='margin:8px 16px;padding:8px 10px;background:#eff6ff;border-radius:8px;border:1px solid #bfdbfe;position:relative';
|
| 333 |
-
sec.innerHTML='<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px"><span style="font-size:10px;font-weight:700;color:#1e40af">👤 Mã KH</span><span id="vai-makh-badge" style="font-size:9px;color:#64748b"></span></div><input id="vai-makh-input" placeholder="Mã KH hoặc tên..." style="width:100%;padding:6px 10px;border:1px solid #93c5fd;border-radius:6px;font-size:11px;box-sizing:border-box"><div id="vai-makh-dropdown" style="position:absolute;left:10px;right:10px;top:54px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;max-height:100px;overflow-y:auto;display:none;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,.1)"></div>';
|
| 334 |
-
pI.parentElement.insertBefore(sec,pI);
|
| 335 |
-
var input=document.getElementById('vai-makh-input'),dd=document.getElementById('vai-makh-dropdown'),badge=document.getElementById('vai-makh-badge');
|
| 336 |
-
input.oninput=function(){var q=this.value.trim();if(q.length<1){dd.style.display='none';badge.textContent='';return;}loadKhachHang().then(function(){var ms=findKH(q);if(!ms.length){dd.style.display='none';badge.textContent='⚠️';badge.style.color='#dc2626';return;}var ex=ms.find(function(k){return k.ma_kh.toLowerCase()===q.toLowerCase();});if(ex){dd.style.display='none';badge.innerHTML='✅ '+esc(ex.ten);badge.style.color='#16a34a';applyKHToQuoteModal(ex);return;}dd.style.display='block';dd.innerHTML=ms.slice(0,4).map(function(k){return'<div class="vai-kh-opt" data-ma="'+esc(k.ma_kh)+'" style="padding:5px 8px;cursor:pointer;border-bottom:1px solid #f1f5f9;font-size:10px"><b>'+esc(k.ma_kh)+'</b> '+esc(k.ten)+'</div>';}).join('');dd.querySelectorAll('.vai-kh-opt').forEach(function(o){o.onmousedown=function(e){e.preventDefault();var k=KH_LIST.find(function(x){return x.ma_kh===this.dataset.ma;}.bind(this));if(k){input.value=k.ma_kh;dd.style.display='none';badge.innerHTML='✅ '+esc(k.ten);badge.style.color='#16a34a';applyKHToQuoteModal(k);}};});});};
|
| 337 |
-
input.onblur=function(){setTimeout(function(){dd.style.display='none';},200);};
|
| 338 |
-
}
|
| 339 |
-
function applyKHToQuoteModal(kh){var codes=kh.ck_codes||[];if(!codes.length)return;var pI=null;document.querySelectorAll('input,textarea').forEach(function(el){var ph=(el.placeholder||'').toLowerCase();if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('chiet khau'))pI=el;});if(!pI)return;var ckVal=null;for(var i=0;i<codes.length;i++){var m=codes[i].match(/ck(\d+)/i);if(m){ckVal=parseInt(m[1]);break;}}if(!ckVal)return;var cur=pI.value||'';cur=cur.replace(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)\s*[\d.,]+\s*%?\s*[,;]?\s*/gim,'').trim();pI.value=('CK '+ckVal+(cur?', '+cur:'')).trim();pI.dispatchEvent(new Event('input',{bubbles:true}));}
|
| 340 |
-
|
| 341 |
-
var _mO=new MutationObserver(function(){setTimeout(injectMaKHToQuote,500);});_mO.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','style']});setInterval(injectMaKHToQuote,3000);
|
| 342 |
-
|
| 343 |
-
window.VAI_ORDERS={save:saveCurrentOrder,search:searchOrders,openSearch:openSearchModal,openDetail:openOrderDetail,getAll:getOrders,getByCode:function(c){return getOrders().find(function(o){return o.code===c;});},add:addOrder,delete:deleteOrder,confirm:confirmOrder,unconfirm:unconfirmOrder,toVAIData:orderToVAIData,loadKH:loadKhachHang,findKH:findKH,processQueue:processQueue,syncFromServer:syncFromServer};
|
| 344 |
-
var qLen=getQueue().length;
|
| 345 |
-
console.log('[OS v19] '+getOrders().length+' orders'+(qLen?' | ⏳ '+qLen+' pending sync':'')+' | ☁️ Server sync enabled | ➕ Add product to order enabled');
|
| 346 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
products_konox.json
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{"n": "Bếp từ nồi cảm ống Canzy CK-200", "l": "https://example.com/ck-200", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/canzy.png", "p": "7.500.000đ", "pn": 7500000, "c": "Bếp điện từ", "cs": "bep-dien-tu", "ci": "fa-blender", "sum": "Bếp từ Canzy với công nghệ cảm ống", "desc": "Bếp từ nồi cảm ống Canzy CK-200 thiết kế hiện đại", "specs": {"Công suất": "2000W", "Kích thước": "32x24x18 cm", "Trọng lượng": "3.2kg"}, "feats": ["Cảm ống", "Nấu nhanh", "An toàn"], "sku": "CK-200", "mod": "CK-200", "slug": "canzy-bep-tu-noni-cam-ong-ck-200", "_source": "Canzy"},
|
| 3 |
-
{"n": "Máy hút mùi Điện Máy Xanh DX-100", "l": "https://example.com/dx-100", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/dmx.jpg", "p": "5.200.000đ", "pn": 5200000, "c": "Máy hút mùi", "cs": "may-hut-mui", "ci": "fa-wind", "sum": "Máy hút mùi Điện Máy Xanh chất lượng", "desc": "Máy hút mùi DX-100 với thiết kế tiết kiệm không gian", "specs": {"Công suất hút": "150W", "Độ sạch": "98%", "Tiêu thụ điện": "80W"}, "feats": ["Nhỏ gọn", "Tiết kiệm điện", "Dễ lắp"], "sku": "DX-100", "mod": "DX-100", "slug": "dien-may-xanh-may-hut-mui-dx-100", "_source": "Điện Máy Xanh"}
|
| 4 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
products_with_slugs.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{"n": "Bếp từ Malloca MT-750H | 750H", "l": "https://example.com/mt-750h", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png", "p": "12.500.000đ", "pn": 12500000, "c": "Bếp điện từ", "cs": "bep-dien-tu", "ci": "fa-blender", "sum": "Bếp từ nồi cảm ống Malloca chất lượng cao", "desc": "Bếp từ Malloca MT-750H với công nghệ nấu nhanh, tiết kiệm năng lượng", "specs": {"Công suất": "750W", "Kích thước": "35x25x15 cm", "Trọng lượng": "2.5kg"}, "feats": ["Nấu nhanh", "An toàn", "Dễ vệ sinh"], "sku": "MT-750H", "mod": "MT-750H", "slug": "malloca-bep-tu-doi-750h", "_source": "Malloca"},
|
| 3 |
-
{"n": "Máy hút mùi Eurogold EG-220 | 220", "l": "https://example.com/eg-220", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/eurogold.png", "p": "8.900.000đ", "pn": 8900000, "c": "Máy hút mùi", "cs": "may-hut-mui", "ci": "fa-wind", "sum": "Máy hút mùi Eurogold với công suất mạnh mẽ", "desc": "Máy hút mùi Eurogold EG-220 thiết kế hiện đại", "specs": {"Công suất hút": "220W", "Độ sạch": "99%", "Âm thanh": "<45dB"}, "feats": ["Hút mạnh", "Thiết kế sang trọng", "Giảm ồn"], "sku": "EG-220", "mod": "EG-220", "slug": "eurogold-may-hut-mui-eg-220", "_source": "Eurogold"},
|
| 4 |
-
{"n": "Chậu rửa chén Grob GR-120 | 120", "l": "https://example.com/gr-120", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/grob.png", "p": "4.500.000đ", "pn": 4500000, "c": "Chậu rửa chén", "cs": "chau-rua-chen", "ci": "fa-sink", "sum": "Chậu rửa chén Grob chất lượng cao", "desc": "Chậu rửa chén Grob GR-120 thiết kế tiện dụng", "specs": {"Chất liệu": "Thép không gỉ", "Dung tích": "120L", "Số cửa": "3 cửa"}, "feats": ["Bền chắc", "Dễ dùng", "Tiết kiệm nước"], "sku": "GR-120", "mod": "GR-120", "slug": "grob-chau-rua-chen-gr-120", "_source": "Grob"},
|
| 5 |
-
{"n": "Kệ bếp cao su Malloca KS-900 | 900", "l": "https://example.com/ks-900", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png", "p": "2.800.000đ", "pn": 2800000, "c": "Kệ bếp", "cs": "gia-bo", "ci": "fa-layer-group", "sum": "Kệ bếp cao su chịu lực tốt", "desc": "Kệ bếp cao su Malloca KS-900 giúp tổ chức dễ dàng", "specs": {"Chiều cao": "90cm", "Chiều rộng": "60cm", "Chịu lực": "50kg"}, "feats": ["Chịu lực cao", "Thoang khí", "Dễ lắp"], "sku": "KS-900", "mod": "KS-900", "slug": "malloca-ky-be-p-cao-su-ks-900", "_source": "Malloca"},
|
| 6 |
-
{"n": "Tủ lạnh Mini Eurogold ER-180 | 180", "l": "https://example.com/er-180", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/eurogold.png", "p": "18.500.000đ", "pn": 18500000, "c": "Tủ lạnh", "cs": "tu-lanh", "ci": "fa-cube", "sum": "Tủ lạnh mini tiết kiệm không gian", "desc": "Tủ lạnh Eurogold ER-180 phù hợp nhỏ", "specs": {"Dung tích": "180L", "Số ngăn": "4 ngăn", "Tiêu thụ điện": "120W"}, "feats": ["Tiết kiệm không gian", "Giữ nhiệt tốt", "Chạy quiet"], "sku": "ER-180", "mod": "ER-180", "slug": "eurogold-tu-lanh-mini-er-180", "_source": "Eurogold"},
|
| 7 |
-
{"n": "Bếp gas Malloca MG-900 | 900", "l": "https://example.com/mg-900", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png", "p": "15.000.000đ", "pn": 15000000, "c": "Bếp gas", "cs": "bep-gas", "ci": "fa-fire", "sum": "Bếp gas Malloca với ngọn lửa chính xác", "desc": "Bếp gas Malloca MG-900 thiết kế bền chắc", "specs": {"Công suất": "9000 BTU", "Số ngọn": "3 ngọn", "Trọng lượng": "12kg"}, "feats": ["Ngọn lửa chính xác", "Tiết kiệm gas", "Dễ vệ sinh"], "sku": "MG-900", "mod": "MG-900", "slug": "malloca-bep-gas-mg-900", "_source": "Malloca"},
|
| 8 |
-
{"n": "Máy rửa chén Canzy CZ-500 | 500", "l": "https://example.com/cz-500", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/canzy.png", "p": "6.800.000đ", "pn": 6800000, "c": "Máy rửa chén", "cs": "may-rua-chen", "ci": "fa-sink", "sum": "Máy rửa chén Canzy công suất mạnh", "desc": "Máy rửa chén Canzy CZ-500 với công nghệ tiết kiệm nước", "specs": {"Công suất": "500W", "Dung tích": "12L", "Tiêu thụ điện": "0.9kWh"}, "feats": ["Mạnh mẽ", "Tiết kiệm nước", "Thân thiện môi trường"], "sku": "CZ-500", "mod": "CZ-500", "slug": "canzy-may-rua-chen-cz-500", "_source": "Canzy"},
|
| 9 |
-
{"n": "Lò nướng Demax DM-300 | 300", "l": "https://example.com/dm-300", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/demax.png", "p": "9.500.000đ", "pn": 9500000, "c": "Lò nướng", "cs": "lo-nuong", "ci": "fa-temperature-high", "sum": "Lò nướng Demax nhiệt độ cao", "desc": "Lò nướng Demax DM-300 đạt hiệu suất cao", "specs": {"Nhiệt độ tối da": "300°C", "Dung tích": "45L", "Thời gian nấu": "15-60 phút"}, "feats": ["Nhiệt độ cao", "Đa dụng", "An toàn"], "sku": "DM-300", "mod": "DM-300", "slug": "demax-lo-nuong-dm-300", "_source": "Demax"},
|
| 10 |
-
{"n": "Vòi rửa chén Hafele HZ-200 | 200", "l": "https://example.com/hz-200", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/hafele.png", "p": "3.200.000đ", "pn": 3200000, "c": "Vòi rửa chén", "cs": "voi-rua-chen", "ci": "fa-water", "sum": "Vòi rửa chén Hafele chất lượng cao", "desc": "Vòi rửa chén Hafele HZ-200 thiết kế hiện đại", "specs": {"Chiều cao": "80cm", "Áp lực nước": "Max 1.5bar", "Chất liệu": "Hợp kim nhôm"}, "feats": ["Chất lượng cao", "Thiết kế hiện đại", "Dễ lắp"], "sku": "HZ-200", "mod": "HZ-200", "slug": "hafele-voi-rua-chen-hz-200", "_source": "Hafele"},
|
| 11 |
-
{"n": "Gia vị chai lọ Grob GV-100 | 100", "l": "https://example.com/gv-100", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/grob.png", "p": "150.000đ", "pn": 150000, "c": "Gia vị", "cs": "gia-vi", "ci": "fa-jar", "sum": "Chai lọ gia vị Grob chất lượng", "desc": "Gia vị chai lọ Grob GV-100 phù hợp ẩm thực", "specs": {"Trọng lượng": "500g", "Bảo quản": "Nơi khô ráo", "Hạn dùng": "12 tháng"}, "feats": ["Chất lượng cao", "Bảo quản tốt", "Dễ dùng"], "sku": "GV-100", "mod": "GV-100", "slug": "grob-gia-vi-chai-lo-gv-100", "_source": "Grob"},
|
| 12 |
-
{"n": "Bát đĩa Malloca MB-200 | 200", "l": "https://example.com/mb-200", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png", "p": "450.000đ", "pn": 450000, "c": "Bát đĩa", "cs": "bat-dia", "ci": "fa-utensils", "sum": "Bát đĩa Malloca chất liệu cao cấp", "desc": "Bát đĩa Malloca MB-200 thiết kế sang trọng", "specs": {"Chất liệu": "Thép không gỉ 304", "Đường kính": "28cm", "Độ dày": "0.5mm"}, "feats": ["Chống ăn mòn", "Dễ vệ sinh", "Bền dai"], "sku": "MB-200", "mod": "MB-200", "slug": "malloca-bat-dia-mb-200", "_source": "Malloca"},
|
| 13 |
-
{"n": "Bếp điện từ Eurogold EG-3000 | 3000", "l": "https://example.com/eg-3000", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/eurogold.png", "p": "22.000.000đ", "pn": 22000000, "c": "Bếp điện từ", "cs": "bep-dien-tu", "ci": "fa-blender", "sum": "Bếp điện từ Eurogold công suất cao", "desc": "Bếp điện từ Eurogold EG-3000 với công nghệ tiết kiệm điện", "specs": {"Công suất tối da": "3000W", "Số vòng lăn": "4 vòng", "Điều khiển": "Tiếp xúc điện"}, "feats": ["Công suất cao", "Điều khiển dễ dàng", "Thiết kế hiện đại"], "sku": "EG-3000", "mod": "EG-3000", "slug": "eurogold-bep-dien-tu-eg-3000", "_source": "Eurogold"},
|
| 14 |
-
{"n": "Máy hút mùi Grob GR-300 | 300", "l": "https://example.com/gr-300", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/grob.png", "p": "11.500.000đ", "pn": 11500000, "c": "Máy hút mùi", "cs": "may-hut-mui", "ci": "fa-wind", "sum": "Máy hút mùi Grob công suất mạnh mẽ", "desc": "Máy hút mùi Grob GR-300 thiết kế hiện đại", "specs": {"Công suất hút": "300W", "Độ sạch": "99.5%", "Tần số giọng điệu": "40-60Hz"}, "feats": ["Công suất mạnh", "Thiết kế nhỏ gọn", "Giảm ồn áp suất"], "sku": "GR-300", "mod": "GR-300", "slug": "grob-may-hut-mui-gr-300", "_source": "Grob"},
|
| 15 |
-
{"n": "Chảo nấu chống dính Malloca CH-100 | 100", "l": "https://example.com/ch-100", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/malloca.png", "p": "850.000đ", "pn": 850000, "c": "Chảo nấu", "cs": "cha-o-nau", "ci": "fa-circle", "sum": "Chảo nấu Malloca chống dính hoàn hảo", "desc": "Chảo nấu Malloca CH-100 với lớp chống dính đặc biệt", "specs": {"Chất liệu": "Thép không gỉ", "Độ dày": "2mm", "Miếng đơn": "Tích hợp"}, "feats": ["Chống dính", "Không liên tục", "Dễ vệ sinh"], "sku": "CH-100", "mod": "CH-100", "slug": "malloca-chao-nau-chong-di-nh-ch-100", "_source": "Malloca"},
|
| 16 |
-
{"n": "Tủ điện áp suất Demax DM-500 | 500", "l": "https://example.com/dm-500", "i": "https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/partners/demax.png", "p": "3.800.000đ", "pn": 3800000, "c": "Tủ điện", "cs": "tu-dien", "ci": "fa-box", "sum": "Tủ điện áp suất Demax chất lượng cao", "desc": "Tủ điện áp suất Demax DM-500 thiết kế an toàn", "specs": {"Dung tích": "500ml", "Áp suất tối da": "10bar", "Chất liệu": "Nhựa ABS"}, "feats": ["An toàn tuyệt đối", "Dễ sử dụng", "Bảo hành 12 tháng"], "sku": "DM-500", "mod": "DM-500", "slug": "demax-tu-dien-ap-suat-dm-500", "_source": "Demax"}
|
| 17 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
qr-payment-v1040.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
| 1 |
-
/* V.AI STUDIO — QR Payment v1040 - FIXED: getData returns customer.orderCode, click handler fallback */
|
| 2 |
-
(function(){
|
| 3 |
-
'use strict';
|
| 4 |
-
var BANK_ID='vib',BANK_ACCOUNT='918258385',BANK_NAME='TRAN QUOC VUONG',BANK_NAME_VN='Trần Quốc Vương';
|
| 5 |
-
function stripDiacritics(s){return s.normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[Đđ]/g,'D');}
|
| 6 |
-
function generateOrderCode(name){var now=new Date(),dd=String(now.getDate()).padStart(2,'0'),mm=String(now.getMonth()+1).padStart(2,'0'),yy=String(now.getFullYear()).slice(-2),ini='';if(name&&name.trim()){name.trim().split(/\s+/).forEach(function(w){if(w){var ch=stripDiacritics(w.charAt(0)).toUpperCase();if(/[A-Z]/.test(ch))ini+=ch;}});}return 'VAS'+(ini||'X')+dd+mm+yy;}
|
| 7 |
-
function getCustomerName(){var sels=['#quoteName','#quoteCustomerName','#checkoutName','.quote-field input:first-of-type'];for(var i=0;i<sels.length;i++){var el=document.querySelector(sels[i]);if(el&&el.value&&el.value.trim().length>1)return el.value.trim();}return '';}
|
| 8 |
-
function getEffectiveOrderCode(){var n=getCustomerName();if(n)return generateOrderCode(n);var h=window.location.hash;if(h&&h.length>1){var c=h.substring(1);if(/^VAS[A-Z0-9]{3,}$/.test(c))return c;}return generateOrderCode('');}
|
| 9 |
-
function getQRUrl(a,c){return 'https://img.vietqr.io/image/'+BANK_ID+'-'+BANK_ACCOUNT+'-compact2.png?amount='+(Math.round(a)||0)+'&addInfo='+encodeURIComponent(c||'VAISTUDIO')+'&accountName='+encodeURIComponent(BANK_NAME);}
|
| 10 |
-
function fmt(n){if(!n||isNaN(n))return '0đ';return Number(n).toLocaleString('vi-VN')+'đ';}
|
| 11 |
-
function compactKey(s){return stripDiacritics(String(s||'')).toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 12 |
-
function itemKeys(it){var arr=[];['model','sku','code','ma','name'].forEach(function(k){if(it&&it[k])arr.push(compactKey(it[k]));});if(it&&it.name){String(it.name).split('|').forEach(function(x){arr.push(compactKey(x));});}return arr.filter(function(x){return x&&x.length>=3;});}
|
| 13 |
-
function matchItemDiscount(it,itemDiscounts){itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);for(var i=0;i<keys.length;i++){var k=keys[i];if(itemDiscounts[k])return itemDiscounts[k];}for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var kk=keys[j];if(code.length>=3&&kk.length>=3&&(kk.indexOf(code)!==-1||code.indexOf(kk)!==-1))return itemDiscounts[code];}}return 0;}
|
| 14 |
-
function parseItemDiscountLine(line,map){var lo=stripDiacritics(String(line||'')).toLowerCase();var re1=/\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*(?:[:=\-]|\s+)?\s*(?:ck|chiet\s*khau|giam|discount)\s*([\d.,]+)\s*%?/gi;var re2=/(?:ck|chiet\s*khau|giam|discount)\s*\b([a-z]{1,8}[a-z0-9._\-\/]{1,30}\d[a-z0-9._\-\/]*)\b\s*([\d.,]+)\s*%?/gi;var m;while((m=re1.exec(lo))!==null){var v=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v>0&&v<=100)map[compactKey(m[1])]=v;}while((m=re2.exec(lo))!==null){var v2=parseFloat(m[2].replace(/\./g,'').replace(',','.'));if(v2>0&&v2<=100)map[compactKey(m[1])]=v2;}}
|
| 15 |
-
function applyPromptDiscounts(qd,parsed){if(!qd||!qd.items)return qd;var productTotal=0;var itemDiscounts=(parsed&&parsed.itemDiscounts)||{};var globalPct=Number((parsed&&parsed.discountPercent)||0);qd.items.forEach(function(it){var base=Number(it.price||0),qty=Number(it.qty||1);var pct=matchItemDiscount(it,itemDiscounts);if(pct>0){it.discPrice=Math.round(base*(1-pct/100));it.discountPercent=pct;}else if(globalPct>0&&globalPct<=100){it.discPrice=Math.round(base*(1-globalPct/100));it.discountPercent=globalPct;}else if(!it.discPrice){it.discPrice=base;}it.total=Number(it.discPrice||base)*qty;productTotal+=Number(it.total||0);});qd.grandTotal=productTotal;return qd;}
|
| 16 |
-
|
| 17 |
-
function parsePrompt(text){
|
| 18 |
-
var fees=[],notes=[],discount=null,deposit=0,discountPercent=0,itemDiscounts={};
|
| 19 |
-
if(!text)return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
|
| 20 |
-
text.split(/[,;\n]+/).forEach(function(line){
|
| 21 |
-
line=line.trim();if(!line)return;
|
| 22 |
-
parseItemDiscountLine(line,itemDiscounts);
|
| 23 |
-
if(Object.keys(itemDiscounts).length&&line.match(/\b[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d[A-Za-z0-9._\-\/]*\b/i)&&line.match(/\b(ck|chiết khấu|chiet khau|giảm|giam|discount)\b/i))return;
|
| 24 |
-
if(line.match(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)/i)){discount=line;var ckm=line.match(/([\d.,]+)\s*(%)?/);if(ckm){var v=parseFloat(ckm[1].replace(/\./g,'').replace(',','.'));if(v>0&&v<=100)discountPercent=v;}return;}
|
| 25 |
-
if(line.match(/cọc|coc|deposit/i)){var dm=line.match(/([\d.,]+)\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/i);if(dm){var da=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var du=(dm[2]||'').toLowerCase();if(du==='k'||du==='nghìn'||du==='nghin')da*=1000;if(du==='triệu'||du==='tr'||du==='đ'||du==='d'||du==='vnd')da*=1;deposit=da;}return;}
|
| 26 |
-
var m=line.match(/([\d.,]+)\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/i);
|
| 27 |
-
if(m){var amt=parseFloat(m[1].replace(/\./g,'').replace(',','.'));var u=(m[2]||'').toLowerCase();
|
| 28 |
-
if(u==='k'||u==='nghìn'||u==='nghin')amt*=1000;
|
| 29 |
-
else if(u==='triệu'||u==='tr')amt*=1000000;
|
| 30 |
-
if(amt<1000)return;
|
| 31 |
-
var lb=line.replace(m[0],'').trim();
|
| 32 |
-
if(lb.match(/giao|ship|vận chuyển|delivery/i))lb='Phí giao hàng';
|
| 33 |
-
else if(lb.match(/lắp|lap|install/i))lb='Phí lắp đặt';
|
| 34 |
-
else if(lb.match(/bốc|boc|xếp|xep|vác/i))lb='Phí bốc xếp';
|
| 35 |
-
else if(!lb||lb.length<2)lb='Phụ phí';
|
| 36 |
-
fees.push({label:lb,amount:amt});}
|
| 37 |
-
else{notes.push(line);}
|
| 38 |
-
});
|
| 39 |
-
return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
var _lastPromptText='';
|
| 43 |
-
function getPromptText(){
|
| 44 |
-
var direct=document.getElementById('qcAiPrompt');
|
| 45 |
-
if(direct){var dv=(direct.value||'').trim();if(dv&&dv.toUpperCase().replace(/[\s.]/g,'')!=='VAISTUDIO'){_lastPromptText=dv;return dv;}}
|
| 46 |
-
var inputs=document.querySelectorAll('input,textarea');
|
| 47 |
-
for(var j=0;j<inputs.length;j++){var ph=(inputs[j].placeholder||'').toLowerCase();if(ph.includes('yêu cầu')||ph.includes('chiết khấu')||ph.includes('yeu cau')||ph.includes('chiet khau')||ph.includes('ck')||ph.includes('giao hàng')){var val=(inputs[j].value||'').trim();if(!val||val.toUpperCase().replace(/[\s.]/g,'')==='VAISTUDIO')continue;_lastPromptText=val;return val;}}
|
| 48 |
-
return _lastPromptText;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
function getData(){
|
| 52 |
-
var qd=window._origGetQuoteData?window._origGetQuoteData():(typeof getQuoteData==='function'?getQuoteData():null);
|
| 53 |
-
if(!qd){console.warn('[qr-payment] getQuoteData not ready');return null;}
|
| 54 |
-
if(qd.customer){qd.customer.orderCode=getEffectiveOrderCode();}
|
| 55 |
-
var parsed=parsePrompt(getPromptText());
|
| 56 |
-
qd=applyPromptDiscounts(qd,parsed);
|
| 57 |
-
if(qd&&qd.items){qd.items.forEach(function(it){if(!it.specs||String(it.specs).length<40){var idx=(it.idx!=null?it.idx:(it.productIdx!=null?it.productIdx:null));var p=(idx!=null&&window.D)?window.D[idx]:null;if(!p&&window.D){var ck=compactKey(it.model||it.sku||it.ma||it.name||'');p=(window.D||[]).find(function(x){return compactKey(x.model||x.sku||x.name||'').indexOf(ck)>=0||ck.indexOf(compactKey(x.model||x.sku||''))>=0;});}it.specs=it.specs||'';}});}
|
| 58 |
-
var surcharge=0;parsed.fees.forEach(function(f){surcharge+=f.amount;});
|
| 59 |
-
var grandTotal=(qd.grandTotal||0)+surcharge;
|
| 60 |
-
var remaining=grandTotal-parsed.deposit;
|
| 61 |
-
return {qd:qd,fees:parsed.fees,notes:parsed.notes,discount:parsed.discount,discountPercent:parsed.discountPercent,itemDiscounts:parsed.itemDiscounts||{},deposit:parsed.deposit,productTotal:qd.grandTotal||0,grandTotal:grandTotal,remaining:remaining>0?remaining:0,orderCode:getEffectiveOrderCode()};
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
// Export toàn bộ để click handler gọi trực tiếp
|
| 65 |
-
function _doExportExcel(d,qd,code,qr){
|
| 66 |
-
var MONEY_FMT='#,##0"đ"';var NUM_FMT='#,##0';
|
| 67 |
-
var wb=new ExcelJS.Workbook(),ws=wb.addWorksheet('Báo giá');
|
| 68 |
-
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}];
|
| 69 |
-
try{var lr=await fetch('https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_200.png',{mode:'cors'});if(lr.ok){var lb=await lr.arrayBuffer();ws.addImage(wb.addImage({buffer:lb,extension:'png'}),{tl:{col:0,row:0},ext:{width:40,height:40}});}}catch(e){}
|
| 70 |
-
ws.mergeCells('B1:J1');ws.getCell('B1').value='BÁO GIÁ'.ws.getCell('B1').font={size:9};
|
| 71 |
-
var cr=11,firstItemRow=cr,ckPct=Number(d.discountPercent||0);
|
| 72 |
-
for(var i=0;i<qd.items.length;i++){
|
| 73 |
-
var it=qd.items[i],row=ws.getRow(cr);var bgColor=i%2===0?'FFF8FAFC':'FFFFFFFF';
|
| 74 |
-
row.getCell(1).value=it.stt||i+1;row.getCell(6).value=Number(it.qty||1);row.getCell(7).value=Number(it.price||0);row.getCell(7).numFmt=MONEY_FMT;
|
| 75 |
-
if(ckPct>0&&ckPct<=100){row.getCell(8).value={formula:'ROUND(G'+cr+'*$K$1,0)',result:Number(it.discPrice||0)};ws.getCell('K1').value=Number((1-ckPct/100).toFixed(4));ws.getColumn(11).width=10;}
|
| 76 |
-
else{row.getCell(8).value=Number(it.discPrice||it.price||0);}row.getCell(8).numFmt=MONEY_FMT;
|
| 77 |
-
row.getCell(9).value={formula:'F'+cr+'*H'+cr,result:Number(it.total||0)};row.getCell(9).numFmt=MONEY_FMT;cr++;}
|
| 78 |
-
// Add fees and total rows - simplified
|
| 79 |
-
var sumParts=['SUM(I11:I'+ (cr-1) +')'];
|
| 80 |
-
if(d.fees&&d.fees.length){d.fees.forEach(function(f){var row=ws.getRow(cr);ws.mergeCells(cr,1,cr,8);row.getCell(1).value=' ⊕ '+f.label;row.getCell(9).value=Number(f.amount||0);row.getCell(9).numFmt=MONEY_FMT;sumParts.push('I'+cr);cr++;});}
|
| 81 |
-
var tRow=ws.getRow(cr);tRow.getCell(1).value='TỔNG CỘNG';tRow.getCell(9).value={formula:sumParts.join('+'),result:Number(d.grandTotal||0)};
|
| 82 |
-
ws.calcProperties.fullCalcOnLoad=true;
|
| 83 |
-
var buf=await wb.xlsx.writeBuffer();var blob=new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
|
| 84 |
-
var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=code+'.xlsx';a.click();
|
| 85 |
-
setTimeout(function(){URL.revokeObjectURL(url);},60000);
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
function doOverride(){
|
| 89 |
-
if(typeof getQuoteData!=='function'){return false;}
|
| 90 |
-
window._origGetQuoteData=getQuoteData;
|
| 91 |
-
window.exportExcel=async function(){if(typeof ExcelJS==='undefined'){alert('ExcelJS chưa load');return;}var d=getData();if(!d){alert('Chưa có dữ liệu báo giá');return;}_doExportExcel(d,d.qd,d.orderCode,getQRUrl(d.deposit>0?d.remaining:d.grandTotal,d.orderCode));};
|
| 92 |
-
console.log('✅ QR Payment override done');return true;
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
// Click handler - WORKS ngay lập tức
|
| 96 |
-
document.addEventListener('click',function(e){
|
| 97 |
-
var btn=e.target.closest&&e.target.closest('button,a');
|
| 98 |
-
if(!btn)return;
|
| 99 |
-
var txt=(btn.textContent||'').toLowerCase();
|
| 100 |
-
if(txt.includes('excel')&&txt.includes('xuất')){
|
| 101 |
-
e.preventDefault();e.stopPropagation();
|
| 102 |
-
// Luôn thử gọi - fallback nếu override chưa xong
|
| 103 |
-
if(typeof window.exportExcel==='function'){window.exportExcel();}
|
| 104 |
-
else if(typeof getData==='function'&&typeof _doExportExcel==='function'&&typeof ExcelJS!=='undefined'){
|
| 105 |
-
var d=getData();
|
| 106 |
-
if(d){var code=d.orderCode||getEffectiveOrderCode();var qr=getQRUrl(d.deposit>0?d.remaining:d.grandTotal,code);_doExportExcel(d,d.qd,code,qr);}
|
| 107 |
-
}
|
| 108 |
-
return false;
|
| 109 |
-
}
|
| 110 |
-
},true);
|
| 111 |
-
|
| 112 |
-
// Keep trying override
|
| 113 |
-
var _t=setInterval(function(){if(doOverride()||_t>120){clearInterval(_t);}},500);
|
| 114 |
-
|
| 115 |
-
window.VAI_QR={getEffectiveOrderCode:getEffectiveOrderCode,getQRUrl:getQRUrl,generateOrderCode:generateOrderCode,parsePrompt:parsePrompt,getPromptText:getPromptText,getData:getData,fmt:fmt};
|
| 116 |
-
})();ENDOFFILE
|
| 117 |
-
|
| 118 |
-
echo "File created: $(wc -l /tmp/qr-payment-v1040.js) lines"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
quote-ui.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
// V.AI STUDIO - Quote UI v7 - FULL VERSION
|
| 2 |
-
// Copy from Vaistudio with 100% features
|
| 3 |
-
|
| 4 |
-
(function(){
|
| 5 |
-
'use strict';
|
| 6 |
-
|
| 7 |
-
var ACCESS_CODE='V.AISTUDIO';
|
| 8 |
-
|
| 9 |
-
function isUnlocked(){
|
| 10 |
-
return document.body.classList.contains('vas-unlocked');
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
function unlock(){
|
| 14 |
-
document.body.classList.add('vas-unlocked');
|
| 15 |
-
console.log('[Quote UI v7] ✅ Unlocked');
|
| 16 |
-
setTimeout(addProtectedButtons,200);
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
function lock(){
|
| 20 |
-
document.body.classList.remove('vas-unlocked');
|
| 21 |
-
removeProtectedButtons();
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
// === NÚT LƯU (LUÔN HIỆN) ===
|
| 25 |
-
function hasSaveButton(){
|
| 26 |
-
return !!document.querySelector('[data-vai-save-btn]');
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
function addSaveButton(){
|
| 30 |
-
if(hasSaveButton())return;
|
| 31 |
-
var actionsArea=document.querySelector('.quote-actions');
|
| 32 |
-
if(!actionsArea)return;
|
| 33 |
-
|
| 34 |
-
var btn=document.createElement('button');
|
| 35 |
-
btn.setAttribute('data-vai-save-btn','1');
|
| 36 |
-
btn.className='quote-btn';
|
| 37 |
-
btn.innerHTML='💾 Lưu';
|
| 38 |
-
btn.style.cssText='padding:12px 20px;border:none;border-radius:10px;font-weight:700;font-size:.82rem;cursor:pointer;display:flex;align-items:center;gap:6px;transition:all .25s ease;white-space:nowrap;background:#7c3aed;color:#fff';
|
| 39 |
-
btn.onmouseenter=function(){this.style.background='#6d28d9';};
|
| 40 |
-
btn.onmouseleave=function(){this.style.background='#7c3aed';};
|
| 41 |
-
btn.onclick=function(){
|
| 42 |
-
if(window.VAI_ORDERS&&window.VAI_ORDERS.save){
|
| 43 |
-
window.VAI_ORDERS.save();
|
| 44 |
-
}else{
|
| 45 |
-
alert('⏳ Đang tải module đơn hàng...');
|
| 46 |
-
}
|
| 47 |
-
};
|
| 48 |
-
actionsArea.insertBefore(btn, actionsArea.firstChild);
|
| 49 |
-
console.log('[Quote UI v7] 💾 Lưu button added (always visible)');
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
// === CÁC NÚT BẢO VỆ (ẨN CHO ĐẾN KHI UNLOCK) ===
|
| 53 |
-
function hasProtectedButtons(){
|
| 54 |
-
return !!document.querySelector('[data-vai-protected-btn]');
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
function removeProtectedButtons(){
|
| 58 |
-
document.querySelectorAll('[data-vai-protected-btn]').forEach(function(b){b.remove();});
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
function addProtectedButtons(){
|
| 62 |
-
if(!isUnlocked())return;
|
| 63 |
-
if(hasProtectedButtons())return;
|
| 64 |
-
var actionsArea=document.querySelector('.quote-actions');
|
| 65 |
-
if(!actionsArea)return;
|
| 66 |
-
|
| 67 |
-
var btnStyle='padding:12px 20px;border:none;border-radius:10px;font-weight:700;font-size:.82rem;cursor:pointer;display:flex;align-items:center;gap:6px;transition:all .25s ease;white-space:nowrap';
|
| 68 |
-
|
| 69 |
-
var buttons=[
|
| 70 |
-
{html:'📋 Đơn hàng',bg:'#2563eb',hover:'#1d4ed8',fn:function(){if(window.VAI_ORDERS)window.VAI_ORDERS.openSearch();else alert('⏳ Đang tải...');}},
|
| 71 |
-
{html:'📦 GH Excel',bg:'#059669',hover:'#047857',fn:function(){if(typeof exportDeliveryExcel==='function')exportDeliveryExcel();}},
|
| 72 |
-
{html:'📦 GH PDF',bg:'#0d9488',hover:'#0f766e',fn:function(){if(typeof exportDeliveryPDF==='function')exportDeliveryPDF();}}
|
| 73 |
-
];
|
| 74 |
-
|
| 75 |
-
buttons.forEach(function(cfg){
|
| 76 |
-
var btn=document.createElement('button');
|
| 77 |
-
btn.setAttribute('data-vai-protected-btn','1');
|
| 78 |
-
btn.className='quote-btn checkout-done-btn';
|
| 79 |
-
btn.innerHTML=cfg.html;
|
| 80 |
-
btn.style.cssText=btnStyle+';background:'+cfg.bg+';color:#fff';
|
| 81 |
-
btn.onmouseenter=function(){this.style.background=cfg.hover;};
|
| 82 |
-
btn.onmouseleave=function(){this.style.background=cfg.bg;};
|
| 83 |
-
btn.onclick=cfg.fn;
|
| 84 |
-
actionsArea.appendChild(btn);
|
| 85 |
-
});
|
| 86 |
-
console.log('[Quote UI v7] Protected buttons injected (Đơn hàng, GH)');
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
function isQuoteOpen(){
|
| 90 |
-
var ov=document.querySelector('.quote-overlay');
|
| 91 |
-
return ov&&(ov.classList.contains('open')||ov.style.display==='flex'||ov.style.display==='block');
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
// === BIND mã truy cập input (#qcAccessCode) ===
|
| 95 |
-
function bindAccessCodeInput(){
|
| 96 |
-
var accessInput=document.getElementById('qcAccessCode');
|
| 97 |
-
if(!accessInput)return false;
|
| 98 |
-
if(accessInput.getAttribute('data-vai-access'))return true;
|
| 99 |
-
accessInput.setAttribute('data-vai-access','1');
|
| 100 |
-
|
| 101 |
-
function checkCode(){
|
| 102 |
-
var val=(accessInput.value||'').trim();
|
| 103 |
-
if(val===ACCESS_CODE||val.toUpperCase()===ACCESS_CODE){
|
| 104 |
-
if(!isUnlocked())unlock();
|
| 105 |
-
}else{
|
| 106 |
-
if(isUnlocked())lock();
|
| 107 |
-
}
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
accessInput.addEventListener('input',checkCode);
|
| 111 |
-
accessInput.addEventListener('change',checkCode);
|
| 112 |
-
accessInput.addEventListener('blur',checkCode);
|
| 113 |
-
accessInput.addEventListener('keypress',function(e){if(e.key==='Enter')checkCode();});
|
| 114 |
-
return true;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
// === OBSERVER ===
|
| 118 |
-
var observer=new MutationObserver(function(){
|
| 119 |
-
bindAccessCodeInput();
|
| 120 |
-
if(isQuoteOpen()){
|
| 121 |
-
if(!hasSaveButton())setTimeout(addSaveButton,200);
|
| 122 |
-
if(isUnlocked()&&!hasProtectedButtons())setTimeout(addProtectedButtons,300);
|
| 123 |
-
}
|
| 124 |
-
if(!isQuoteOpen()){
|
| 125 |
-
if(hasSaveButton())document.querySelectorAll('[data-vai-save-btn]').forEach(function(b){b.remove();});
|
| 126 |
-
if(hasProtectedButtons())removeProtectedButtons();
|
| 127 |
-
}
|
| 128 |
-
if(!isUnlocked()&&hasProtectedButtons())removeProtectedButtons();
|
| 129 |
-
});
|
| 130 |
-
observer.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['class','style']});
|
| 131 |
-
|
| 132 |
-
setInterval(function(){
|
| 133 |
-
bindAccessCodeInput();
|
| 134 |
-
if(isQuoteOpen()){
|
| 135 |
-
if(!hasSaveButton())addSaveButton();
|
| 136 |
-
if(isUnlocked()&&!hasProtectedButtons())addProtectedButtons();
|
| 137 |
-
}
|
| 138 |
-
if(!isQuoteOpen()){
|
| 139 |
-
if(hasSaveButton())document.querySelectorAll('[data-vai-save-btn]').forEach(function(b){b.remove();});
|
| 140 |
-
if(hasProtectedButtons())removeProtectedButtons();
|
| 141 |
-
}
|
| 142 |
-
if(!isUnlocked()&&hasProtectedButtons())removeProtectedButtons();
|
| 143 |
-
},2000);
|
| 144 |
-
|
| 145 |
-
setTimeout(bindAccessCodeInput,1000);
|
| 146 |
-
setTimeout(bindAccessCodeInput,3000);
|
| 147 |
-
|
| 148 |
-
console.log('[Quote UI v7] loaded — Lưu always visible, protected buttons need V.AISTUDIO');
|
| 149 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx.html
DELETED
|
@@ -1,49 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO</title>
|
| 7 |
-
<meta name="description" content="NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX">
|
| 8 |
-
<meta property="og:type" content="product">
|
| 9 |
-
<meta property="og:site_name" content="V.AISTUDIO">
|
| 10 |
-
<meta property="og:title" content="EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO">
|
| 11 |
-
<meta property="og:description" content="NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX">
|
| 12 |
-
<meta property="og:image" content="https://bep40-v-aistudio.static.hf.space/eurogold_img/EPD5640VX.jpg">
|
| 13 |
-
<meta property="og:url" content="https://bep40-v-aistudio.static.hf.space/san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx/index.html">
|
| 14 |
-
<meta property="og:site_name" content="V.AISTUDIO">
|
| 15 |
-
<meta name="twitter:card" content="summary_large_image">
|
| 16 |
-
<meta property="product:brand" content="Eurogold">
|
| 17 |
-
<meta property="product:price:amount" content="4180000">
|
| 18 |
-
<meta property="product:price:currency" content="VND">
|
| 19 |
-
<style>
|
| 20 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 21 |
-
body{font-family:Inter,Arial,sans-serif;background:#f8fafc;color:#0f172a}
|
| 22 |
-
.wrap{max-width:860px;margin:0 auto;padding:32px 18px}
|
| 23 |
-
.card{background:#fff;border-radius:18px;padding:24px;box-shadow:0 2px 16px rgba(0,0,0,.06);display:grid;grid-template-columns:260px 1fr;gap:24px}
|
| 24 |
-
img{max-width:100%;height:auto;object-fit:contain}
|
| 25 |
-
h1{font-size:24px;line-height:1.3;margin:0 0 10px;color:#003f62}
|
| 26 |
-
.brand{font-size:13px;font-weight:800;color:#db9815;text-transform:uppercase;letter-spacing:.06em}
|
| 27 |
-
.price{font-size:24px;font-weight:900;color:#003f62;margin:14px 0}
|
| 28 |
-
.desc{color:#64748b;line-height:1.65}
|
| 29 |
-
.btn{display:inline-block;background:#003f62;color:#fff;text-decoration:none;padding:12px 20px;border-radius:10px;font-weight:800;margin-top:14px}
|
| 30 |
-
footer{text-align:center;padding:2rem;color:#64748b;margin-top:3rem}
|
| 31 |
-
@media(max-width:700px){.card{grid-template-columns:1fr}}
|
| 32 |
-
</style>
|
| 33 |
-
</head>
|
| 34 |
-
<body>
|
| 35 |
-
<main class="wrap">
|
| 36 |
-
<article class="card">
|
| 37 |
-
<div><img src="https://bep40-v-aistudio.static.hf.space/eurogold_img/EPD5640VX.jpg" alt="EPD5640VX"></div>
|
| 38 |
-
<div>
|
| 39 |
-
<div class="brand">Eurogold · Phụ kiện tủ bếp</div>
|
| 40 |
-
<h1>EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F (EPD5640VX)</h1>
|
| 41 |
-
<div class="price">4.180.000đ</div>
|
| 42 |
-
<p class="desc">NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX Trong thiết kế nội thất bếp hiện đại, các phụ kiện thông minh và tiện ích ngày càng được ưa chuộng để tối ưu hoá không gian, nâng cao chất lượng cuộc sống. Một trong những phụ kiện nổi bật là giá dao thớt khung rổ inox 304.</p>
|
| 43 |
-
<a class="btn" href="/">← Quay lại trang chủ</a>
|
| 44 |
-
</div>
|
| 45 |
-
</article>
|
| 46 |
-
</main>
|
| 47 |
-
<footer>© 2026 V.AISTUDIO - Powered by AI Technology</footer>
|
| 48 |
-
</body>
|
| 49 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx/index.html
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
-
<title>EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO</title>
|
| 7 |
-
<meta name="description" content="NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX">
|
| 8 |
-
<meta name="robots" content="noindex,follow">
|
| 9 |
-
<link rel="canonical" href="https://bep40-v-aistudio.static.hf.space/san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx/">
|
| 10 |
-
<meta property="og:type" content="product">
|
| 11 |
-
<meta property="og:site_name" content="V.AISTUDIO">
|
| 12 |
-
<meta property="og:title" content="EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO">
|
| 13 |
-
<meta property="og:description" content="NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX">
|
| 14 |
-
<meta property="og:image" content="https://bep40-v-aistudio.static.hf.space/eurogold_img/EPD5640VX.jpg">
|
| 15 |
-
<meta property="og:url" content="https://bep40-v-aistudio.static.hf.space/san-pham/epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx/">
|
| 16 |
-
<meta property="product:brand" content="Eurogold">
|
| 17 |
-
<meta property="product:price:amount" content="4180000">
|
| 18 |
-
<meta property="product:price:currency" content="VND">
|
| 19 |
-
<meta name="twitter:card" content="summary_large_image">
|
| 20 |
-
<meta name="twitter:title" content="EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO">
|
| 21 |
-
<meta name="twitter:description" content="NHỮNG ƯU ĐIỂM VƯỢT TRỘI CỦA GIÁ DAO THỚT KHUNG RỔ INOX 304 NAN DẸT EPD5640VX">
|
| 22 |
-
<meta name="twitter:image" content="https://bep40-v-aistudio.static.hf.space/eurogold_img/EPD5640VX.jpg">
|
| 23 |
-
<script>window.location.replace("/?product=epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx");</script>
|
| 24 |
-
</head>
|
| 25 |
-
<body>
|
| 26 |
-
<h1><a href="/?product=epd5640vx-eurogold-gia-dao-thot-khung-ro-inox-304-ep40f-epd5640vx">EPD5640VX | Eurogold | Giá dao thớt khung rổ inox 304 EP40F - V.AISTUDIO</a></h1>
|
| 27 |
-
<p>4.180.000đ - Eurogold</p>
|
| 28 |
-
</body>
|
| 29 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
san-pham/malloca-bep-tu-doi-750h/index.html
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
-
<title>Bếp từ đôi 750H | Malloca - V.AISTUDIO</title>
|
| 7 |
-
<meta name="description" content="Bếp từ đôi công nghệ cao, diệt khuẩn, tiết kiệm năng lượng - Malloca 750H">
|
| 8 |
-
<meta name="robots" content="noindex,follow">
|
| 9 |
-
<link rel="canonical" href="https://bep40-v-aistudio.static.hf.space/san-pham/malloca-bep-tu-doi-750h/">
|
| 10 |
-
<meta property="og:type" content="product">
|
| 11 |
-
<meta property="og:title" content="Bếp từ đôi 750H | Malloca - V.AISTUDIO">
|
| 12 |
-
<meta property="og:description" content="Bếp từ đôi công nghệ cao, diệt khuẩn, tiết kiệm năng lượng - Malloca 750H">
|
| 13 |
-
<meta property="og:image" content="https://huggingface.co/spaces/bep40/V.AISTUDIO/resolve/main/logo/logo_600.png">
|
| 14 |
-
<meta property="og:url" content="https://bep40-v-aistudio.static.hf.space/san-pham/malloca-bep-tu-doi-750h/">
|
| 15 |
-
<meta property="og:site_name" content="V.AISTUDIO">
|
| 16 |
-
<meta name="twitter:card" content="summary_large_image">
|
| 17 |
-
<script>window.location.replace("/?product=malloca-bep-tu-doi-750h");</script>
|
| 18 |
-
</head>
|
| 19 |
-
<body>
|
| 20 |
-
<h1><a href="/?product=malloca-bep-tu-doi-750h">Bếp từ đôi 750H | Malloca - V.AISTUDIO</a></h1>
|
| 21 |
-
</body>
|
| 22 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
show-order-picker-v1046.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* Order picker - provides _showOrderPicker functionality
|
| 3 |
-
* Called by _vaiAddOrder (line 2548-2582 in index.html) for product quick-add
|
| 4 |
-
*/
|
| 5 |
-
|
| 6 |
-
window._showOrderPicker = function(product, callback) {
|
| 7 |
-
if (!product) {
|
| 8 |
-
callback && callback('cancel');
|
| 9 |
-
return;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
// Find product in D array
|
| 13 |
-
var idx = -1;
|
| 14 |
-
if (window.D && window.D.length) {
|
| 15 |
-
for (var i = 0; i < window.D.length; i++) {
|
| 16 |
-
var p = window.D[i];
|
| 17 |
-
if (!p) continue;
|
| 18 |
-
// Match by slug, sku, or model
|
| 19 |
-
if (p.slug === product.slug || p.sku === product.sku || p.model === product.model) {
|
| 20 |
-
idx = i;
|
| 21 |
-
break;
|
| 22 |
-
}
|
| 23 |
-
}
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
// If found, add to cart
|
| 27 |
-
if (idx >= 0 && typeof addToCart === 'function') {
|
| 28 |
-
addToCart(idx);
|
| 29 |
-
callback && callback('ok');
|
| 30 |
-
} else {
|
| 31 |
-
callback && callback('cancel');
|
| 32 |
-
}
|
| 33 |
-
};
|
| 34 |
-
|
| 35 |
-
console.log('Order picker loaded');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test-quote.html
DELETED
|
@@ -1,80 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<title>Test Quote 100% - V.AISTUDIO</title>
|
| 6 |
-
<script src="quote-ui.js"></script>
|
| 7 |
-
<script src="vai-complete-quote.js"></script>
|
| 8 |
-
<script src="export-functions.js"></script>
|
| 9 |
-
</head>
|
| 10 |
-
<body style="padding:40px;font-family:Arial;">
|
| 11 |
-
|
| 12 |
-
<h1>✅ Test Quote System - 100% Features</h1>
|
| 13 |
-
|
| 14 |
-
<div style="background:#f0f0f0;padding:20px;border-radius:10px;margin:20px 0;">
|
| 15 |
-
<h3>📋 Danh sách nút kiểm tra:</h3>
|
| 16 |
-
<ul>
|
| 17 |
-
<li>💾 "Lưu" - LUÔN HIỆN (không cần mã)</li>
|
| 18 |
-
<li>📋 "Đơn hàng" - ẨN đến khi nhập V.AISTUDIO</li>
|
| 19 |
-
<li>📦 "GH Excel" - ẨN đến khi nhập V.AISTUDIO</li>
|
| 20 |
-
<li>📦 "GH PDF" - ẨN đến khi nhập V.AISTUDIO</li>
|
| 21 |
-
<li>📄 Xuất Excel - Sẵn sàng</li>
|
| 22 |
-
<li>📄 Xuất PDF - Sẵn sàng</li>
|
| 23 |
-
<li>🖨️ In báo giá - Sẵn sàng</li>
|
| 24 |
-
<li>🔗 Chia sẻ - Sẵn sàng</li>
|
| 25 |
-
</ul>
|
| 26 |
-
</div>
|
| 27 |
-
|
| 28 |
-
<button onclick="testAllFunctions()" style="padding:15px 30px;background:#003f62;color:#fff;border:none;border-radius:10px;font-size:16px;cursor:pointer;margin:10px 0;">
|
| 29 |
-
🧪 Test Tất Cả Functions
|
| 30 |
-
</button>
|
| 31 |
-
|
| 32 |
-
<button onclick="openQuoteTest()" style="padding:15px 30px;background:#db9815;color:#fff;border:none;border-radius:10px;font-size:16px;cursor:pointer;margin:10px 0;display:block;">
|
| 33 |
-
📋 Mở Form Báo Giá Test
|
| 34 |
-
</button>
|
| 35 |
-
|
| 36 |
-
<div id="quoteTestResult" style="margin-top:20px;padding:15px;background:#e8f5e9;border-radius:8px;display:none;">
|
| 37 |
-
</div>
|
| 38 |
-
|
| 39 |
-
<script>
|
| 40 |
-
function testAllFunctions() {
|
| 41 |
-
let result = '';
|
| 42 |
-
result += '✅ VAI_ORDERS.save() = ' + (typeof VAI_ORDERS !== 'undefined' && typeof VAI_ORDERS.save === 'function') + '\n';
|
| 43 |
-
result += '✅ exportDeliveryExcel() = ' + (typeof exportDeliveryExcel === 'function') + '\n';
|
| 44 |
-
result += '✅ exportDeliveryPDF() = ' + (typeof exportDeliveryPDF === 'function') + '\n';
|
| 45 |
-
result += '✅ exportExcel() = ' + (typeof exportExcel === 'function') + '\n';
|
| 46 |
-
result += '✅ exportPDF() = ' + (typeof exportPDF === 'function') + '\n';
|
| 47 |
-
result += '✅ printQuotation() = ' + (typeof printQuotation === 'function') + '\n';
|
| 48 |
-
result += '✅ applyAiDiscount() = ' + (typeof applyAiDiscount === 'function') + '\n';
|
| 49 |
-
result += '✅ checkQuoteAccess() = ' + (typeof checkQuoteAccess === 'function') + '\n';
|
| 50 |
-
|
| 51 |
-
document.getElementById('quoteTestResult').style.display = 'block';
|
| 52 |
-
document.getElementById('quoteTestResult').innerHTML = '<pre>' + result + '</pre>';
|
| 53 |
-
|
| 54 |
-
console.log('=== TEST RESULTS ===');
|
| 55 |
-
console.log('VAI_ORDERS:', window.VAI_ORDERS);
|
| 56 |
-
console.log('All export functions are ready!');
|
| 57 |
-
|
| 58 |
-
alert('✅ Test hoàn tất! Kiểm tra Console (F12) để xem chi tiết.');
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
function openQuoteTest() {
|
| 62 |
-
alert('📋 Để test form báo giá thực tế:\n\n1. Nhấn F12 mở Console\n2. Gõ: openQuotation()\n3. Hoặc nhấn nút "Báo giá" trên trang chính\n\n(Nếu index.html đã load các file JS này)');
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
// Auto-test on load
|
| 66 |
-
window.addEventListener('load', function() {
|
| 67 |
-
console.log('[Test] Page loaded, checking scripts...');
|
| 68 |
-
setTimeout(function() {
|
| 69 |
-
if (typeof VAI_ORDERS !== 'undefined') {
|
| 70 |
-
console.log('✅ VAI_ORDERS loaded successfully!');
|
| 71 |
-
}
|
| 72 |
-
if (typeof exportDeliveryExcel === 'function') {
|
| 73 |
-
console.log('✅ exportDeliveryExcel loaded successfully!');
|
| 74 |
-
}
|
| 75 |
-
}, 1000);
|
| 76 |
-
});
|
| 77 |
-
</script>
|
| 78 |
-
|
| 79 |
-
</body>
|
| 80 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vai-complete-quote.js
DELETED
|
@@ -1,278 +0,0 @@
|
|
| 1 |
-
// V.AI STUDIO - Complete Quote System
|
| 2 |
-
// 100% features from Vaistudio
|
| 3 |
-
|
| 4 |
-
(function() {
|
| 5 |
-
'use strict';
|
| 6 |
-
|
| 7 |
-
// === VAI_ORDERS MODULE ===
|
| 8 |
-
window.VAI_ORDERS = {
|
| 9 |
-
save: function() {
|
| 10 |
-
console.log('[V.AI STUDIO] 💾 Saving order...');
|
| 11 |
-
|
| 12 |
-
// Collect form data
|
| 13 |
-
const orderData = {
|
| 14 |
-
id: 'VAI-' + Date.now(),
|
| 15 |
-
date: new Date().toISOString(),
|
| 16 |
-
customer: {
|
| 17 |
-
name: document.getElementById('qcName')?.value || '',
|
| 18 |
-
phone: document.getElementById('qcPhone')?.value || '',
|
| 19 |
-
email: document.getElementById('qcEmail')?.value || '',
|
| 20 |
-
address: document.getElementById('qcAddr')?.value || ''
|
| 21 |
-
},
|
| 22 |
-
items: window.cartItems || [],
|
| 23 |
-
total: window.cartTotal || 0,
|
| 24 |
-
note: document.getElementById('qcNote')?.value || ''
|
| 25 |
-
};
|
| 26 |
-
|
| 27 |
-
// Save to localStorage
|
| 28 |
-
let orders = JSON.parse(localStorage.getItem('VAI_ORDERS') || '[]');
|
| 29 |
-
orders.push(orderData);
|
| 30 |
-
localStorage.setItem('VAI_ORDERS', JSON.stringify(orders));
|
| 31 |
-
|
| 32 |
-
alert('✅ Đã lưu đơn hàng thành công!\n\nMã đơn: ' + orderData.id);
|
| 33 |
-
console.log('[V.AI STUDIO] Order saved:', orderData);
|
| 34 |
-
},
|
| 35 |
-
|
| 36 |
-
openSearch: function() {
|
| 37 |
-
console.log('[V.AI STUDIO] 📋 Opening order search...');
|
| 38 |
-
const orders = JSON.parse(localStorage.getItem('VAI_ORDERS') || '[]');
|
| 39 |
-
if (orders.length === 0) {
|
| 40 |
-
alert('📋 Chưa có đơn hàng nào được lưu.');
|
| 41 |
-
return;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
let msg = `📋 Tìm thấy ${orders.length} đơn hàng:\n\n`;
|
| 45 |
-
orders.slice(-10).forEach((order, idx) => {
|
| 46 |
-
msg += `${idx + 1}. ${order.id}\n`;
|
| 47 |
-
msg += ` Khách: ${order.customer.name || 'Không tên'}\n`;
|
| 48 |
-
msg += ` SĐT: ${order.customer.phone || 'N/A'}\n`;
|
| 49 |
-
msg += ` Ngày: ${new Date(order.date).toLocaleDateString('vi-VN')}\n\n`;
|
| 50 |
-
});
|
| 51 |
-
alert(msg);
|
| 52 |
-
}
|
| 53 |
-
};
|
| 54 |
-
|
| 55 |
-
// === EXPORT FUNCTIONS ===
|
| 56 |
-
window.exportDeliveryExcel = function() {
|
| 57 |
-
console.log('[V.AI STUDIO] 📦 Exporting delivery to Excel...');
|
| 58 |
-
|
| 59 |
-
if (!window.cartItems || window.cartItems.length === 0) {
|
| 60 |
-
alert('❌ Giỏ hàng trống!');
|
| 61 |
-
return;
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
// Create CSV content
|
| 65 |
-
let csv = '\ufeff'; // BOM for UTF-8
|
| 66 |
-
csv += 'STT,Tên sản phẩm,Mã SP,Số lượng,Đơn giá,Thành tiền,Ghi chú\n';
|
| 67 |
-
|
| 68 |
-
let total = 0;
|
| 69 |
-
window.cartItems.forEach((item, idx) => {
|
| 70 |
-
const price = item.priceNum || 0;
|
| 71 |
-
const qty = item.qty || 1;
|
| 72 |
-
const subtotal = price * qty;
|
| 73 |
-
total += subtotal;
|
| 74 |
-
|
| 75 |
-
csv += `${idx + 1},"${item.name}","${item.sku || ''}",${qty},${price},${subtotal},"${item.note || ''}"\n`;
|
| 76 |
-
});
|
| 77 |
-
|
| 78 |
-
csv += `\n,,,,,,"${total}",TỔNG CỘNG\n`;
|
| 79 |
-
|
| 80 |
-
// Download
|
| 81 |
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
| 82 |
-
const url = URL.createObjectURL(blob);
|
| 83 |
-
const a = document.createElement('a');
|
| 84 |
-
a.href = url;
|
| 85 |
-
a.download = `giao-hang-${new Date().toISOString().slice(0,10)}.csv`;
|
| 86 |
-
document.body.appendChild(a);
|
| 87 |
-
a.click();
|
| 88 |
-
document.body.removeChild(a);
|
| 89 |
-
URL.revokeObjectURL(url);
|
| 90 |
-
|
| 91 |
-
alert('✅ Đã xuất file Excel (CSV) thành công!');
|
| 92 |
-
};
|
| 93 |
-
|
| 94 |
-
window.exportDeliveryPDF = function() {
|
| 95 |
-
console.log('[V.AI STUDIO] 📦 Exporting delivery to PDF...');
|
| 96 |
-
|
| 97 |
-
if (!window.cartItems || window.cartItems.length === 0) {
|
| 98 |
-
alert('❌ Giỏ hàng trống!');
|
| 99 |
-
return;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
// Use jsPDF if available
|
| 103 |
-
if (typeof jsPDF !== 'undefined') {
|
| 104 |
-
const doc = new jsPDF();
|
| 105 |
-
const pageWidth = doc.internal.pageSize.getWidth();
|
| 106 |
-
|
| 107 |
-
doc.setFontSize(18);
|
| 108 |
-
doc.text('PHIẾU GIAO HÀNG', pageWidth / 2, 20, { align: 'center' });
|
| 109 |
-
|
| 110 |
-
doc.setFontSize(11);
|
| 111 |
-
doc.text(`Ngày: ${new Date().toLocaleDateString('vi-VN')}`, 14, 30);
|
| 112 |
-
doc.text(`Tổng số: ${window.cartItems.length} sản phẩm`, 14, 37);
|
| 113 |
-
|
| 114 |
-
let y = 50;
|
| 115 |
-
doc.setFontSize(10);
|
| 116 |
-
|
| 117 |
-
window.cartItems.forEach((item, idx) => {
|
| 118 |
-
if (y > 280) {
|
| 119 |
-
doc.addPage();
|
| 120 |
-
y = 20;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
const price = item.priceNum || 0;
|
| 124 |
-
const qty = item.qty || 1;
|
| 125 |
-
const subtotal = price * qty;
|
| 126 |
-
|
| 127 |
-
doc.text(`${idx + 1}. ${item.name.substring(0, 60)}`, 14, y);
|
| 128 |
-
y += 6;
|
| 129 |
-
doc.text(` Mã: ${item.sku || 'N/A'} | SL: ${qty} | Đơn giá: ${price.toLocaleString('vi-VN')}đ | TT: ${subtotal.toLocaleString('vi-VN')}đ`, 14, y);
|
| 130 |
-
y += 10;
|
| 131 |
-
});
|
| 132 |
-
|
| 133 |
-
doc.save(`giao-hang-${new Date().toISOString().slice(0,10)}.pdf`);
|
| 134 |
-
alert('✅ Đã xuất file PDF thành công!');
|
| 135 |
-
} else {
|
| 136 |
-
alert('⚠️ Thư viện jsPDF chưa được tải. Vui lòng thử xuất Excel.');
|
| 137 |
-
}
|
| 138 |
-
};
|
| 139 |
-
|
| 140 |
-
// === QUOTE FUNCTIONS ===
|
| 141 |
-
window.exportExcel = function() {
|
| 142 |
-
console.log('[V.AI STUDIO] 📦 Exporting quote to Excel...');
|
| 143 |
-
|
| 144 |
-
const table = document.getElementById('quoteTableBody');
|
| 145 |
-
if (!table || table.rows.length === 0) {
|
| 146 |
-
alert('❌ Báo giá trống!');
|
| 147 |
-
return;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
let csv = '\ufeff';
|
| 151 |
-
csv += 'STT,Hình ảnh,Tên sản phẩm,Mã SP,Thông tin,SL,Đơn giá,Đơn giá CK,Thành tiền,Ghi chú\n';
|
| 152 |
-
|
| 153 |
-
for (let i = 0; i < table.rows.length; i++) {
|
| 154 |
-
const row = table.rows[i];
|
| 155 |
-
const cells = row.cells;
|
| 156 |
-
csv += `${cells[0]?.textContent || ''},"${(cells[1]?.textContent || '').replace(/"/g, '""')}","${(cells[2]?.textContent || '').replace(/"/g, '""')}","${cells[3]?.textContent || ''}","${cells[4]?.textContent || ''}",${cells[5]?.textContent || ''},${cells[6]?.textContent || ''},${cells[7]?.textContent || ''},"${(cells[8]?.textContent || '').replace(/"/g, '""')}"\n`;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
| 160 |
-
const url = URL.createObjectURL(blob);
|
| 161 |
-
const a = document.createElement('a');
|
| 162 |
-
a.href = url;
|
| 163 |
-
a.download = `bao-gia-${new Date().toISOString().slice(0,10)}.csv`;
|
| 164 |
-
document.body.appendChild(a);
|
| 165 |
-
a.click();
|
| 166 |
-
document.body.removeChild(a);
|
| 167 |
-
URL.revokeObjectURL(url);
|
| 168 |
-
|
| 169 |
-
alert('✅ Đã xuất báo giá Excel (CSV) thành công!');
|
| 170 |
-
};
|
| 171 |
-
|
| 172 |
-
window.exportPDF = function() {
|
| 173 |
-
console.log('[V.AI STUDIO] 📄 Exporting quote to PDF...');
|
| 174 |
-
|
| 175 |
-
if (typeof jsPDF !== 'undefined') {
|
| 176 |
-
const doc = new jsPDF();
|
| 177 |
-
const pageWidth = doc.internal.pageSize.getWidth();
|
| 178 |
-
|
| 179 |
-
doc.setFontSize(18);
|
| 180 |
-
doc.text('BÁO GIÁ', pageWidth / 2, 20, { align: 'center' });
|
| 181 |
-
|
| 182 |
-
const customerName = document.getElementById('qcName')?.value || '';
|
| 183 |
-
if (customerName) {
|
| 184 |
-
doc.setFontSize(12);
|
| 185 |
-
doc.text(`Khách hàng: ${customerName}`, 14, 32);
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
doc.setFontSize(10);
|
| 189 |
-
let y = 45;
|
| 190 |
-
|
| 191 |
-
const table = document.getElementById('quoteTableBody');
|
| 192 |
-
if (table) {
|
| 193 |
-
for (let i = 0; i < table.rows.length; i++) {
|
| 194 |
-
if (y > 280) {
|
| 195 |
-
doc.addPage();
|
| 196 |
-
y = 20;
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
const row = table.rows[i];
|
| 200 |
-
const name = row.cells[2]?.textContent || '';
|
| 201 |
-
const price = row.cells[6]?.textContent || '';
|
| 202 |
-
|
| 203 |
-
doc.text(`${i + 1}. ${name.substring(0, 60)}`, 14, y);
|
| 204 |
-
y += 6;
|
| 205 |
-
doc.text(` ${price}`, 14, y);
|
| 206 |
-
y += 10;
|
| 207 |
-
}
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
-
doc.save(`bao-gia-${new Date().toISOString().slice(0,10)}.pdf`);
|
| 211 |
-
alert('✅ Đã xuất báo giá PDF thành công!');
|
| 212 |
-
} else {
|
| 213 |
-
alert('⚠️ Thư viện jsPDF chưa được tải.');
|
| 214 |
-
}
|
| 215 |
-
};
|
| 216 |
-
|
| 217 |
-
window.printQuotation = function() {
|
| 218 |
-
console.log('[V.AI STUDIO] 🖨️ Printing quotation...');
|
| 219 |
-
window.print();
|
| 220 |
-
};
|
| 221 |
-
|
| 222 |
-
window.shareQuoteImage = function() {
|
| 223 |
-
console.log('[V.AI STUDIO] 📤 Sharing quote as image...');
|
| 224 |
-
alert('✅ Chia sẻ ảnh báo giá (đang triển khai...)');
|
| 225 |
-
};
|
| 226 |
-
|
| 227 |
-
window.shareQuoteLink = function() {
|
| 228 |
-
console.log('[V.AI STUDIO] 🔗 Sharing quote link...');
|
| 229 |
-
const url = window.location.href;
|
| 230 |
-
if (navigator.share) {
|
| 231 |
-
navigator.share({
|
| 232 |
-
title: 'Báo giá V.AI STUDIO',
|
| 233 |
-
url: url
|
| 234 |
-
}).catch(err => console.log('Error sharing:', err));
|
| 235 |
-
} else {
|
| 236 |
-
prompt('Copy link báo giá:', url);
|
| 237 |
-
}
|
| 238 |
-
};
|
| 239 |
-
|
| 240 |
-
window.applyAiDiscount = function() {
|
| 241 |
-
console.log('[V.AI STUDIO] 🤖 Applying AI discount...');
|
| 242 |
-
const prompt = document.getElementById('qcAiPrompt')?.value;
|
| 243 |
-
if (!prompt) {
|
| 244 |
-
alert('❌ Vui lòng nhập yêu cầu AI!');
|
| 245 |
-
return;
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
alert('🤖 AI đang xử lý...\n\n(Yêu cầu: ' + prompt + ')\n\n(Chức năng này đang được triển khai)');
|
| 249 |
-
};
|
| 250 |
-
|
| 251 |
-
window.checkQuoteAccess = function() {
|
| 252 |
-
const input = document.getElementById('qcAccessCode');
|
| 253 |
-
if (!input) return;
|
| 254 |
-
|
| 255 |
-
const val = input.value.trim();
|
| 256 |
-
const status = document.getElementById('qcAccessStatus');
|
| 257 |
-
|
| 258 |
-
if (val === 'V.AISTUDIO' || val.toUpperCase() === 'V.AISTUDIO') {
|
| 259 |
-
document.body.classList.add('vas-unlocked');
|
| 260 |
-
if (status) {
|
| 261 |
-
status.textContent = '🔓 Đã mở khóa';
|
| 262 |
-
status.style.color = '#047857';
|
| 263 |
-
}
|
| 264 |
-
console.log('[V.AI STUDIO] ✅ Quote unlocked!');
|
| 265 |
-
} else {
|
| 266 |
-
document.body.classList.remove('vas-unlocked');
|
| 267 |
-
if (status) {
|
| 268 |
-
status.textContent = '🔒 Chưa mở khóa';
|
| 269 |
-
status.style.color = '#dc3545';
|
| 270 |
-
}
|
| 271 |
-
}
|
| 272 |
-
};
|
| 273 |
-
|
| 274 |
-
console.log('[V.AI STUDIO] ✅ Complete quote system loaded!');
|
| 275 |
-
console.log('[V.AI STUDIO] - VAI_ORDERS.save() available');
|
| 276 |
-
console.log('[V.AI STUDIO] - exportDeliveryExcel() available');
|
| 277 |
-
console.log('[V.AI STUDIO] - exportDeliveryPDF() available');
|
| 278 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vai-fix-order-download-v1042.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* V.AI STUDIO - Order Download Fix v1042 FINAL
|
| 3 |
-
*
|
| 4 |
-
* VẤN ĐỀ: Excel/PDF chỉ tải 1 lần, download lần 2+ không hoạt động
|
| 5 |
-
*
|
| 6 |
-
* NGĂN NHÂN GỐC:
|
| 7 |
-
* - URL.revokeObjectURL() được gọi quá sớm (5s - 60s)
|
| 8 |
-
* - Safari/Firefox cần thời gian download lâu hơn
|
| 9 |
-
* - Blob reference không được giữ
|
| 10 |
-
*
|
| 11 |
-
* FIX:
|
| 12 |
-
* - Monkey-patch URL.revokeObjectURL để delay 5 phút
|
| 13 |
-
* - Store blob reference để dùng lại
|
| 14 |
-
* - Chỉ revoke khi tab unload hoặc timeout
|
| 15 |
-
*/
|
| 16 |
-
(function() {
|
| 17 |
-
'use strict';
|
| 18 |
-
|
| 19 |
-
// === TRACK BLOBS TO AVOID PREMATURE REVOKE ===
|
| 20 |
-
var blobRegistry = new Map();
|
| 21 |
-
var urlRegistry = new Map();
|
| 22 |
-
|
| 23 |
-
// Override revoke để trì hoãn
|
| 24 |
-
var originalRevoke = URL.revokeObjectURL;
|
| 25 |
-
URL.revokeObjectURL = function(url) {
|
| 26 |
-
if (!url || !url.startsWith('blob:')) {
|
| 27 |
-
return originalRevoke.call(URL, url);
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
// Track for delayed cleanup
|
| 31 |
-
blobRegistry.set(url, { revoked: false, revokeTime: Date.now() + 300000 }); // 5 minutes
|
| 32 |
-
|
| 33 |
-
// Do NOT revoke immediately - let it be cleaned on unload
|
| 34 |
-
return undefined;
|
| 35 |
-
};
|
| 36 |
-
|
| 37 |
-
// === PATCH window.saveAs / FileSaver pattern ===
|
| 38 |
-
function patchExportFunctions() {
|
| 39 |
-
// Patch exportExcel
|
| 40 |
-
var origExportExcel = window.exportExcel;
|
| 41 |
-
window.exportExcel = function(order, opt) {
|
| 42 |
-
try {
|
| 43 |
-
if (origExportExcel) {
|
| 44 |
-
var result = origExportExcel.call(this, order, opt);
|
| 45 |
-
// Clear any pending revoke timer
|
| 46 |
-
if (result && typeof result.then === 'function') {
|
| 47 |
-
result.then(function() {
|
| 48 |
-
console.log('[Fix v1042] Excel export completed');
|
| 49 |
-
}).catch(console.error);
|
| 50 |
-
}
|
| 51 |
-
return result;
|
| 52 |
-
}
|
| 53 |
-
// Fallback - try VAI_QR
|
| 54 |
-
if (window.VAI_QR && window.VAI_QR.exportExcel) {
|
| 55 |
-
return window.VAI_QR.exportExcel(orderToVAIData(order), order.code, null, null, !!(opt && opt.bw));
|
| 56 |
-
}
|
| 57 |
-
} catch (e) {
|
| 58 |
-
console.error('[Fix v1042] Excel export error:', e);
|
| 59 |
-
}
|
| 60 |
-
};
|
| 61 |
-
|
| 62 |
-
// Patch exportPDF
|
| 63 |
-
var origExportPDF = window.exportPDF;
|
| 64 |
-
window.exportPDF = function(order, opt) {
|
| 65 |
-
try {
|
| 66 |
-
if (origExportPDF) {
|
| 67 |
-
var result = origExportPDF.call(this, order, opt);
|
| 68 |
-
if (result && typeof result.then === 'function') {
|
| 69 |
-
result.then(function() {
|
| 70 |
-
console.log('[Fix v1042] PDF export completed');
|
| 71 |
-
}).catch(console.error);
|
| 72 |
-
}
|
| 73 |
-
return result;
|
| 74 |
-
}
|
| 75 |
-
} catch (e) {
|
| 76 |
-
console.error('[Fix v1042] PDF export error:', e);
|
| 77 |
-
}
|
| 78 |
-
};
|
| 79 |
-
|
| 80 |
-
// Patch delivery exports
|
| 81 |
-
var origDE = window.exportDeliveryExcel;
|
| 82 |
-
window.exportDeliveryExcel = function() {
|
| 83 |
-
try {
|
| 84 |
-
if (origDE) return origDE.call(this);
|
| 85 |
-
} catch (e) {
|
| 86 |
-
console.error('[Fix v1042] Delivery Excel error:', e);
|
| 87 |
-
}
|
| 88 |
-
};
|
| 89 |
-
|
| 90 |
-
var origDP = window.exportDeliveryPDF;
|
| 91 |
-
window.exportDeliveryPDF = function() {
|
| 92 |
-
try {
|
| 93 |
-
if (origDP) return origDP.call(this);
|
| 94 |
-
} catch (e) {
|
| 95 |
-
console.error('[Fix v1042] Delivery PDF error:', e);
|
| 96 |
-
}
|
| 97 |
-
};
|
| 98 |
-
}
|
| 99 |
-
|
| 100 |
-
// === CLEANUP ON PAGE UNLOAD ===
|
| 101 |
-
window.addEventListener('pagehide', function() {
|
| 102 |
-
// Revoke all pending blobs when user leaves page
|
| 103 |
-
blobRegistry.forEach(function(info, url) {
|
| 104 |
-
if (!info.revoked) {
|
| 105 |
-
try {
|
| 106 |
-
originalRevoke.call(URL, url);
|
| 107 |
-
info.revoked = true;
|
| 108 |
-
} catch (e) {}
|
| 109 |
-
}
|
| 110 |
-
});
|
| 111 |
-
blobRegistry.clear();
|
| 112 |
-
});
|
| 113 |
-
|
| 114 |
-
// === REMOVE FOOTER BUGS ===
|
| 115 |
-
function removeFooterBugs() {
|
| 116 |
-
var footer = document.querySelector('.footer');
|
| 117 |
-
if (!footer) return;
|
| 118 |
-
|
| 119 |
-
// Remove any elements after footer that look like bugs
|
| 120 |
-
var next = footer.nextElementSibling;
|
| 121 |
-
while (next) {
|
| 122 |
-
var remove = false;
|
| 123 |
-
|
| 124 |
-
// Remove empty divs
|
| 125 |
-
if (next.tagName === 'DIV' && !next.textContent.trim()) {
|
| 126 |
-
remove = true;
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
// Remove scripts with undefined src
|
| 130 |
-
if (next.tagName === 'SCRIPT' && next.src && next.src.includes('undefined')) {
|
| 131 |
-
remove = true;
|
| 132 |
-
}
|
| 133 |
-
|
| 134 |
-
// Remove divs with invalid styles
|
| 135 |
-
if (next.tagName === 'DIV' && next.style.cssText && next.style.cssText.includes('undefined')) {
|
| 136 |
-
remove = true;
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
if (remove) {
|
| 140 |
-
var toRemove = next;
|
| 141 |
-
next = next.nextElementSibling;
|
| 142 |
-
toRemove.remove();
|
| 143 |
-
} else {
|
| 144 |
-
break;
|
| 145 |
-
}
|
| 146 |
-
}
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
// === INIT ===
|
| 150 |
-
function init() {
|
| 151 |
-
patchExportFunctions();
|
| 152 |
-
removeFooterBugs();
|
| 153 |
-
console.log('[Fix v1042] Loaded - 5 minute blob retention + footer cleanup');
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
if (document.readyState === 'loading') {
|
| 157 |
-
document.addEventListener('DOMContentLoaded', init);
|
| 158 |
-
} else {
|
| 159 |
-
init();
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
console.log('[Fix v1042] Module ready');
|
| 163 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vai-fix.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* V.AI STUDIO — Fix v6
|
| 3 |
-
* =======================
|
| 4 |
-
* FIX 1: closeAddProductModal / submitAddProductUrl (missing functions)
|
| 5 |
-
* FIX 2: Cart items không có idx → quote table render lỗi
|
| 6 |
-
* FIX 3: Nút "Báo giá" trong modal giỏ không load quote table correctly
|
| 7 |
-
* FIX 4: closeAddProductModal button không hoạt động
|
| 8 |
-
* FIX 5: exportDeliveryExcel, exportDeliveryPDF, openOrderPage, saveOrder
|
| 9 |
-
* dùng D[c.idx] nhưng cart có thể thiếu idx
|
| 10 |
-
*/
|
| 11 |
-
|
| 12 |
-
(function(){
|
| 13 |
-
'use strict';
|
| 14 |
-
|
| 15 |
-
// ===== FIX 1: AddProduct Modal functions =====
|
| 16 |
-
window.closeAddProductModal = function(){
|
| 17 |
-
var overlay = document.getElementById('addProductOverlay');
|
| 18 |
-
if(overlay){
|
| 19 |
-
overlay.classList.remove('open');
|
| 20 |
-
overlay.style.display = 'none';
|
| 21 |
-
}
|
| 22 |
-
document.body.style.overflow = '';
|
| 23 |
-
};
|
| 24 |
-
|
| 25 |
-
window.submitAddProductUrl = function(){
|
| 26 |
-
var secret = document.getElementById('addProductSecret');
|
| 27 |
-
var url = document.getElementById('addProductUrl');
|
| 28 |
-
var status = document.getElementById('addProductStatus');
|
| 29 |
-
if(!secret || !url) return;
|
| 30 |
-
if((secret.value||'').trim() !== 'V.AISTUDIO'){
|
| 31 |
-
if(status){ status.className = 'add-product-status err'; status.textContent = 'Sai ma truy cap'; }
|
| 32 |
-
return;
|
| 33 |
-
}
|
| 34 |
-
var productUrl = (url.value||'').trim();
|
| 35 |
-
if(!productUrl){
|
| 36 |
-
if(status){ status.className = 'add-product-status err'; status.textContent = 'Vui long nhap URL san pham'; }
|
| 37 |
-
return;
|
| 38 |
-
}
|
| 39 |
-
if(status){ status.className = 'add-product-status ok'; status.textContent = 'Dang xu ly... AI dang phan tich...'; }
|
| 40 |
-
setTimeout(function(){
|
| 41 |
-
if(status){ status.className = 'add-product-status ok'; status.textContent = 'Da them thanh cong!'; }
|
| 42 |
-
url.value = '';
|
| 43 |
-
}, 2000);
|
| 44 |
-
};
|
| 45 |
-
|
| 46 |
-
// Re-bind close button
|
| 47 |
-
document.addEventListener('DOMContentLoaded', function(){
|
| 48 |
-
var closeBtn = document.querySelector('.add-product-close');
|
| 49 |
-
if(closeBtn && !closeBtn.getAttribute('data-vfx')){
|
| 50 |
-
closeBtn.setAttribute('data-vfx', '1');
|
| 51 |
-
closeBtn.onclick = function(){ window.closeAddProductModal(); };
|
| 52 |
-
}
|
| 53 |
-
var overlay = document.getElementById('addProductOverlay');
|
| 54 |
-
if(overlay && !overlay.getAttribute('data-vfx')){
|
| 55 |
-
overlay.setAttribute('data-vfx', '1');
|
| 56 |
-
overlay.onclick = function(e){ if(e.target === this) window.closeAddProductModal(); };
|
| 57 |
-
}
|
| 58 |
-
var submitBtn = document.getElementById('addProductSubmit');
|
| 59 |
-
if(submitBtn && !submitBtn.getAttribute('data-vfx')){
|
| 60 |
-
submitBtn.setAttribute('data-vfx', '1');
|
| 61 |
-
submitBtn.onclick = function(){ window.submitAddProductUrl(); };
|
| 62 |
-
}
|
| 63 |
-
});
|
| 64 |
-
|
| 65 |
-
// ===== Helper: find product idx =====
|
| 66 |
-
function findProductIdx(item){
|
| 67 |
-
if(typeof D === 'undefined' || !D || !D.length) return -1;
|
| 68 |
-
if(typeof item.idx === 'number' && item.idx >= 0 && item.idx < D.length && D[item.idx]) return item.idx;
|
| 69 |
-
var slug = item.slug || '';
|
| 70 |
-
var sku = item.sku || '';
|
| 71 |
-
for(var i = 0; i < D.length; i++){
|
| 72 |
-
var p = D[i];
|
| 73 |
-
if(!p) continue;
|
| 74 |
-
if(slug && p.slug === slug) return i;
|
| 75 |
-
if(sku && (p.sku === sku || p.model === sku || p.mod === sku)) return i;
|
| 76 |
-
}
|
| 77 |
-
if(item.name){
|
| 78 |
-
var itemName = item.name.toLowerCase().replace(/[^a-z0-9]/g,'');
|
| 79 |
-
for(var j = 0; j < D.length; j++){
|
| 80 |
-
if(!D[j]) continue;
|
| 81 |
-
var pName = (D[j].name||'').toLowerCase().replace(/[^a-z0-9]/g,'');
|
| 82 |
-
if(itemName === pName || itemName.indexOf(pName) !== -1 || pName.indexOf(itemName) !== -1) return j;
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
return -1;
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
// ===== Fix cart items =====
|
| 89 |
-
function fixCartItems(cartArr){
|
| 90 |
-
if(!cartArr || !cartArr.length) return cartArr;
|
| 91 |
-
var changed = false;
|
| 92 |
-
cartArr.forEach(function(item){
|
| 93 |
-
if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){
|
| 94 |
-
if(typeof D !== 'undefined' && D){
|
| 95 |
-
var found = findProductIdx(item);
|
| 96 |
-
if(found >= 0){ item.idx = found; changed = true; }
|
| 97 |
-
}
|
| 98 |
-
}
|
| 99 |
-
});
|
| 100 |
-
if(changed){
|
| 101 |
-
try { localStorage.setItem('malloca_cart', JSON.stringify(cartArr)); } catch(e){}
|
| 102 |
-
}
|
| 103 |
-
return cartArr;
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
// Auto-fix idx on localStorage read
|
| 107 |
-
var origGetItem = localStorage.getItem;
|
| 108 |
-
localStorage.getItem = function(key){
|
| 109 |
-
var val = origGetItem.call(localStorage, key);
|
| 110 |
-
if(key === 'malloca_cart' && val){
|
| 111 |
-
try {
|
| 112 |
-
var parsed = JSON.parse(val);
|
| 113 |
-
if(Array.isArray(parsed)){
|
| 114 |
-
var changed = false;
|
| 115 |
-
parsed.forEach(function(item){
|
| 116 |
-
if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){
|
| 117 |
-
if(typeof D !== 'undefined' && D){
|
| 118 |
-
var found = findProductIdx(item);
|
| 119 |
-
if(found >= 0){ item.idx = found; changed = true; }
|
| 120 |
-
}
|
| 121 |
-
}
|
| 122 |
-
});
|
| 123 |
-
if(changed){
|
| 124 |
-
localStorage.setItem('malloca_cart', JSON.stringify(parsed));
|
| 125 |
-
return JSON.stringify(parsed);
|
| 126 |
-
}
|
| 127 |
-
}
|
| 128 |
-
} catch(e){}
|
| 129 |
-
}
|
| 130 |
-
return val;
|
| 131 |
-
};
|
| 132 |
-
|
| 133 |
-
// Auto-fix idx on localStorage set
|
| 134 |
-
var origSetItem = localStorage.setItem;
|
| 135 |
-
localStorage.setItem = function(key, val){
|
| 136 |
-
if(key === 'malloca_cart'){
|
| 137 |
-
try {
|
| 138 |
-
var parsed = JSON.parse(val);
|
| 139 |
-
if(Array.isArray(parsed)){
|
| 140 |
-
var changed = false;
|
| 141 |
-
parsed.forEach(function(item){
|
| 142 |
-
if(typeof item.idx !== 'number' || item.idx < 0){
|
| 143 |
-
if(typeof D !== 'undefined' && D){
|
| 144 |
-
var found = findProductIdx(item);
|
| 145 |
-
if(found >= 0){ item.idx = found; changed = true; }
|
| 146 |
-
}
|
| 147 |
-
}
|
| 148 |
-
});
|
| 149 |
-
if(changed) val = JSON.stringify(parsed);
|
| 150 |
-
}
|
| 151 |
-
} catch(e){}
|
| 152 |
-
}
|
| 153 |
-
origSetItem.call(localStorage, key, val);
|
| 154 |
-
};
|
| 155 |
-
|
| 156 |
-
// ===== Safe product lookup =====
|
| 157 |
-
function safeGetProduct(cartItem){
|
| 158 |
-
if(!cartItem) return null;
|
| 159 |
-
if(typeof cartItem.idx === 'number' && cartItem.idx >= 0 && typeof D !== 'undefined' && D && D[cartItem.idx]) return D[cartItem.idx];
|
| 160 |
-
var found = findProductIdx(cartItem);
|
| 161 |
-
if(found >= 0 && typeof D !== 'undefined' && D && D[found]){
|
| 162 |
-
cartItem.idx = found;
|
| 163 |
-
return D[found];
|
| 164 |
-
}
|
| 165 |
-
return null;
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
// ===== Override openQuotation =====
|
| 169 |
-
var origOpenQuotation = window.openQuotation;
|
| 170 |
-
window.openQuotation = function(){
|
| 171 |
-
try {
|
| 172 |
-
var cartRaw = JSON.parse(localStorage.getItem('malloca_cart') || '[]');
|
| 173 |
-
cartRaw = fixCartItems(cartRaw);
|
| 174 |
-
localStorage.setItem('malloca_cart', JSON.stringify(cartRaw));
|
| 175 |
-
if(typeof window.cart !== 'undefined') window.cart = cartRaw;
|
| 176 |
-
} catch(e){}
|
| 177 |
-
if(origOpenQuotation) origOpenQuotation();
|
| 178 |
-
};
|
| 179 |
-
|
| 180 |
-
// ===== Safe renderQuoteTable =====
|
| 181 |
-
window.renderQuoteTable = function(){
|
| 182 |
-
var tbody = document.getElementById('quoteTableBody');
|
| 183 |
-
if(!tbody) return;
|
| 184 |
-
var cartData = [];
|
| 185 |
-
try { cartData = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); } catch(e){}
|
| 186 |
-
if(!cartData || !cartData.length){
|
| 187 |
-
tbody.innerHTML = '<tr><td colspan="10" style="text-align:center;padding:30px;color:var(--g)">Gio hang trong</td></tr>';
|
| 188 |
-
return;
|
| 189 |
-
}
|
| 190 |
-
cartData = fixCartItems(cartData);
|
| 191 |
-
if(typeof window.cart !== 'undefined') window.cart = cartData;
|
| 192 |
-
var locked = !(document.body.classList.contains('vas-unlocked'));
|
| 193 |
-
tbody.innerHTML = cartData.map(function(c, i){
|
| 194 |
-
var product = safeGetProduct(c);
|
| 195 |
-
var model = (product && (product.model || product.sku)) || c.sku || '';
|
| 196 |
-
var specs = '';
|
| 197 |
-
if(product && product.specs && typeof product.specs === 'object'){
|
| 198 |
-
try {
|
| 199 |
-
specs = Object.entries(product.specs).slice(0,3).map(function(e){ return e[0]+': '+e[1]; }).join(', ');
|
| 200 |
-
} catch(e){}
|
| 201 |
-
}
|
| 202 |
-
var lineTotal = (c.priceNum || 0) * (c.qty || 1);
|
| 203 |
-
var discStyle = 'width:80px;padding:5px 6px;border:1.5px solid var(--gl);border-radius:6px;font-size:.78rem;text-align:right;font-family:inherit;outline:none;';
|
| 204 |
-
discStyle += locked ? 'background:var(--l);cursor:not-allowed' : 'background:#fff';
|
| 205 |
-
return '<tr>'
|
| 206 |
-
+ '<td style="text-align:center">' + (i + 1) + '</td>'
|
| 207 |
-
+ '<td><img class="qt-img" src="' + (c.image || '') + '" referrerpolicy="no-referrer" alt="' + (c.name||'') + '" onerror="this.style.display=\'none\'" style="width:72px;height:72px;object-fit:contain"></td>'
|
| 208 |
-
+ '<td class="qt-name">' + (c.name||'') + '</td>'
|
| 209 |
-
+ '<td style="text-align:center">' + model + '</td>'
|
| 210 |
-
+ '<td style="font-size:.72rem;color:var(--g);max-width:160px">' + specs + '</td>'
|
| 211 |
-
+ '<td style="text-align:center">' + (c.qty || 1) + '</td>'
|
| 212 |
-
+ '<td class="qt-price">' + (c.priceNum > 0 ? Number(c.priceNum).toLocaleString('vi-VN') + 'đ' : 'Lien he') + '</td>'
|
| 213 |
-
+ '<td><input class="qt-disc" data-idx="' + i + '" value="' + (c.priceNum > 0 ? Number(c.priceNum).toLocaleString('vi-VN') : '') + '" oninput="window.updateQuoteTotal()" ' + (locked ? 'disabled' : '') + ' style="' + discStyle + '"></td>'
|
| 214 |
-
+ '<td class="qt-price qt-linetotal" data-idx="' + i + '">' + (lineTotal > 0 ? lineTotal.toLocaleString('vi-VN') + 'đ' : '') + '</td>'
|
| 215 |
-
+ '<td><input class="qt-note" data-idx="' + i + '" placeholder="Ghi chu"></td>'
|
| 216 |
-
+ '</tr>';
|
| 217 |
-
}).join('');
|
| 218 |
-
if(typeof window.updateQuoteTotal === 'function') window.updateQuoteTotal();
|
| 219 |
-
};
|
| 220 |
-
|
| 221 |
-
// ===== Safe getQuoteData =====
|
| 222 |
-
window.getQuoteData = function(){
|
| 223 |
-
var cartData = [];
|
| 224 |
-
try { cartData = JSON.parse(localStorage.getItem('malloca_cart') || '[]'); } catch(e){}
|
| 225 |
-
cartData = fixCartItems(cartData);
|
| 226 |
-
var customer = {
|
| 227 |
-
name: (document.getElementById('qcName')||{}).value || '',
|
| 228 |
-
phone: (document.getElementById('qcPhone')||{}).value || '',
|
| 229 |
-
email: (document.getElementById('qcEmail')||{}).value || '',
|
| 230 |
-
addr: (document.getElementById('qcAddr')||{}).value || '',
|
| 231 |
-
orderCode: (document.getElementById('qcCompany')||{}).value || '',
|
| 232 |
-
date: (document.getElementById('qcDate')||{}).value || new Date().toISOString().split('T')[0]
|
| 233 |
-
};
|
| 234 |
-
var items = cartData.map(function(c, i){
|
| 235 |
-
var product = safeGetProduct(c);
|
| 236 |
-
var discInput = document.querySelector('.qt-disc[data-idx="' + i + '"]');
|
| 237 |
-
var noteInput = document.querySelector('.qt-note[data-idx="' + i + '"]');
|
| 238 |
-
var discPrice = discInput ? (parseInt(discInput.value.replace(/\D/g,'')) || 0) : (c.priceNum || 0);
|
| 239 |
-
if(!discPrice) discPrice = c.priceNum || 0;
|
| 240 |
-
var specs = '';
|
| 241 |
-
if(product && product.specs && typeof product.specs === 'object'){
|
| 242 |
-
try {
|
| 243 |
-
var parts=[], seen={};
|
| 244 |
-
Object.entries(product.specs).forEach(function(x){
|
| 245 |
-
if(!x[0] || x[1]==null || x[1]==='') return;
|
| 246 |
-
var t = x[0]+': '+x[1], key = (x[0]+x[1]).replace(/[^a-z0-9]/gi,'');
|
| 247 |
-
if(seen[key]) return;
|
| 248 |
-
seen[key]=true; parts.push(t);
|
| 249 |
-
});
|
| 250 |
-
specs = parts.join('; ').slice(0,900);
|
| 251 |
-
} catch(e2){}
|
| 252 |
-
}
|
| 253 |
-
return {
|
| 254 |
-
stt: i+1, name: c.name || '',
|
| 255 |
-
model: (product && (product.model || product.sku)) || c.sku || '',
|
| 256 |
-
specs: specs, image: c.image || '',
|
| 257 |
-
qty: c.qty || 1, price: c.priceNum || 0,
|
| 258 |
-
discPrice: discPrice, total: discPrice * (c.qty || 1),
|
| 259 |
-
note: noteInput ? noteInput.value : ''
|
| 260 |
-
};
|
| 261 |
-
});
|
| 262 |
-
var grandTotal = items.reduce(function(s, it){ return s + it.total; }, 0);
|
| 263 |
-
return { customer: customer, items: items, grandTotal: grandTotal };
|
| 264 |
-
};
|
| 265 |
-
|
| 266 |
-
// ===== Safe wrappers =====
|
| 267 |
-
var origDeliveryExcel = window.exportDeliveryExcel;
|
| 268 |
-
window.exportDeliveryExcel = function(){
|
| 269 |
-
try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){}
|
| 270 |
-
if(origDeliveryExcel) origDeliveryExcel();
|
| 271 |
-
};
|
| 272 |
-
|
| 273 |
-
var origDeliveryPDF = window.exportDeliveryPDF;
|
| 274 |
-
window.exportDeliveryPDF = function(){
|
| 275 |
-
try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){}
|
| 276 |
-
if(origDeliveryPDF) origDeliveryPDF();
|
| 277 |
-
};
|
| 278 |
-
|
| 279 |
-
var origOrderPage = window.openOrderPage;
|
| 280 |
-
window.openOrderPage = function(){
|
| 281 |
-
try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){}
|
| 282 |
-
if(origOrderPage) origOrderPage();
|
| 283 |
-
};
|
| 284 |
-
|
| 285 |
-
var origSaveOrder = window.saveOrder;
|
| 286 |
-
window.saveOrder = function(){
|
| 287 |
-
try { fixCartItems(JSON.parse(localStorage.getItem('malloca_cart')||'[]')); } catch(e){}
|
| 288 |
-
if(origSaveOrder) origSaveOrder();
|
| 289 |
-
};
|
| 290 |
-
|
| 291 |
-
// ===== Patch addToCart to fix idx after adding =====
|
| 292 |
-
var origAddToCart = window.addToCart;
|
| 293 |
-
window.addToCart = function(product){
|
| 294 |
-
if(origAddToCart) origAddToCart(product);
|
| 295 |
-
try {
|
| 296 |
-
var cartArr = JSON.parse(localStorage.getItem('malloca_cart') || '[]');
|
| 297 |
-
var changed = false;
|
| 298 |
-
cartArr.forEach(function(item){
|
| 299 |
-
if(typeof item.idx !== 'number' || item.idx < 0 || !D || !D[item.idx]){
|
| 300 |
-
if(typeof D !== 'undefined' && D){
|
| 301 |
-
var found = findProductIdx(item);
|
| 302 |
-
if(found >= 0){ item.idx = found; changed = true; }
|
| 303 |
-
}
|
| 304 |
-
}
|
| 305 |
-
});
|
| 306 |
-
if(changed){
|
| 307 |
-
localStorage.setItem('malloca_cart', JSON.stringify(cartArr));
|
| 308 |
-
if(typeof window.cart !== 'undefined') window.cart = cartArr;
|
| 309 |
-
}
|
| 310 |
-
} catch(e){}
|
| 311 |
-
};
|
| 312 |
-
|
| 313 |
-
// Periodic cart fix
|
| 314 |
-
setInterval(function(){
|
| 315 |
-
try {
|
| 316 |
-
var cartArr = JSON.parse(localStorage.getItem('malloca_cart') || '[]');
|
| 317 |
-
if(!cartArr.length) return;
|
| 318 |
-
var changed = false;
|
| 319 |
-
cartArr.forEach(function(item){
|
| 320 |
-
if(typeof item.idx !== 'number' || item.idx < 0){
|
| 321 |
-
if(typeof D !== 'undefined' && D){
|
| 322 |
-
var found = findProductIdx(item);
|
| 323 |
-
if(found >= 0){ item.idx = found; changed = true; }
|
| 324 |
-
}
|
| 325 |
-
}
|
| 326 |
-
});
|
| 327 |
-
if(changed){
|
| 328 |
-
localStorage.setItem('malloca_cart', JSON.stringify(cartArr));
|
| 329 |
-
if(typeof window.cart !== 'undefined') window.cart = cartArr;
|
| 330 |
-
}
|
| 331 |
-
} catch(e){}
|
| 332 |
-
}, 3000);
|
| 333 |
-
|
| 334 |
-
console.log('[VAI Fix v6] Loaded: closeAddProductModal, submitAddProductUrl, quote cart idx fix, export/openOrder/saveOrder, addToCart fix');
|
| 335 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vai-patch-v1047.js
DELETED
|
@@ -1,205 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* V.AI STUDIO - UNIVERSAL PATCH v1047
|
| 3 |
-
* Fixes all missing functionality:
|
| 4 |
-
* 1. AI Search - Implements actual search with semantic matching
|
| 5 |
-
* 2. Order/Quote buttons - Ensures all handlers work
|
| 6 |
-
* 3. Excel/PDF Export - Robust export with fallback
|
| 7 |
-
*/
|
| 8 |
-
|
| 9 |
-
(function() {
|
| 10 |
-
'use strict';
|
| 11 |
-
|
| 12 |
-
// Wait for DOM and main data to load
|
| 13 |
-
function ready(fn) {
|
| 14 |
-
if (document.readyState !== 'loading') fn();
|
| 15 |
-
else document.addEventListener('DOMContentLoaded', fn);
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
ready(function() {
|
| 19 |
-
|
| 20 |
-
// ===== 1. AI SEARCH PATCH =====
|
| 21 |
-
// Override with full implementation
|
| 22 |
-
window.aiSearch = async function(query, products, token) {
|
| 23 |
-
if (!products || !products.length) {
|
| 24 |
-
return { results: [], aiAnswer: null, categories: [], constraints: [], budget: 0 };
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
const norm = (s) => String(s || '').toLowerCase()
|
| 28 |
-
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
| 29 |
-
.replace(/[đĐ]/g, 'd').replace(/[.\-\s]/g, '');
|
| 30 |
-
|
| 31 |
-
const q = norm(query);
|
| 32 |
-
let budget = 0;
|
| 33 |
-
let categories = [];
|
| 34 |
-
let constraints = [];
|
| 35 |
-
|
| 36 |
-
// Extract budget constraint
|
| 37 |
-
const budgetMatch = query.toLowerCase().match(/(dưới|trên|trong khoảng)?\s*([\d,.]+)\s*(triệu|nghìn|k|đồng)/g);
|
| 38 |
-
if (budgetMatch) {
|
| 39 |
-
const amt = parseFloat(budgetMatch[0].replace(/[^\d,.]/g, '').replace(',', '.'));
|
| 40 |
-
const unit = budgetMatch[0].match(/(triệu|nghìn|k|đồng)/g);
|
| 41 |
-
if (unit && unit[0] === 'triệu') budget = amt * 1000000;
|
| 42 |
-
else if (unit && (unit[0] === 'nghìn' || unit[0] === 'k')) budget = amt * 1000;
|
| 43 |
-
else budget = amt;
|
| 44 |
-
constraints.push({ label: budgetMatch[0].trim() });
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
// Category keywords
|
| 48 |
-
const catMap = {
|
| 49 |
-
'bep': 'Bếp điện từ',
|
| 50 |
-
'bep-tu': 'Bếp từ',
|
| 51 |
-
'bep-gas': 'Bếp gas',
|
| 52 |
-
'hut-mui': 'Máy hút mùi',
|
| 53 |
-
'hut-khoi': 'Máy hút khói',
|
| 54 |
-
'chau-rua': 'Chậu rửa',
|
| 55 |
-
'voi-rua': 'Vòi rửa',
|
| 56 |
-
'lo-nuong': 'Lò nướng',
|
| 57 |
-
'tu-lanh': 'Tủ lạnh',
|
| 58 |
-
'may-rua-chen': 'Máy rửa chén'
|
| 59 |
-
};
|
| 60 |
-
|
| 61 |
-
Object.keys(catMap).forEach(key => {
|
| 62 |
-
if (q.includes(key)) {
|
| 63 |
-
categories.push(catMap[key]);
|
| 64 |
-
}
|
| 65 |
-
});
|
| 66 |
-
|
| 67 |
-
// Brand extraction
|
| 68 |
-
const brandMap = {
|
| 69 |
-
'malloca': 'Malloca',
|
| 70 |
-
'eurogold': 'Eurogold',
|
| 71 |
-
'grob': 'Grob',
|
| 72 |
-
'canzy': 'Canzy',
|
| 73 |
-
'demax': 'Demax',
|
| 74 |
-
'hafele': 'Hafele',
|
| 75 |
-
'garis': 'Garis'
|
| 76 |
-
};
|
| 77 |
-
|
| 78 |
-
// Search logic with scoring
|
| 79 |
-
let results = [];
|
| 80 |
-
for (let i = 0; i < products.length && results.length < 30; i++) {
|
| 81 |
-
const p = products[i];
|
| 82 |
-
let score = 0;
|
| 83 |
-
const name = norm(p.name || '');
|
| 84 |
-
const sku = norm(p.sku || p.mod || '');
|
| 85 |
-
const brand = norm(p.brand || '');
|
| 86 |
-
const cat = norm(p.cat || '');
|
| 87 |
-
|
| 88 |
-
// Exact/partial matches
|
| 89 |
-
if (name.includes(q)) score += 10;
|
| 90 |
-
if (sku.includes(q)) score += 8;
|
| 91 |
-
if (brand.includes(q)) score += 6;
|
| 92 |
-
if (cat.includes(q)) score += 4;
|
| 93 |
-
|
| 94 |
-
// Word-by-word matching
|
| 95 |
-
const words = q.split(/\s+/).filter(w => w.length > 1);
|
| 96 |
-
words.forEach(w => {
|
| 97 |
-
if (name.includes(w)) score += 2;
|
| 98 |
-
if (sku.includes(w)) score += 1;
|
| 99 |
-
if (brand.includes(w)) score += 1;
|
| 100 |
-
});
|
| 101 |
-
|
| 102 |
-
// Price filter by budget
|
| 103 |
-
if (budget > 0 && p.priceNum && p.priceNum > budget) {
|
| 104 |
-
score = 0;
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
// Brand filter
|
| 108 |
-
for (let b in brandMap) {
|
| 109 |
-
if (q.includes(b)) {
|
| 110 |
-
if (brand.includes(b)) score += 5;
|
| 111 |
-
else if (!brand.includes(b)) score = 0;
|
| 112 |
-
}
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
if (score > 0) {
|
| 116 |
-
const labels = [];
|
| 117 |
-
if (p.priceNum && budget > 0 && p.priceNum <= budget) labels.push('Trong ngân sách');
|
| 118 |
-
results.push({ p, idx: i, score, labels });
|
| 119 |
-
}
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
// Sort by score desc
|
| 123 |
-
results.sort((a, b) => b.score - a.score);
|
| 124 |
-
|
| 125 |
-
// Generate AI response
|
| 126 |
-
let aiAnswer = '';
|
| 127 |
-
if (results.length > 0) {
|
| 128 |
-
aiAnswer = `Tìm thấy ${results.length} sản phẩm phù hợp. `;
|
| 129 |
-
if (categories.length) aiAnswer += `Danh mục: ${categories.join(', ')}. `;
|
| 130 |
-
if (budget > 0) aiAnswer += `Ngân sách: ${(budget/1000000).toFixed(0)} triệu.`;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
return { results, aiAnswer, categories, constraints, budget };
|
| 134 |
-
};
|
| 135 |
-
|
| 136 |
-
// ===== 2. EXCEL EXPORT PATCH =====
|
| 137 |
-
const origExportExcel = window.exportExcel;
|
| 138 |
-
window.exportExcel = async function() {
|
| 139 |
-
if (typeof ExcelJS === 'undefined') {
|
| 140 |
-
if (typeof exportQuoteCSV === 'function') {
|
| 141 |
-
exportQuoteCSV();
|
| 142 |
-
} else {
|
| 143 |
-
alert('Thư viện Excel chưa tải. Vui lòng đợi 2-3 giây và thử lại.');
|
| 144 |
-
}
|
| 145 |
-
return;
|
| 146 |
-
}
|
| 147 |
-
if (typeof origExportExcel === 'function') {
|
| 148 |
-
return origExportExcel();
|
| 149 |
-
}
|
| 150 |
-
};
|
| 151 |
-
|
| 152 |
-
// ===== 3. PDF EXPORT PATCH =====
|
| 153 |
-
const origExportPDF = window.exportPDF;
|
| 154 |
-
window.exportPDF = async function() {
|
| 155 |
-
if (!window.jspdf || !window.jspdf.jsPDF) {
|
| 156 |
-
alert('Thư viện PDF chưa tải. Vui lòng đợi 2-3 giây và thử lại.');
|
| 157 |
-
return;
|
| 158 |
-
}
|
| 159 |
-
if (typeof origExportPDF === 'function') {
|
| 160 |
-
return origExportPDF();
|
| 161 |
-
}
|
| 162 |
-
};
|
| 163 |
-
|
| 164 |
-
// ===== 4. FORMAT PRICE HELPER =====
|
| 165 |
-
window.formatPrice = function(priceNum) {
|
| 166 |
-
if (!priceNum || priceNum <= 0) return 'Liên hệ';
|
| 167 |
-
return priceNum.toLocaleString('vi-VN') + 'đ';
|
| 168 |
-
};
|
| 169 |
-
|
| 170 |
-
// ===== 5. ORDER PICKER STUB =====
|
| 171 |
-
// If _showOrderPicker is null, provide a simple implementation
|
| 172 |
-
if (!window._showOrderPicker) {
|
| 173 |
-
window._showOrderPicker = function(product, callback) {
|
| 174 |
-
// Simple add-to-cart flow
|
| 175 |
-
if (product && typeof addToCart === 'function') {
|
| 176 |
-
// Find product index
|
| 177 |
-
var idx = -1;
|
| 178 |
-
if (window.D) {
|
| 179 |
-
for (var i = 0; i < window.D.length; i++) {
|
| 180 |
-
if (window.D[i] && (window.D[i].slug === product.slug ||
|
| 181 |
-
window.D[i].sku === product.sku ||
|
| 182 |
-
window.D[i].model === product.model)) {
|
| 183 |
-
idx = i; break;
|
| 184 |
-
}
|
| 185 |
-
}
|
| 186 |
-
}
|
| 187 |
-
if (idx >= 0) addToCart(idx);
|
| 188 |
-
callback && callback('ok');
|
| 189 |
-
}
|
| 190 |
-
};
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
// ===== 6. UPDATE CART BADGE ON LOAD =====
|
| 194 |
-
if (typeof updateCartBadge === 'function') {
|
| 195 |
-
updateCartBadge();
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
// ===== 7. ENSURE EXPORT FUNCTIONS ARE AVAILABLE =====
|
| 199 |
-
window._ensureExportFunctions = function() {
|
| 200 |
-
return typeof exportExcel !== 'undefined' && typeof exportPDF !== 'undefined';
|
| 201 |
-
};
|
| 202 |
-
|
| 203 |
-
console.log('V.AI STUDIO Patch v1047 initialized');
|
| 204 |
-
});
|
| 205 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|