Spaces:
Running
Running
File size: 16,915 Bytes
94211e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | /**
* V.AI STUDIO — Comprehensive Fix for Brand Discounts, Excel Formulas & Notes
* ========================================================================
* Fixes:
* 1. Brand-level discount parsing: "Malloca Ck% 35%", "eurogold ck% 50%" → itemDiscounts
* 2. Excel discount column (Giá CK) uses FORMULA (G*0.65) not hardcoded value
* 3. Notes from AI prompt are filtered out from Excel/modal (no "Ghi chú:" row)
* 4. Fees (giao hàng, lắp đặt, cọc, bốc xếp) appear as separate lines in Excel
* 5. Applies to both _doExportExcel and _doExportDeliveryExcel
*/
(function() {
'use strict';
if (window.__COMPREHENSIVE_FIX__) return;
window.__COMPREHENSIVE_FIX__ = true;
// Wait for VAIR_QR to be available
function waitForVAIQR() {
return new Promise(resolve => {
if (window.VAI_QR) {
resolve(window.VAI_QR);
return;
}
let attempts = 0;
const check = setInterval(() => {
attempts++;
if (window.VAI_QR) {
clearInterval(check);
resolve(window.VAI_QR);
} else if (attempts > 100) { // 10 seconds
clearInterval(check);
console.warn('[Comprehensive Fix] VAI_QR not found after 10s');
resolve(null);
}
}, 100);
});
}
waitForVAIQR().then(VAI_QR => {
if (!VAI_QR) return;
// =====================================================================
// 1. PATCH parsePrompt TO HANDLE BRAND-LEVEL DISCOUNTS
// =====================================================================
if (VAI_QR.parsePrompt && !VAI_QR.parsePrompt._patchedBrandDiscount) {
const origParsePrompt = VAI_QR.parsePrompt;
VAI_QR.parsePrompt = function(text) {
// First, extract brand-level discounts BEFORE the original parser runs
// Pattern: "BrandName Ck% XX%" or "BrandName ck% XX" or "BrandName chiết khấu XX%"
const brandDiscounts = {};
const brandDiscountRegex = /\b([a-zA-Z][a-zA-Z0-9\s\-]{1,30}?)\s+(?:ck|chiết\s*khấu|chiet\s*khau|giảm|giam|discount)\s*([\d.,]+)\s*%/gi;
let match;
while ((match = brandDiscountRegex.exec(text)) !== null) {
const brand = match[1].trim().toLowerCase();
const pct = parseFloat(match[2].replace(/\./g, '').replace(',', '.'));
if (pct > 0 && pct <= 100) {
brandDiscounts[brand] = pct;
}
}
// Remove brand discount lines from text so they don't get parsed as fees
let cleanText = text;
if (Object.keys(brandDiscounts).length > 0) {
cleanText = text.replace(brandDiscountRegex, '');
}
// Call original parser with cleaned text
const result = origParsePrompt.call(this, cleanText);
// Merge brand discounts into itemDiscounts
// Store them in a special property for later matching
result._brandDiscounts = brandDiscounts;
// Also merge into itemDiscounts for backward compatibility
if (Object.keys(brandDiscounts).length > 0) {
result.itemDiscounts = { ...result.itemDiscounts, ...brandDiscounts };
}
return result;
};
VAI_QR.parsePrompt._patchedBrandDiscount = true;
console.log('[Comprehensive Fix] Patched VAI_QR.parsePrompt for brand-level discounts');
}
// =====================================================================
// 2. PATCH matchItemDiscount TO HANDLE BRAND MATCHING
// =====================================================================
if (VAI_QR.matchItemDiscount && !VAI_QR.matchItemDiscount._patchedBrandMatch) {
const origMatchItemDiscount = VAI_QR.matchItemDiscount;
VAI_QR.matchItemDiscount = function(it, itemDiscounts) {
// First try original matching (SKU/model/code matching)
const originalResult = origMatchItemDiscount.call(this, it, itemDiscounts);
if (originalResult > 0) return originalResult;
// If no match, try brand matching from _brandDiscounts
// Check if itemDiscounts has brand keys (lowercase, no special chars)
const brandKeys = Object.keys(itemDiscounts || {}).filter(k =>
!k.match(/[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d/) // Not a product code pattern
);
if (brandKeys.length === 0) return 0;
// Get item's brand from product data
let itemBrand = '';
if (it.brand) {
itemBrand = it.brand.toLowerCase();
} else if (window.D) {
// Try to find product in D array
const idx = it.idx != null ? it.idx : (it.productIdx != null ? it.productIdx : null);
let product = null;
if (idx != null && window.D[idx]) {
product = window.D[idx];
} else if (it.model || it.sku || it.ma || it.name) {
const ck = (it.model || it.sku || it.ma || it.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
product = window.D.find(x => {
const xck = (x.model || x.sku || x.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
return xck.includes(ck) || ck.includes(xck);
});
}
if (product && product.brand) {
itemBrand = product.brand.toLowerCase();
}
}
// Match against brand discounts
const cleanItemBrand = itemBrand.replace(/[^a-z0-9]/g, '');
for (const bk of brandKeys) {
const cleanBk = bk.replace(/[^a-z0-9]/g, '');
if (cleanBk && cleanItemBrand && (cleanItemBrand.includes(cleanBk) || cleanBk.includes(cleanItemBrand))) {
return itemDiscounts[bk];
}
}
return 0;
};
VAI_QR.matchItemDiscount._patchedBrandMatch = true;
console.log('[Comprehensive Fix] Patched VAI_QR.matchItemDiscount for brand matching');
}
// =====================================================================
// 3. PATCH applyPromptDiscounts TO PROPAGATE BRAND DISCOUNTS
// =====================================================================
if (VAI_QR.applyPromptDiscounts && !VAI_QR.applyPromptDiscounts._patchedBrand) {
const origApplyPromptDiscounts = VAI_QR.applyPromptDiscounts;
VAI_QR.applyPromptDiscounts = function(qd, parsed) {
// Call original
const result = origApplyPromptDiscounts.call(this, qd, parsed);
// If we have brand discounts but no item-level discounts were applied,
// apply them to matching items
if (parsed && parsed._brandDiscounts && Object.keys(parsed._brandDiscounts).length > 0) {
if (result && result.items) {
result.items.forEach(it => {
// Check if this item already has a discount
if (!it.discountPercent || it.discountPercent === 0) {
// Try to match brand
let itemBrand = '';
if (it.brand) {
itemBrand = it.brand.toLowerCase();
} else if (window.D) {
const idx = it.idx != null ? it.idx : (it.productIdx != null ? it.productIdx : null);
let product = null;
if (idx != null && window.D[idx]) {
product = window.D[idx];
} else if (it.model || it.sku || it.ma || it.name) {
const ck = (it.model || it.sku || it.ma || it.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
product = window.D.find(x => {
const xck = (x.model || x.sku || x.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
return xck.includes(ck) || ck.includes(xck);
});
}
if (product && product.brand) {
itemBrand = product.brand.toLowerCase();
}
}
const cleanItemBrand = itemBrand.replace(/[^a-z0-9]/g, '');
for (const [brand, pct] of Object.entries(parsed._brandDiscounts)) {
const cleanBrand = brand.replace(/[^a-z0-9]/g, '');
if (cleanBrand && cleanItemBrand && (cleanItemBrand.includes(cleanBrand) || cleanBrand.includes(cleanItemBrand))) {
it.discPrice = Math.round((it.price || 0) * (1 - pct / 100));
it.discountPercent = pct;
it.total = (it.discPrice || it.price || 0) * (it.qty || 1);
it._promptDiscount = true;
break;
}
}
}
});
// Recalculate grandTotal
let productTotal = 0;
result.items.forEach(it => {
productTotal += Number(it.total || 0);
});
result.grandTotal = productTotal;
}
}
return result;
};
VAI_QR.applyPromptDiscounts._patchedBrand = true;
console.log('[Comprehensive Fix] Patched VAI_QR.applyPromptDiscounts for brand discounts');
}
// =====================================================================
// 4. PATCH _doExportExcel FOR FORMULA-BASED DISCOUNT & NO NOTES ROW
// =====================================================================
if (VAI_QR.exportExcel && !VAI_QR.exportExcel._patchedComprehensive) {
const orig = VAI_QR.exportExcel;
VAI_QR.exportExcel = async function(d, qd, code, qr) {
// Ensure items have correct discPrice from prompt discounts
if (qd && qd.items) {
qd.items.forEach((it, i) => {
// If item has discountPercent from prompt, ensure discPrice reflects it
if (it.discountPercent && it.discountPercent > 0 && it.price) {
it.discPrice = Math.round(it.price * (1 - it.discountPercent / 100));
it.total = it.discPrice * (it.qty || 1);
}
// Mark that this item has prompt-based discount (for formula)
it._promptDiscount = (it.discountPercent && it.discountPercent > 0) ||
(d.itemDiscounts && matchItemDiscountForPrompt(it, d.itemDiscounts)) ||
(d._brandDiscounts && matchBrandDiscountForPrompt(it, d._brandDiscounts));
});
}
// SUPPRESS NOTES ROW - set d.notes to empty array
// Fees (giao hàng, lắp đặt, cọc, bốc xếp) are in d.fees and SHOULD appear
const originalNotes = d.notes;
d.notes = [];
try {
return await orig.call(this, d, qd, code, qr);
} finally {
// Restore
d.notes = originalNotes;
}
};
VAI_QR.exportExcel._patchedComprehensive = true;
console.log('[Comprehensive Fix] Patched VAI_QR.exportExcel for formula discount & no notes');
}
// Helper: Check if item has prompt-based item discount (product code matching)
function matchItemDiscountForPrompt(it, itemDiscounts) {
if (!itemDiscounts || !it) return false;
const keys = (it.model || it.sku || it.ma || it.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
for (const code in itemDiscounts) {
const ck = code.toLowerCase().replace(/[^a-z0-9]/g, '');
if (ck && keys && (keys.includes(ck) || ck.includes(keys)) &&
code.match(/[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d/)) { // product code pattern
return true;
}
}
return false;
}
// Helper: Check if item has prompt-based brand discount
function matchBrandDiscountForPrompt(it, brandDiscounts) {
if (!brandDiscounts || !it) return false;
let itemBrand = '';
if (it.brand) {
itemBrand = it.brand.toLowerCase();
} else if (window.D) {
const idx = it.idx != null ? it.idx : (it.productIdx != null ? it.productIdx : null);
let product = null;
if (idx != null && window.D[idx]) {
product = window.D[idx];
} else if (it.model || it.sku || it.ma || it.name) {
const ck = (it.model || it.sku || it.ma || it.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
product = window.D.find(x => {
const xck = (x.model || x.sku || x.name || '').toString().toLowerCase().replace(/[^a-z0-9]/g, '');
return xck.includes(ck) || ck.includes(xck);
});
}
if (product && product.brand) {
itemBrand = product.brand.toLowerCase();
}
}
const cleanItemBrand = itemBrand.replace(/[^a-z0-9]/g, '');
for (const brand in brandDiscounts) {
const cleanBrand = brand.replace(/[^a-z0-9]/g, '');
if (cleanBrand && cleanItemBrand && (cleanItemBrand.includes(cleanBrand) || cleanBrand.includes(cleanItemBrand))) {
return true;
}
}
return false;
}
// =====================================================================
// 5. PATCH _doExportDeliveryExcel FOR NO NOTES ROW
// =====================================================================
if (VAI_QR.exportDeliveryExcel && !VAI_QR.exportDeliveryExcel._patchedComprehensive) {
const orig2 = VAI_QR.exportDeliveryExcel;
VAI_QR.exportDeliveryExcel = async function(qd, code) {
// Suppress notes row
const originalNotes = qd.notes;
qd.notes = [];
try {
return await orig2.call(this, qd, code);
} finally {
qd.notes = originalNotes;
}
};
VAI_QR.exportDeliveryExcel._patchedComprehensive = true;
console.log('[Comprehensive Fix] Patched VAI_QR.exportDeliveryExcel for no notes');
}
// =====================================================================
// 6. PATCH getData TO MARK PROMPT DISCOUNTS
// =====================================================================
if (VAI_QR.getData && !VAI_QR.getData._patchedComprehensive) {
const origGetData = VAI_QR.getData;
VAI_QR.getData = function() {
const result = origGetData.apply(this, arguments);
// Mark items with prompt discount (global, item-level, or brand-level)
if (result.qd && result.qd.items && (result.itemDiscounts || result.discountPercent || result._brandDiscounts)) {
result.qd.items.forEach(it => {
if (it.discountPercent && it.discountPercent > 0) {
it._promptDiscount = true;
}
});
}
// The notes are already in result.notes (from parsed.notes)
// Excel patch will suppress them by setting d.notes = []
// Fees in result.fees will still appear (giao hàng, lắp đặt, cọc, bốc xếp)
return result;
};
VAI_QR.getData._patchedComprehensive = true;
console.log('[Comprehensive Fix] Patched VAI_QR.getData for prompt discount marking');
}
// =====================================================================
// 7. PATCH buildQuoteHTML (PDF/Image export) TO SUPPRESS NOTES
// =====================================================================
if (VAI_QR.buildQuoteHTML && !VAI_QR.buildQuoteHTML._patchedComprehensive) {
const origBuildQuoteHTML = VAI_QR.buildQuoteHTML;
VAI_QR.buildQuoteHTML = function(d, code, qrUrl) {
// Suppress notes in HTML export too
const originalNotes = d.notes;
d.notes = [];
try {
return origBuildQuoteHTML.call(this, d, code, qrUrl);
} finally {
d.notes = originalNotes;
}
};
VAI_QR.buildQuoteHTML._patchedComprehensive = true;
console.log('[Comprehensive Fix] Patched VAI_QR.buildQuoteHTML for no notes');
}
// =====================================================================
// 8. PATCH buildDeliveryHTML (PDF/Image export) TO SUPPRESS NOTES
// =====================================================================
if (VAI_QR.buildDeliveryHTML && !VAI_QR.buildDeliveryHTML._patchedComprehensive) {
const origBuildDeliveryHTML = VAI_QR.buildDeliveryHTML;
VAI_QR.buildDeliveryHTML = function(d, code, qrUrl) {
// Suppress notes in delivery HTML export too
const originalNotes = d.notes;
d.notes = [];
try {
return origBuildDeliveryHTML.call(this, d, code, qrUrl);
} finally {
d.notes = originalNotes;
}
};
VAI_QR.buildDeliveryHTML._patchedComprehensive = true;
console.log('[Comprehensive Fix] Patched VAI_QR.buildDeliveryHTML for no notes');
}
console.log('[Comprehensive Fix] All 8 patches applied successfully!');
});
})(); |