/** * BOGO Service - Handles Buy One Get One logic * * Processes BOGO rules from the API and calculates free items/discounts */ const BogoService = { /** * Check if a product has any BOGO rules * @param {Object} product * @returns {boolean} */ hasBogoRules(product) { return product.bogo_rules && product.bogo_rules.length > 0; }, /** * Get the primary BOGO rule for a product (first one) * @param {Object} product * @returns {Object|null} */ getPrimaryBogoRule(product) { if (!this.hasBogoRules(product)) return null; return product.bogo_rules[0]; }, /** * Get BOGO label for display * @param {Object} product * @returns {string|null} */ getBogoLabel(product) { const rule = this.getPrimaryBogoRule(product); return rule ? rule.label : null; }, /** * Calculate BOGO discount for cart items * * @param {Array} cartItems - Array of cart items with product data * @param {Array} products - Full products list with BOGO rules * @returns {Object} { freeItems: [], discountAmount: number } */ calculateBogoForCart(cartItems, products) { const result = { freeItems: [], discountAmount: 0, appliedRules: [] }; // Group cart items by product ID const itemsByProduct = this.groupCartItemsByProduct(cartItems); // Check each product group for BOGO eligibility for (const [productId, items] of Object.entries(itemsByProduct)) { const product = products.find(p => p.id == productId); if (!product || !this.hasBogoRules(product)) continue; const rule = this.getPrimaryBogoRule(product); const quantity = items.length; // Calculate how many free items based on the rule const bogoResult = this.applyBogoRule(rule, quantity, product, products); if (bogoResult.freeItems.length > 0 || bogoResult.discountAmount > 0) { result.freeItems.push(...bogoResult.freeItems); result.discountAmount += bogoResult.discountAmount; result.appliedRules.push({ rule, productId: parseInt(productId), productName: product.name, ...bogoResult }); } } return result; }, /** * Group cart items by product ID * @param {Array} cartItems * @returns {Object} */ groupCartItemsByProduct(cartItems) { return cartItems.reduce((groups, item) => { const id = item.id; if (!groups[id]) groups[id] = []; groups[id].push(item); return groups; }, {}); }, /** * Apply a BOGO rule to calculate free items/discount * * @param {Object} rule - BOGO rule from API * @param {number} quantity - Number of items in cart * @param {Object} product - The product * @param {Array} products - All products (for Buy X Get Y) * @returns {Object} */ applyBogoRule(rule, quantity, product, products) { const result = { freeItems: [], discountAmount: 0, freeQuantity: 0 }; if (quantity < rule.buy_qty) { return result; // Not enough items to trigger BOGO } // Calculate how many times the rule applies let timesApplied = 1; if (rule.recursive) { timesApplied = Math.floor(quantity / rule.buy_qty); } const freeQty = timesApplied * rule.get_qty; result.freeQuantity = freeQty; if (rule.type === 'buy_x_get_x') { // Same product is free or discounted if (rule.discount_type === 'free_product' || rule.discount_value >= 100) { // Fully free - automatically add get_qty items to cart result.discountAmount = freeQty * parseFloat(product.price); // Add get_qty free items (not just 1) for (let i = 0; i < freeQty; i++) { result.freeItems.push({ id: product.id, name: product.name + ' (GRATIS)', price: '0.00', originalPrice: product.price, isFree: true, ruleId: rule.rule_id }); } } // For percentage and flat discounts, we DON'T automatically add items // These are handled manually via addBogoDiscountedItem() in cart.js // The user clicks a button to add the discounted item } else if (rule.type === 'buy_x_get_y' && rule.get_product_ids) { // Different product (Y) is free or discounted console.log('[BOGO DEBUG] Processing buy_x_get_y rule:', { rule_id: rule.rule_id, rule_title: rule.rule_title, get_product_ids: rule.get_product_ids, discount_type: rule.discount_type, discount_value: rule.discount_value, freeQty: freeQty }); // For buy_x_get_y with free_product, we AUTO-ADD the free items // For other discount types, user must manually add them for (const freeProductId of rule.get_product_ids) { const freeProduct = products.find(p => p.id == freeProductId); if (!freeProduct) { console.log('[BOGO DEBUG] Free product not found:', freeProductId); continue; } const freeProductPrice = parseFloat(freeProduct.price); console.log('[BOGO DEBUG] Found free product:', { id: freeProduct.id, name: freeProduct.name, price: freeProductPrice }); if (rule.discount_type === 'free_product' || rule.discount_value >= 100) { // 100% korting - product Y is gratis - AUTO-ADD to cart result.discountAmount += freeQty * freeProductPrice; console.log('[BOGO DEBUG] Adding free items to cart:', freeQty); // Add get_qty free items for (let i = 0; i < freeQty; i++) { result.freeItems.push({ id: freeProduct.id, name: freeProduct.name + ' (GRATIS)', price: '0.00', originalPrice: freeProduct.price, isFree: true, ruleId: rule.rule_id }); } } else if (rule.discount_type === 'percentage') { // Percentage korting op product Y (but not auto-added) const discountPerItem = freeProductPrice * (rule.discount_value / 100); result.discountAmount += freeQty * discountPerItem; console.log('[BOGO DEBUG] Percentage discount (not auto-added):', rule.discount_value + '%'); } else if (rule.discount_type === 'flat') { // Vaste korting op product Y (but not auto-added) const discountedPrice = Math.max(0, freeProductPrice - rule.discount_value).toFixed(2); const actualDiscount = freeProductPrice - parseFloat(discountedPrice); result.discountAmount += freeQty * actualDiscount; console.log('[BOGO DEBUG] Flat discount (not auto-added):', rule.discount_value); } } } return result; }, /** * Get suggested free items for a product (for display before adding to cart) * * @param {Object} product * @param {Array} products * @returns {Array} */ getSuggestedFreeItems(product, products) { if (!this.hasBogoRules(product)) return []; const rule = this.getPrimaryBogoRule(product); const suggestions = []; if (rule.type === 'buy_x_get_y' && rule.get_product_ids) { for (const freeProductId of rule.get_product_ids) { const freeProduct = products.find(p => p.id == freeProductId); if (freeProduct) { suggestions.push({ ...freeProduct, bogoLabel: `Gratis bij ${rule.buy_qty}x ${product.name}` }); } } } return suggestions; }, /** * Get BOGO discount info for a Y product based on X product's rules * Checks ALL BOGO rules on the X product, not just the primary one * * @param {Object} xProduct - The main product (X) with BOGO rules * @param {Object} yProduct - The upsell product (Y) to check * @returns {Object|null} - Discount info or null if no discount applies */ getYProductDiscount(xProduct, yProduct) { if (!xProduct || !this.hasBogoRules(xProduct)) return null; // Check ALL BOGO rules, not just the primary one for (const rule of xProduct.bogo_rules) { // Only for buy_x_get_y rules if (rule.type !== 'buy_x_get_y') continue; // Check if yProduct is in the get_product_ids if (!rule.get_product_ids || !rule.get_product_ids.includes(parseInt(yProduct.id))) { continue; } // Return discount info for the first matching rule return { rule_id: rule.rule_id, discount_type: rule.discount_type, discount_value: rule.discount_value, label: rule.label, badge_text: rule.badge_text }; } return null; } };