feat: Centralize pricing calculations into a new calculateTotals utility function and Totals interface.

This commit is contained in:
2026-02-04 16:59:30 +01:00
parent 4bba61078a
commit 39006c16b1
6 changed files with 95 additions and 78 deletions

View File

@@ -4,7 +4,7 @@ import * as readline from 'node:readline/promises';
import { fileURLToPath } from 'node:url';
import { createElement } from 'react';
import { renderToFile } from '@react-pdf/renderer';
import { calculatePositions } from '../src/logic/pricing/calculator.js';
import { calculatePositions, calculateTotals } from '../src/logic/pricing/calculator.js';
import { CombinedQuotePDF } from '../src/components/CombinedQuotePDF.js';
import { initialState, PRICING } from '../src/logic/pricing/constants.js';
import { getTechDetails, getPrinciples } from '../src/logic/content-provider.js';
@@ -35,9 +35,8 @@ async function main() {
console.warn('⚠️ Missing recipient name or email. Document might look incomplete.');
}
const totalPrice = calculateTotal(state);
const monthlyPrice = calculateMonthly(state);
const totalPagesCount = (state.selectedPages?.length || 0) + (state.otherPages?.length || 0) + (state.otherPagesCount || 0);
const totals = calculateTotals(state, PRICING);
const { totalPrice, monthlyPrice, totalPagesCount } = totals;
const finalOutputPath = generateDefaultPath(state);
const outputDir = path.dirname(finalOutputPath);
@@ -115,32 +114,6 @@ async function runWizard(state: any) {
return state;
}
function calculateTotal(state: any) {
// Basic duplication of logic from ContactForm for consistency
if (state.projectType !== 'website') return 0;
const totalPagesCount = (state.selectedPages?.length || 0) + (state.otherPages?.length || 0) + (state.otherPagesCount || 0);
let total = PRICING.BASE_WEBSITE;
total += totalPagesCount * PRICING.PAGE;
total += ((state.features?.length || 0) + (state.otherFeatures?.length || 0) + (state.otherFeaturesCount || 0)) * PRICING.FEATURE;
total += ((state.functions?.length || 0) + (state.otherFunctions?.length || 0) + (state.otherFunctionsCount || 0)) * PRICING.FUNCTION;
total += ((state.apiSystems?.length || 0) + (state.otherTech?.length || 0) + (state.otherTechCount || 0)) * PRICING.API_INTEGRATION;
if (state.cmsSetup) {
total += PRICING.CMS_SETUP;
total += ((state.features?.length || 0) + (state.otherFeatures?.length || 0) + (state.otherFeaturesCount || 0)) * PRICING.CMS_CONNECTION_PER_FEATURE;
}
const languagesCount = state.languagesList?.length || 1;
if (languagesCount > 1) {
total *= (1 + (languagesCount - 1) * 0.2);
}
return Math.round(total);
}
function calculateMonthly(state: any) {
if (state.projectType !== 'website') return 0;
return PRICING.HOSTING_MONTHLY + ((state.storageExpansion || 0) * PRICING.STORAGE_EXPANSION_MONTHLY);
}
function generateDefaultPath(state: any) {
const now = new Date();