feat(multiselect): support multi-select categories and free items limits in category config and order wizard
All checks were successful
Staging Build / build (push) Successful in 2m38s

This commit is contained in:
DanielS
2026-07-07 16:56:24 +02:00
parent 00d1be3e4f
commit cc7445e4c5
9 changed files with 451 additions and 212 deletions

View File

@@ -99,40 +99,59 @@ export function buildOrderSnapshot(
for (const cat of categories) {
const sel: CategorySelection | undefined = selections[cat.id]
if (!sel?.productId) continue
if (!sel) continue
const prod = products.find((p) => p.id === sel.productId)
if (!prod) continue
dominantTaxRate = prod.tax_rate
const selectedModules = sel.moduleIds
.map((modId) => prod.modules?.find((m) => m.id === modId))
.filter((m): m is NonNullable<typeof m> => !!m)
.map((mod) => {
const qty = moduleQuantities?.[mod.id] || 1
return {
module_id: mod.id,
module_name: mod.name,
price: mod.price,
quantity: qty,
total_price: mod.price * qty,
}
const selectedProds: Product[] = []
if (cat.allow_multiselect && sel.productIds && sel.productIds.length > 0) {
sel.productIds.forEach(pId => {
const p = products.find(prod => prod.id === pId)
if (p) selectedProds.push(p)
})
} else if (sel.productId) {
const p = products.find(prod => prod.id === sel.productId)
if (p) selectedProds.push(p)
}
const moduleTotal = selectedModules.reduce((acc, m) => acc + (m.total_price || m.price), 0)
const itemTotal = prod.base_price + moduleTotal
subtotal += itemTotal
if (selectedProds.length === 0) continue
items.push({
category_id: cat.id,
category_name: cat.name,
product_id: prod.id,
product_name: prod.name,
base_price: prod.base_price,
billing_interval: prod.billing_interval,
selected_modules: selectedModules,
item_total: itemTotal,
// Apply free items limit: sort selected products by base_price ascending so that the cheapest are free
const sortedProds = [...selectedProds].sort((a, b) => a.base_price - b.base_price)
const freeLimit = cat.allow_multiselect ? cat.free_items_limit : 0
sortedProds.forEach((prod, sortedIndex) => {
dominantTaxRate = prod.tax_rate
const selectedModules = sel.moduleIds
.map((modId) => prod.modules?.find((m) => m.id === modId))
.filter((m): m is NonNullable<typeof m> => !!m)
.map((mod) => {
const qty = moduleQuantities?.[mod.id] || 1
return {
module_id: mod.id,
module_name: mod.name,
price: mod.price,
quantity: qty,
total_price: mod.price * qty,
}
})
const moduleTotal = selectedModules.reduce((acc, m) => acc + (m.total_price || m.price), 0)
// If this product falls under the free limit, set its base price to 0
const actualBasePrice = (sortedIndex < freeLimit) ? 0 : prod.base_price
const itemTotal = actualBasePrice + moduleTotal
subtotal += itemTotal
items.push({
category_id: cat.id,
category_name: cat.name,
product_id: prod.id,
product_name: prod.name + (actualBasePrice === 0 ? " (Inklusive/Frei)" : ""),
base_price: actualBasePrice,
billing_interval: prod.billing_interval,
selected_modules: selectedModules,
item_total: itemTotal,
})
})
}