Quantity bug fix
This commit is contained in:
parent
ae6c5b0ca4
commit
cdc3b88b65
@ -21,7 +21,7 @@ function handleCreateOrder(): void
|
||||
|
||||
$input['payment_method'] = 'cod';
|
||||
$input['payment_method_title'] = 'Sales Panel Order';
|
||||
$input['status'] = 'on-hold';
|
||||
$input['status'] = 'processing-unpaid';
|
||||
|
||||
// IMPORTANT: Disable automatic price calculation
|
||||
// This ensures WooCommerce uses the prices from the sales panel
|
||||
|
||||
90
index.html
90
index.html
@ -23,7 +23,7 @@
|
||||
<!-- Load JavaScript modules with cache busting -->
|
||||
<script>
|
||||
// Change this version number to bust cache for all JS files
|
||||
const APP_VERSION = '1.2.0';
|
||||
const APP_VERSION = '1.5.1';
|
||||
|
||||
const scripts = [
|
||||
'js/services/api.js',
|
||||
@ -171,11 +171,12 @@
|
||||
class="font-bold mb-4 text-slate-400 uppercase text-[10px] tracking-widest border-b pb-2 text-center italic">
|
||||
Producten</h2>
|
||||
|
||||
<!-- Custom searchable dropdown for main product -->
|
||||
<div class="relative mb-6" x-data="{ open: false }" @click.away="open = false">
|
||||
<!-- Custom searchable dropdown for product selection with Add button -->
|
||||
<div class="flex gap-2 mb-6">
|
||||
<div class="relative flex-1" x-data="{ open: false }" @click.away="open = false">
|
||||
<button type="button" @click="open = !open"
|
||||
class="w-full border-2 border-slate-100 p-5 rounded-2xl font-black text-slate-700 bg-slate-50 outline-none focus:border-blue-500 shadow-sm text-left flex justify-between items-center">
|
||||
<span x-text="selectedProductId ? products.find(p => p.id == selectedProductId)?.name : '-- Kies Hoofdproduct --'" class="truncate"></span>
|
||||
<span x-text="selectedProductId ? products.find(p => p.id == selectedProductId)?.name : '-- Kies Product --'" class="truncate"></span>
|
||||
<svg class="w-5 h-5 text-slate-400 transition-transform" :class="{ 'rotate-180': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
@ -188,9 +189,9 @@
|
||||
@click.stop>
|
||||
</div>
|
||||
<div class="max-h-60 overflow-y-auto custom-scrollbar">
|
||||
<div @click="selectedProductId = ''; selectProduct(); open = false; productSearch = ''"
|
||||
<div @click="selectedProductId = ''; activeProduct = null; variations = []; open = false; productSearch = ''"
|
||||
class="p-4 hover:bg-slate-50 cursor-pointer text-slate-500 font-medium text-sm">
|
||||
-- Kies Hoofdproduct --
|
||||
-- Kies Product --
|
||||
</div>
|
||||
<template x-for="p in filteredProducts" :key="p.id">
|
||||
<div @click="selectedProductId = p.id; selectProduct(); open = false; productSearch = ''"
|
||||
@ -211,6 +212,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="addProductToCart()"
|
||||
:disabled="!activeProduct"
|
||||
class="bg-blue-600 text-white px-8 py-5 rounded-2xl font-black text-sm uppercase shadow-md hover:bg-blue-700 transition active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed whitespace-nowrap">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- BOGO Actie Banner - Only for buy_x_get_x rules -->
|
||||
<div x-show="activeProduct && activeProduct.bogo_rules && activeProduct.bogo_rules.length > 0 && activeProduct.bogo_rules[0].type === 'buy_x_get_x'" x-cloak
|
||||
@ -258,22 +265,37 @@
|
||||
<div x-show="getYProductBogoDiscount(u)">
|
||||
<div class="p-4 bg-gradient-to-r from-red-500 to-pink-500 rounded-2xl text-white shadow-lg">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-3 flex-1">
|
||||
<div class="bg-white/20 p-2 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<p class="font-black text-sm uppercase tracking-wide" x-text="u.name"></p>
|
||||
<p class="text-[10px] opacity-80" x-text="getYProductDiscountLabel(u)"></p>
|
||||
<p x-show="getYProductCountInCart(u) > 0" class="text-[10px] opacity-90 font-bold mt-1">
|
||||
<span x-text="getYProductCountInCart(u)"></span> in winkelwagen
|
||||
<!-- <span x-show="getYProductRemainingQty(u) > 0">
|
||||
• <span x-text="getYProductRemainingQty(u)"></span> nog beschikbaar
|
||||
</span> -->
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="toggleUpsell(u)"
|
||||
:class="isInCart(u.id) ? 'bg-slate-700' : 'bg-white'"
|
||||
class="text-red-500 px-4 py-2 rounded-xl font-black text-xs uppercase shadow-md hover:bg-red-50 transition disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap">
|
||||
<span x-text="isInCart(u.id) ? 'Toegevoegd' : ('+ €' + getYProductDiscountedPrice(u))"></span>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
x-show="getYProductCountInCart(u) > 0"
|
||||
@click.stop="removeOneYProduct(u)"
|
||||
class="bg-slate-700 text-white px-3 py-2 rounded-xl font-black text-xs uppercase shadow-md hover:bg-slate-600 transition">
|
||||
-
|
||||
</button>
|
||||
<button
|
||||
@click.stop="toggleUpsell(u)"
|
||||
:disabled="!canAddYProductDiscount(u, getYProductBogoDiscount(u))"
|
||||
class="bg-white text-red-500 px-4 py-2 rounded-xl font-black text-xs uppercase shadow-md hover:bg-red-50 transition disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap">
|
||||
<span x-text="'+ €' + getYProductDiscountedPrice(u)"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upsell information text for BOGO products -->
|
||||
@ -300,50 +322,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-8 border-t pt-6">
|
||||
<h2 class="font-bold mb-4 text-slate-400 uppercase text-[10px] tracking-widest italic text-center">
|
||||
Voeg extra product toe</h2>
|
||||
<div class="flex gap-2">
|
||||
<!-- Custom searchable dropdown for extra products -->
|
||||
<div class="relative flex-1" x-data="{ openExtra: false }" @click.away="openExtra = false">
|
||||
<button type="button" @click="openExtra = !openExtra"
|
||||
class="w-full border-2 border-slate-100 p-3 rounded-xl font-bold text-xs bg-slate-50 outline-none focus:border-green-500 text-left flex justify-between items-center text-slate-700">
|
||||
<span x-text="extraProductId ? (products.find(p => p.id == extraProductId)?.name + ' (€' + products.find(p => p.id == extraProductId)?.price + ')') : '-- Voeg product toe --'" class="truncate"></span>
|
||||
<svg class="w-4 h-4 text-slate-400 transition-transform flex-shrink-0 ml-2" :class="{ 'rotate-180': openExtra }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openExtra" x-cloak
|
||||
class="absolute z-50 w-full mt-2 bg-white border-2 border-slate-200 rounded-2xl shadow-xl overflow-hidden">
|
||||
<div class="p-3 border-b border-slate-100">
|
||||
<input type="text" x-model="extraProductSearch" placeholder="Zoek product..."
|
||||
class="w-full border border-slate-200 p-2 rounded-xl text-sm outline-none focus:border-green-500"
|
||||
@click.stop>
|
||||
</div>
|
||||
<div class="max-h-60 overflow-y-auto custom-scrollbar">
|
||||
<div @click="extraProductId = ''; openExtra = false; extraProductSearch = ''"
|
||||
class="p-3 hover:bg-slate-50 cursor-pointer text-slate-500 font-medium text-xs">
|
||||
-- Voeg product toe --
|
||||
</div>
|
||||
<template x-for="p in filteredExtraProducts" :key="'extra-'+p.id">
|
||||
<div @click="extraProductId = p.id; openExtra = false; extraProductSearch = ''"
|
||||
class="p-3 hover:bg-green-50 cursor-pointer font-bold text-xs text-slate-700"
|
||||
:class="{ 'bg-green-100': extraProductId == p.id }">
|
||||
<span x-text="p.name + ' (€' + p.price + ')'"></span>
|
||||
</div>
|
||||
</template>
|
||||
<div x-show="filteredExtraProducts.length === 0" class="p-3 text-slate-400 text-xs text-center">
|
||||
Geen producten gevonden
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="addExtraItem()"
|
||||
class="bg-green-600 text-white px-6 py-3 rounded-xl font-black text-[10px] uppercase shadow-md hover:bg-green-700 transition active:scale-95">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 lg:col-span-3">
|
||||
|
||||
@ -62,19 +62,24 @@ const CartComponent = {
|
||||
|
||||
/**
|
||||
* Toggle upsell product in cart
|
||||
* For products with Y discount and get_qty > 1, this ONLY adds items (use - button to remove)
|
||||
* For regular products, this toggles (add/remove)
|
||||
* @param {Object} product
|
||||
*/
|
||||
toggleUpsell(product) {
|
||||
const idx = this.cart.findIndex(i => parseInt(i.id) === parseInt(product.id) && !i.isFree);
|
||||
if (idx > -1) {
|
||||
this.cart.splice(idx, 1);
|
||||
} else {
|
||||
// Check if this product has a Y discount from active product
|
||||
const yDiscount = this.getYProductBogoDiscount(product);
|
||||
|
||||
// For Y-products with discount, ONLY ADD (never remove via this button)
|
||||
if (yDiscount) {
|
||||
console.log('[Y-PRODUCT DEBUG] Adding Y-product with discount:', {
|
||||
product_name: product.name,
|
||||
has_discount: true
|
||||
});
|
||||
|
||||
// Check if we can add more discounted items (respect get_qty limit)
|
||||
if (!this.canAddYProductDiscount(product, yDiscount)) {
|
||||
console.log('[Y-PRODUCT DEBUG] Cannot add more discounted items - limit reached');
|
||||
alert('Maximum aantal kortingsproducten bereikt voor deze actie.');
|
||||
return;
|
||||
}
|
||||
@ -83,6 +88,12 @@ const CartComponent = {
|
||||
const discountedPrice = this.getYProductDiscountedPrice(product);
|
||||
const discountLabel = this.getYProductDiscountLabel(product);
|
||||
|
||||
console.log('[Y-PRODUCT DEBUG] Adding with discount:', {
|
||||
discountedPrice,
|
||||
discountLabel,
|
||||
originalPrice: product.price
|
||||
});
|
||||
|
||||
this.cart.push({
|
||||
id: parseInt(product.id),
|
||||
name: product.name + (discountLabel ? ' (' + discountLabel + ')' : ''),
|
||||
@ -93,7 +104,13 @@ const CartComponent = {
|
||||
ruleId: yDiscount.rule_id
|
||||
});
|
||||
} else {
|
||||
// Add with regular price
|
||||
// Regular product without discount - toggle behavior
|
||||
const idx = this.cart.findIndex(i => parseInt(i.id) === parseInt(product.id) && !i.isFree);
|
||||
if (idx > -1) {
|
||||
console.log('[Y-PRODUCT DEBUG] Removing regular product from cart:', product.name);
|
||||
this.cart.splice(idx, 1);
|
||||
} else {
|
||||
console.log('[Y-PRODUCT DEBUG] Adding regular product with regular price');
|
||||
this.cart.push({
|
||||
id: parseInt(product.id),
|
||||
name: product.name,
|
||||
@ -105,23 +122,52 @@ const CartComponent = {
|
||||
this.recalculateBogo();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove one Y-product from cart (for - button)
|
||||
* @param {Object} product
|
||||
*/
|
||||
removeOneYProduct(product) {
|
||||
console.log('[Y-PRODUCT DEBUG] Removing one Y-product:', product.name);
|
||||
|
||||
// Find last occurrence of this product and remove it
|
||||
for (let i = this.cart.length - 1; i >= 0; i--) {
|
||||
if (parseInt(this.cart[i].id) === parseInt(product.id) && !this.cart[i].isFree) {
|
||||
console.log('[Y-PRODUCT DEBUG] Found and removing item at index:', i);
|
||||
this.cart.splice(i, 1);
|
||||
this.recalculateBogo();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Recalculate BOGO discounts based on current cart
|
||||
*/
|
||||
recalculateBogo() {
|
||||
console.log('[CART DEBUG] recalculateBogo() called, current cart:', this.cart);
|
||||
|
||||
// Remove existing auto-added free items from cart (but keep manually added discounted items)
|
||||
this.cart = this.cart.filter(item => !item.isFree);
|
||||
|
||||
console.log('[CART DEBUG] Cart after removing free items:', this.cart);
|
||||
|
||||
// Calculate new BOGO
|
||||
const bogoResult = BogoService.calculateBogoForCart(this.cart, this.products);
|
||||
|
||||
console.log('[CART DEBUG] BOGO calculation result:', {
|
||||
discountAmount: bogoResult.discountAmount,
|
||||
freeItemsCount: bogoResult.freeItems.length,
|
||||
freeItems: bogoResult.freeItems,
|
||||
appliedRulesCount: bogoResult.appliedRules.length
|
||||
});
|
||||
|
||||
this.bogoDiscount = bogoResult.discountAmount;
|
||||
this.bogoFreeItems = bogoResult.freeItems;
|
||||
this.appliedBogoRules = bogoResult.appliedRules;
|
||||
|
||||
// Add free items to cart (only for buy_x_get_x rules)
|
||||
// buy_x_get_y items are NOT auto-added, user must add them manually
|
||||
// Add free items to cart (for both buy_x_get_x and buy_x_get_y with free_product)
|
||||
for (const freeItem of bogoResult.freeItems) {
|
||||
console.log('[CART DEBUG] Adding free item to cart:', freeItem);
|
||||
this.cart.push({
|
||||
id: parseInt(freeItem.id),
|
||||
variation_id: 0,
|
||||
@ -132,6 +178,8 @@ const CartComponent = {
|
||||
ruleId: freeItem.ruleId
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[CART DEBUG] Final cart after BOGO:', this.cart);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -146,11 +194,37 @@ const CartComponent = {
|
||||
|
||||
const rule = product.bogo_rules[0];
|
||||
|
||||
console.log('[BOGO ADD DEBUG] Adding discounted item:', {
|
||||
product_name: product.name,
|
||||
rule_id: rule.rule_id,
|
||||
discount_type: rule.discount_type,
|
||||
discount_value: rule.discount_value,
|
||||
get_qty: rule.get_qty,
|
||||
buy_qty: rule.buy_qty,
|
||||
recursive: rule.recursive
|
||||
});
|
||||
|
||||
// Check if this is a discount type rule (not free)
|
||||
if (rule.discount_type !== 'flat' && rule.discount_type !== 'percentage') {
|
||||
console.log('[BOGO ADD DEBUG] Not a discount type rule, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Count current items
|
||||
const fullPriceCount = this.cart.filter(item =>
|
||||
parseInt(item.id) === parseInt(product.id) && !item.isDiscounted
|
||||
).length;
|
||||
|
||||
const discountedCount = this.cart.filter(item =>
|
||||
parseInt(item.id) === parseInt(product.id) && item.isDiscounted
|
||||
).length;
|
||||
|
||||
console.log('[BOGO ADD DEBUG] Current cart state:', {
|
||||
fullPriceCount,
|
||||
discountedCount,
|
||||
maxAllowed: rule.get_qty
|
||||
});
|
||||
|
||||
// Calculate discounted price
|
||||
let discountedPrice;
|
||||
let discountLabel;
|
||||
@ -164,6 +238,12 @@ const CartComponent = {
|
||||
discountLabel = rule.discount_value + '% korting';
|
||||
}
|
||||
|
||||
console.log('[BOGO ADD DEBUG] Calculated price:', {
|
||||
originalPrice: product.price,
|
||||
discountedPrice,
|
||||
discountLabel
|
||||
});
|
||||
|
||||
// Add the discounted item to cart (this IS the 2nd item, not a 3rd)
|
||||
// isFree: false because it's not free, just discounted
|
||||
this.cart.push({
|
||||
@ -176,6 +256,8 @@ const CartComponent = {
|
||||
isDiscounted: true,
|
||||
ruleId: rule.rule_id
|
||||
});
|
||||
|
||||
console.log('[BOGO ADD DEBUG] Item added to cart. New discounted count:', discountedCount + 1);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -213,6 +295,12 @@ const CartComponent = {
|
||||
|
||||
// Check if we have enough full-price items
|
||||
if (fullPriceCount < fullPriceNeeded) {
|
||||
console.log('[BOGO CAN ADD DEBUG] Not enough full-price items:', {
|
||||
product_name: product.name,
|
||||
fullPriceCount,
|
||||
fullPriceNeeded,
|
||||
get_qty: rule.get_qty
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -223,6 +311,16 @@ const CartComponent = {
|
||||
maxDiscounted = Math.floor(fullPriceCount / fullPriceNeeded) * rule.get_qty;
|
||||
}
|
||||
|
||||
console.log('[BOGO CAN ADD DEBUG] Checking if can add:', {
|
||||
product_name: product.name,
|
||||
fullPriceCount,
|
||||
discountedCount,
|
||||
maxDiscounted,
|
||||
get_qty: rule.get_qty,
|
||||
canAdd: discountedCount < maxDiscounted,
|
||||
remaining: maxDiscounted - discountedCount
|
||||
});
|
||||
|
||||
// Can add if we haven't reached the max
|
||||
return discountedCount < maxDiscounted;
|
||||
},
|
||||
@ -354,10 +452,64 @@ const CartComponent = {
|
||||
// Maximum Y products allowed = timesApplied * get_qty
|
||||
const maxAllowed = timesApplied * rule.get_qty;
|
||||
|
||||
console.log('[Y-PRODUCT CAN ADD DEBUG] Checking Y product limit:', {
|
||||
yProduct_name: yProduct.name,
|
||||
xProduct_name: this.activeProduct.name,
|
||||
xProductCount,
|
||||
discountedCount,
|
||||
timesApplied,
|
||||
get_qty: rule.get_qty,
|
||||
maxAllowed,
|
||||
canAdd: discountedCount < maxAllowed,
|
||||
remaining: maxAllowed - discountedCount
|
||||
});
|
||||
|
||||
// Can add if we haven't reached the max
|
||||
return discountedCount < maxAllowed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get count of Y product in cart (for display)
|
||||
* @param {Object} yProduct - The upsell product
|
||||
* @returns {number}
|
||||
*/
|
||||
getYProductCountInCart(yProduct) {
|
||||
return this.cart.filter(item =>
|
||||
parseInt(item.id) === parseInt(yProduct.id) && !item.isFree
|
||||
).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get remaining quantity that can be added for Y product
|
||||
* @param {Object} yProduct - The upsell product
|
||||
* @returns {number}
|
||||
*/
|
||||
getYProductRemainingQty(yProduct) {
|
||||
const discount = this.getYProductBogoDiscount(yProduct);
|
||||
if (!discount || !this.activeProduct) return 0;
|
||||
|
||||
const rule = this.activeProduct.bogo_rules?.find(r => r.rule_id === discount.rule_id);
|
||||
if (!rule) return 0;
|
||||
|
||||
const discountedCount = this.cart.filter(item =>
|
||||
parseInt(item.id) === parseInt(yProduct.id) &&
|
||||
item.isDiscounted &&
|
||||
item.ruleId === discount.rule_id
|
||||
).length;
|
||||
|
||||
const xProductCount = this.cart.filter(item =>
|
||||
parseInt(item.id) === parseInt(this.activeProduct.id) && !item.isFree
|
||||
).length;
|
||||
|
||||
let timesApplied = 1;
|
||||
if (rule.recursive && xProductCount >= rule.buy_qty) {
|
||||
timesApplied = Math.floor(xProductCount / rule.buy_qty);
|
||||
}
|
||||
|
||||
const maxAllowed = timesApplied * rule.get_qty;
|
||||
return Math.max(0, maxAllowed - discountedCount);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the cart
|
||||
*/
|
||||
|
||||
@ -14,6 +14,9 @@ const ProductsComponent = {
|
||||
selectedVariationId: '',
|
||||
variations: [],
|
||||
extraProductId: '',
|
||||
extraProduct: null,
|
||||
extraVariations: [],
|
||||
extraSelectedVariationId: '',
|
||||
recommendedOptions: [],
|
||||
productSearch: '',
|
||||
extraProductSearch: ''
|
||||
@ -50,21 +53,92 @@ const ProductsComponent = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle main product selection
|
||||
* Handle main product selection (no longer auto-adds to cart)
|
||||
*/
|
||||
selectProduct() {
|
||||
const p = this.products.find(x => x.id == this.selectedProductId);
|
||||
if (!p) return;
|
||||
|
||||
console.log('[PRODUCT DEBUG] Selected product:', p);
|
||||
|
||||
this.activeProduct = p;
|
||||
this.variations = p.variation_details || [];
|
||||
this.cart = [];
|
||||
this.bogoDiscount = 0;
|
||||
this.bogoFreeItems = [];
|
||||
this.appliedBogoRules = [];
|
||||
this.selectedVariationId = '';
|
||||
|
||||
// Don't auto-add to cart anymore - wait for user to click Add button
|
||||
// Don't clear cart anymore - allow adding multiple products
|
||||
},
|
||||
|
||||
/**
|
||||
* Add selected product to cart (new method for Add button)
|
||||
*/
|
||||
addProductToCart() {
|
||||
if (!this.activeProduct) return;
|
||||
|
||||
const p = this.activeProduct;
|
||||
|
||||
// If variable product, require variation selection
|
||||
if (p.type === 'variable') {
|
||||
if (!this.selectedVariationId) {
|
||||
alert('Selecteer eerst een variatie');
|
||||
return;
|
||||
}
|
||||
|
||||
const v = this.variations.find(x => x.id == this.selectedVariationId);
|
||||
if (!v) return;
|
||||
|
||||
console.log('[PRODUCT DEBUG] Adding variable product with variation:', v);
|
||||
|
||||
this.cart.push({
|
||||
id: parseInt(p.id),
|
||||
variation_id: parseInt(v.id),
|
||||
name: p.name + ' - ' + this.getVarName(v),
|
||||
price: v.price,
|
||||
isFree: false
|
||||
});
|
||||
} else {
|
||||
// Simple product
|
||||
console.log('[PRODUCT DEBUG] Adding simple product');
|
||||
|
||||
this.cart.push({
|
||||
id: parseInt(p.id),
|
||||
name: p.name,
|
||||
price: p.price,
|
||||
isFree: false
|
||||
});
|
||||
}
|
||||
|
||||
this.recalculateBogo();
|
||||
this.loadRecommendations(p);
|
||||
|
||||
// Keep selection visible (don't reset)
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle variation selection (no longer auto-adds to cart)
|
||||
*/
|
||||
selectVariation() {
|
||||
// Just store the selection, don't add to cart
|
||||
// User must click Add button to add to cart
|
||||
console.log('[PRODUCT DEBUG] Variation selected:', this.selectedVariationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select extra product (handles variations like main product)
|
||||
*/
|
||||
selectExtraProduct() {
|
||||
const p = this.products.find(x => x.id == this.extraProductId);
|
||||
if (!p) return;
|
||||
|
||||
console.log('[EXTRA PRODUCT DEBUG] Selected product:', p);
|
||||
|
||||
this.extraProduct = p;
|
||||
this.extraVariations = p.variation_details || [];
|
||||
this.extraSelectedVariationId = '';
|
||||
|
||||
// If not a variable product, add directly
|
||||
if (p.type !== 'variable') {
|
||||
console.log('[EXTRA PRODUCT DEBUG] Simple product, adding to cart');
|
||||
this.cart.push({
|
||||
id: parseInt(p.id),
|
||||
name: p.name,
|
||||
@ -72,44 +146,51 @@ const ProductsComponent = {
|
||||
isFree: false
|
||||
});
|
||||
this.recalculateBogo();
|
||||
this.loadRecommendations(p);
|
||||
this.mergeRecommendations(p);
|
||||
this.extraProductId = '';
|
||||
this.extraProduct = null;
|
||||
}
|
||||
// If variable product, wait for variation selection
|
||||
else {
|
||||
console.log('[EXTRA PRODUCT DEBUG] Variable product, waiting for variation selection. Variations:', this.extraVariations);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle variation selection
|
||||
* Handle extra product variation selection
|
||||
*/
|
||||
selectVariation() {
|
||||
const v = this.variations.find(x => x.id == this.selectedVariationId);
|
||||
if (!v) return;
|
||||
selectExtraVariation() {
|
||||
const v = this.extraVariations.find(x => x.id == this.extraSelectedVariationId);
|
||||
if (!v || !this.extraProduct) return;
|
||||
|
||||
this.cart = [{
|
||||
id: parseInt(this.activeProduct.id),
|
||||
console.log('[EXTRA PRODUCT DEBUG] Selected variation:', v);
|
||||
|
||||
this.cart.push({
|
||||
id: parseInt(this.extraProduct.id),
|
||||
variation_id: parseInt(v.id),
|
||||
name: this.activeProduct.name + ' - ' + this.getVarName(v),
|
||||
name: this.extraProduct.name + ' - ' + this.getVarName(v),
|
||||
price: v.price,
|
||||
isFree: false
|
||||
}];
|
||||
});
|
||||
|
||||
this.recalculateBogo();
|
||||
this.loadRecommendations(this.activeProduct);
|
||||
this.mergeRecommendations(this.extraProduct);
|
||||
|
||||
// Reset extra product state
|
||||
this.extraProductId = '';
|
||||
this.extraProduct = null;
|
||||
this.extraVariations = [];
|
||||
this.extraSelectedVariationId = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Add extra product to cart
|
||||
* Cancel extra product selection
|
||||
*/
|
||||
addExtraItem() {
|
||||
const p = this.products.find(x => x.id == this.extraProductId);
|
||||
if (!p) return;
|
||||
|
||||
this.cart.push({
|
||||
id: parseInt(p.id),
|
||||
name: p.name,
|
||||
price: p.price,
|
||||
isFree: false
|
||||
});
|
||||
this.recalculateBogo();
|
||||
cancelExtraProduct() {
|
||||
this.extraProductId = '';
|
||||
this.extraProduct = null;
|
||||
this.extraVariations = [];
|
||||
this.extraSelectedVariationId = '';
|
||||
},
|
||||
|
||||
/**
|
||||
@ -136,6 +217,42 @@ const ProductsComponent = {
|
||||
this.recommendedOptions = this.products.filter(p => ids.includes(parseInt(p.id)));
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge recommendations from an additional product (for extra products)
|
||||
* @param {Object} product
|
||||
*/
|
||||
mergeRecommendations(product) {
|
||||
console.log('[MERGE RECOMMENDATIONS] Merging recommendations for:', product.name);
|
||||
|
||||
// Get existing recommendation IDs
|
||||
const existingIds = this.recommendedOptions.map(p => parseInt(p.id));
|
||||
console.log('[MERGE RECOMMENDATIONS] Existing IDs:', existingIds);
|
||||
|
||||
// Get new product's recommendations
|
||||
let newIds = [];
|
||||
if (product.recommended_ids && product.recommended_ids.length) {
|
||||
newIds = product.recommended_ids.map(id => parseInt(id));
|
||||
} else {
|
||||
// Fallback: combine Woo upsells + cross-sells
|
||||
const ups = (product.upsell_ids || []).map(id => parseInt(id));
|
||||
const crs = (product.cross_sell_ids || []).map(id => parseInt(id));
|
||||
newIds = [...ups, ...crs];
|
||||
}
|
||||
|
||||
newIds = [...new Set(newIds)].filter(Boolean);
|
||||
console.log('[MERGE RECOMMENDATIONS] New IDs to add:', newIds);
|
||||
|
||||
// Merge: add new recommendations that aren't already in the list
|
||||
const idsToAdd = newIds.filter(id => !existingIds.includes(id));
|
||||
console.log('[MERGE RECOMMENDATIONS] IDs to add (filtered):', idsToAdd);
|
||||
|
||||
const newRecommendations = this.products.filter(p => idsToAdd.includes(parseInt(p.id)));
|
||||
console.log('[MERGE RECOMMENDATIONS] New recommendations to add:', newRecommendations.map(p => p.name));
|
||||
|
||||
this.recommendedOptions = [...this.recommendedOptions, ...newRecommendations];
|
||||
console.log('[MERGE RECOMMENDATIONS] Final recommendations count:', this.recommendedOptions.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get variation name from attributes
|
||||
* @param {Object} v - Variation object
|
||||
@ -154,6 +271,10 @@ const ProductsComponent = {
|
||||
this.activeProduct = null;
|
||||
this.variations = [];
|
||||
this.recommendedOptions = [];
|
||||
this.extraProductId = '';
|
||||
this.extraProduct = null;
|
||||
this.extraVariations = [];
|
||||
this.extraSelectedVariationId = '';
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@ -142,27 +142,58 @@ const BogoService = {
|
||||
// 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
|
||||
// For buy_x_get_y, we DON'T automatically add items to cart
|
||||
// The user must manually add them via the upsell section
|
||||
// We only track the discount amount for display purposes
|
||||
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) continue;
|
||||
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 (but not auto-added)
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user