feat: Add quantity support for product modules, multiply prices in order snapshot and pdf
Some checks failed
Staging Build / build (push) Has been cancelled
Some checks failed
Staging Build / build (push) Has been cancelled
This commit is contained in:
@@ -50,6 +50,7 @@ function hashOrderSnapshot(snapshot: object): string {
|
||||
*/
|
||||
export async function submitOrder(params: {
|
||||
selections: WizardSelections
|
||||
moduleQuantities?: Record<string, number>
|
||||
products?: Product[]
|
||||
categories?: Category[]
|
||||
customerProfile: Partial<Profile>
|
||||
@@ -57,7 +58,7 @@ export async function submitOrder(params: {
|
||||
endCustomer?: EndCustomer | null
|
||||
billingInterval?: 'one_time' | 'monthly'
|
||||
}): Promise<Order> {
|
||||
const { selections, customerProfile, endCustomerId, endCustomer, billingInterval } = params
|
||||
const { selections, moduleQuantities, customerProfile, endCustomerId, endCustomer, billingInterval } = params
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
@@ -106,7 +107,7 @@ export async function submitOrder(params: {
|
||||
// 1. Snapshots aufbauen
|
||||
// Wenn Endkunde vorhanden: dessen Daten einfrieren; sonst Partner-Profil (Fallback)
|
||||
const customerSnapshot = buildCustomerSnapshot(customerProfile, endCustomer ?? null)
|
||||
const orderSnapshot = buildOrderSnapshot(selections, dbProducts, dbCategories, billingInterval)
|
||||
const orderSnapshot = buildOrderSnapshot(selections, dbProducts, dbCategories, billingInterval, moduleQuantities)
|
||||
|
||||
// 2. Idempotenz-Guard: Prüfen ob identische Bestellung in den letzten 30s existiert
|
||||
const orderHash = hashOrderSnapshot(orderSnapshot)
|
||||
|
||||
@@ -89,6 +89,7 @@ export async function createProduct(
|
||||
price: m.price,
|
||||
requirements: m.requirements || [],
|
||||
exclusions: m.exclusions || [],
|
||||
has_quantity: m.has_quantity ?? false,
|
||||
}))
|
||||
const { error: modulesError } = await supabase
|
||||
.from('product_modules')
|
||||
@@ -125,6 +126,7 @@ export async function updateProduct(id: string, product: Partial<Product>, modul
|
||||
price: m.price,
|
||||
requirements: m.requirements || [],
|
||||
exclusions: m.exclusions || [],
|
||||
has_quantity: m.has_quantity ?? false,
|
||||
}))
|
||||
const { error: modulesError } = await supabase
|
||||
.from('product_modules')
|
||||
|
||||
@@ -85,7 +85,8 @@ export function buildOrderSnapshot(
|
||||
selections: WizardSelections,
|
||||
products: Product[],
|
||||
categories: Category[],
|
||||
billingInterval?: 'one_time' | 'monthly'
|
||||
billingInterval?: 'one_time' | 'monthly',
|
||||
moduleQuantities?: Record<string, number>
|
||||
): OrderSnapshot {
|
||||
const items: OrderItem[] = []
|
||||
let subtotal = 0
|
||||
@@ -103,13 +104,18 @@ export function buildOrderSnapshot(
|
||||
const selectedModules = sel.moduleIds
|
||||
.map((modId) => prod.modules?.find((m) => m.id === modId))
|
||||
.filter((m): m is NonNullable<typeof m> => !!m)
|
||||
.map((mod) => ({
|
||||
module_id: mod.id,
|
||||
module_name: mod.name,
|
||||
price: mod.price,
|
||||
}))
|
||||
.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.price, 0)
|
||||
const moduleTotal = selectedModules.reduce((acc, m) => acc + (m.total_price || m.price), 0)
|
||||
const itemTotal = prod.base_price + moduleTotal
|
||||
subtotal += itemTotal
|
||||
|
||||
@@ -157,7 +163,8 @@ export function transformToLicenseBundle(
|
||||
selections: WizardSelections,
|
||||
products: Product[],
|
||||
categories: Category[],
|
||||
customer: Partial<Profile>
|
||||
customer: Partial<Profile>,
|
||||
moduleQuantities?: Record<string, number>
|
||||
): LicenseBundle {
|
||||
const options: LicenseOption[] = []
|
||||
let monthlyTotal = 0
|
||||
@@ -186,6 +193,7 @@ export function transformToLicenseBundle(
|
||||
for (const modId of sel.moduleIds) {
|
||||
const mod = prod.modules?.find((m) => m.id === modId)
|
||||
if (!mod) continue
|
||||
const qty = moduleQuantities?.[mod.id] || 1
|
||||
|
||||
options.push({
|
||||
key: `${prodKey}__${slugify(mod.name)}`,
|
||||
@@ -194,10 +202,11 @@ export function transformToLicenseBundle(
|
||||
// Module erben das Billing-Intervall des übergeordneten Produkts
|
||||
billing: prod.billing_interval,
|
||||
price_net: mod.price,
|
||||
quantity: qty,
|
||||
})
|
||||
|
||||
if (prod.billing_interval === 'monthly') monthlyTotal += mod.price
|
||||
else oneTimeTotal += mod.price
|
||||
if (prod.billing_interval === 'monthly') monthlyTotal += mod.price * qty
|
||||
else oneTimeTotal += mod.price * qty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,16 +247,19 @@ export function licenseBundleFromSnapshot(
|
||||
else oneTimeTotal += item.base_price
|
||||
|
||||
for (const mod of item.selected_modules) {
|
||||
const qty = mod.quantity || 1
|
||||
const modPrice = mod.total_price || (mod.price * qty)
|
||||
options.push({
|
||||
key: `${prodKey}__${slugify(mod.module_name)}`,
|
||||
label: mod.module_name,
|
||||
active: true,
|
||||
billing: item.billing_interval,
|
||||
price_net: mod.price,
|
||||
quantity: qty,
|
||||
})
|
||||
|
||||
if (item.billing_interval === 'monthly') monthlyTotal += mod.price
|
||||
else oneTimeTotal += mod.price
|
||||
if (item.billing_interval === 'monthly') monthlyTotal += modPrice
|
||||
else oneTimeTotal += modPrice
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export type ProductModule = {
|
||||
requirements: string[] // UUID[] anderer Module, die aktiv sein müssen
|
||||
exclusions: string[] // UUID[] von Modulen, die nicht gleichzeitig aktiv sein dürfen
|
||||
created_at: string
|
||||
has_quantity?: boolean
|
||||
}
|
||||
|
||||
export type Profile = {
|
||||
@@ -103,6 +104,8 @@ export type OrderModuleSnapshot = {
|
||||
module_id: string
|
||||
module_name: string
|
||||
price: number
|
||||
quantity?: number
|
||||
total_price?: number
|
||||
}
|
||||
|
||||
export type OrderItem = {
|
||||
@@ -154,6 +157,7 @@ export type LicenseOption = {
|
||||
active: boolean
|
||||
billing: BillingInterval
|
||||
price_net: number
|
||||
quantity?: number
|
||||
}
|
||||
|
||||
export type LicenseBundle = {
|
||||
|
||||
Reference in New Issue
Block a user