/** * Pricing Engine for Proinn Configurator * Based on Dutch market standard pricing */ import { PROFILE_WIDTH, RAIL_HEIGHT_SLIM, RAIL_HEIGHT_ROBUST, type GridLayout, type DoorModel, } from './door-models'; // Pricing constants (EUR) const STEEL_PRICE_PER_METER = 45; const GLASS_PRICE_PER_SQM = 140; const BASE_FEE = 650; const SIDE_PANEL_SURCHARGE = 250; const DOUBLE_DOOR_SURCHARGE = 350; const TAATS_MECHANISM_SURCHARGE = 450; const HANDLE_PRICES: Record = { 'beugelgreep': 85, 'hoekgreep': 75, 'maangreep': 95, 'ovaalgreep': 90, 'klink': 65, 'u-greep': 55, 'geen': 0, }; // Premium finish surcharges const FINISH_SURCHARGES: Record = { 'zwart': 0, 'grijs': 0, 'brons': 0, 'goud': 150, 'beige': 75, 'ral': 200, }; // Frame size multipliers (relative to standard 40mm) const FRAME_SIZE_MULTIPLIERS: Record = { 20: 0.7, 30: 0.85, 40: 1.0, }; function getHorizontalDividerCount(gridLayout: GridLayout): number { switch (gridLayout) { case '2-vlak': return 1; case '3-vlak': return 2; case '4-vlak': return 3; case '6-vlak': return 2; case '8-vlak': return 3; case 'kruis': return 1; case 'ongelijk-3': return 2; case 'boerderij': return 1; case 'herenhuis': return 2; default: return 0; } } function hasVerticalDividers(gridLayout: GridLayout): boolean { return ['6-vlak', '8-vlak', 'kruis', 'boerderij'].includes(gridLayout); } function calculateSteelLength( doorWidth: number, doorHeight: number, gridLayout: GridLayout, hasPaneelVerticalDivider: boolean ): number { const innerWidth = doorWidth - PROFILE_WIDTH * 2; // Perimeter let totalLength = doorHeight * 2 + innerWidth * 2; // Horizontal dividers const hDividers = getHorizontalDividerCount(gridLayout); totalLength += innerWidth * hDividers; // Vertical dividers from grid pattern if (hasVerticalDividers(gridLayout)) { const innerHeight = doorHeight - RAIL_HEIGHT_ROBUST * 2; totalLength += innerHeight; } // Paneel type adds center vertical divider if (hasPaneelVerticalDivider) { totalLength += doorHeight - RAIL_HEIGHT_ROBUST * 2; } return totalLength / 1000; } function calculateGlassArea( doorWidth: number, doorHeight: number, gridLayout: GridLayout ): number { const glassWidth = doorWidth - PROFILE_WIDTH * 2; const glassHeight = doorHeight - RAIL_HEIGHT_ROBUST * 2; const hDividers = getHorizontalDividerCount(gridLayout); let dividerArea = glassWidth * RAIL_HEIGHT_SLIM * hDividers; // Vertical divider area if (hasVerticalDividers(gridLayout)) { dividerArea += PROFILE_WIDTH * (glassHeight - RAIL_HEIGHT_SLIM * hDividers); } return (glassWidth * glassHeight - dividerArea) / 1_000_000; } export interface PriceBreakdown { steelCost: number; glassCost: number; baseFee: number; mechanismSurcharge: number; sidePanelSurcharge: number; handleCost: number; finishSurcharge: number; totalPrice: number; steelLengthM: number; glassAreaSqm: number; } export function calculatePrice( doorWidth: number, doorHeight: number, doorType: DoorModel, gridLayout: GridLayout, doorConfig: 'enkele' | 'dubbele', sidePanel: 'geen' | 'links' | 'rechts' | 'beide', handle: string, finish: string = 'zwart', frameSize: number = 40 ): PriceBreakdown { const leafCount = doorConfig === 'dubbele' ? 2 : 1; const hasPaneelVerticalDivider = doorType === 'paneel'; const frameSizeMultiplier = FRAME_SIZE_MULTIPLIERS[frameSize] ?? 1.0; const steelLengthPerLeaf = calculateSteelLength(doorWidth, doorHeight, gridLayout, hasPaneelVerticalDivider); const glassAreaPerLeaf = calculateGlassArea(doorWidth, doorHeight, gridLayout); const totalSteelLength = steelLengthPerLeaf * leafCount; const totalGlassArea = glassAreaPerLeaf * leafCount; const sidePanelCount = sidePanel === 'beide' ? 2 : (sidePanel === 'geen' ? 0 : 1); const steelCost = Math.round(totalSteelLength * STEEL_PRICE_PER_METER * frameSizeMultiplier); const glassCost = Math.round(totalGlassArea * GLASS_PRICE_PER_SQM); let mechanismSurcharge = 0; if (doorType === 'taats') mechanismSurcharge += TAATS_MECHANISM_SURCHARGE; if (doorConfig === 'dubbele') mechanismSurcharge += DOUBLE_DOOR_SURCHARGE; const handleCost = HANDLE_PRICES[handle] || 0; const sidePanelSurchrg = sidePanelCount * SIDE_PANEL_SURCHARGE; const finishSurcharge = FINISH_SURCHARGES[finish] ?? 0; const totalPrice = steelCost + glassCost + BASE_FEE + mechanismSurcharge + sidePanelSurchrg + handleCost + finishSurcharge; return { steelCost, glassCost, baseFee: BASE_FEE, mechanismSurcharge, sidePanelSurcharge: sidePanelSurchrg, handleCost, finishSurcharge, totalPrice, steelLengthM: Math.round(totalSteelLength * 100) / 100, glassAreaSqm: Math.round(totalGlassArea * 100) / 100, }; }