190 lines
6.3 KiB
JavaScript

/**
* 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 to cart
result.discountAmount = freeQty * parseFloat(product.price);
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 is free
for (const freeProductId of rule.get_product_ids) {
const freeProduct = products.find(p => p.id == freeProductId);
if (!freeProduct) continue;
if (rule.discount_type === 'free_product' || rule.discount_value >= 100) {
result.discountAmount += freeQty * parseFloat(freeProduct.price);
result.freeItems.push({
id: freeProduct.id,
name: freeProduct.name + ' (GRATIS)',
price: '0.00',
originalPrice: freeProduct.price,
isFree: true,
ruleId: rule.rule_id
});
}
}
}
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;
}
};