Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 13s
Monorepo Pipeline / 🏗️ Build (push) Failing after 11s
Monorepo Pipeline / 🧪 Test (push) Failing after 25s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
225 lines
7.8 KiB
TypeScript
225 lines
7.8 KiB
TypeScript
import { FormState, Position, Totals } from "./types.js";
|
|
import {
|
|
FEATURE_LABELS,
|
|
FUNCTION_LABELS,
|
|
API_LABELS,
|
|
PAGE_LABELS,
|
|
} from "./constants.js";
|
|
|
|
export function calculateTotals(state: FormState, pricing: any): Totals {
|
|
if (state.projectType !== "website") {
|
|
return {
|
|
totalPrice: 0,
|
|
monthlyPrice: 0,
|
|
totalPagesCount: 0,
|
|
totalFeatures: 0,
|
|
totalFunctions: 0,
|
|
totalApis: 0,
|
|
languagesCount: 0,
|
|
};
|
|
}
|
|
|
|
const sitemapPagesCount =
|
|
state.sitemap?.reduce(
|
|
(acc: number, cat: any) => acc + (cat.pages?.length || 0),
|
|
0,
|
|
) || 0;
|
|
const totalPagesCount = Math.max(
|
|
(state.selectedPages?.length || 0) +
|
|
(state.otherPages?.length || 0) +
|
|
(state.otherPagesCount || 0),
|
|
sitemapPagesCount,
|
|
);
|
|
|
|
const totalFeatures =
|
|
(state.features?.length || 0) +
|
|
(state.otherFeatures?.length || 0) +
|
|
(state.otherFeaturesCount || 0);
|
|
const totalFunctions =
|
|
(state.functions?.length || 0) +
|
|
(state.otherFunctions?.length || 0) +
|
|
(state.otherFunctionsCount || 0);
|
|
const totalApis =
|
|
(state.apiSystems?.length || 0) +
|
|
(state.otherTech?.length || 0) +
|
|
(state.otherTechCount || 0);
|
|
|
|
let total = pricing.BASE_WEBSITE;
|
|
total += totalPagesCount * pricing.PAGE;
|
|
total += totalFeatures * pricing.FEATURE;
|
|
total += totalFunctions * pricing.FUNCTION;
|
|
total += totalApis * pricing.API_INTEGRATION;
|
|
total += (state.newDatasets || 0) * pricing.NEW_DATASET;
|
|
|
|
if (state.cmsSetup) {
|
|
total += Math.max(1, totalFeatures) * pricing.CMS_CONNECTION_PER_FEATURE;
|
|
}
|
|
|
|
const languagesCount = state.languagesList?.length || 1;
|
|
if (languagesCount > 1) {
|
|
total *= 1 + (languagesCount - 1) * 0.2;
|
|
}
|
|
|
|
const monthlyPrice =
|
|
pricing.HOSTING_MONTHLY +
|
|
(state.storageExpansion || 0) * pricing.STORAGE_EXPANSION_MONTHLY;
|
|
|
|
return {
|
|
totalPrice: Math.round(total),
|
|
monthlyPrice: Math.round(monthlyPrice),
|
|
totalPagesCount,
|
|
totalFeatures,
|
|
totalFunctions,
|
|
totalApis,
|
|
languagesCount,
|
|
};
|
|
}
|
|
|
|
export function calculatePositions(state: FormState, pricing: any): Position[] {
|
|
const positions: Position[] = [];
|
|
let pos = 1;
|
|
|
|
if (state.projectType === "website") {
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Das technische Fundament",
|
|
desc: "Projekt-Setup, Infrastruktur, Hosting-Bereitstellung, Grundstruktur & Design-Vorlage, technisches SEO-Basics, Analytics.",
|
|
qty: 1,
|
|
price: pricing.BASE_WEBSITE,
|
|
});
|
|
|
|
const sitemapPagesCount =
|
|
state.sitemap?.reduce(
|
|
(acc: number, cat: any) => acc + (cat.pages?.length || 0),
|
|
0,
|
|
) || 0;
|
|
const totalPagesCount = Math.max(
|
|
(state.selectedPages?.length || 0) +
|
|
(state.otherPages?.length || 0) +
|
|
(state.otherPagesCount || 0),
|
|
sitemapPagesCount,
|
|
);
|
|
|
|
const allPages = [
|
|
...(state.selectedPages || []).map((p: string) => PAGE_LABELS[p] || p),
|
|
...(state.otherPages || []),
|
|
...(state.sitemap?.flatMap((cat: any) =>
|
|
cat.pages?.map((p: any) => p.title),
|
|
) || []),
|
|
];
|
|
|
|
// Deduplicate labels
|
|
const uniquePages = Array.from(new Set(allPages));
|
|
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Individuelle Seiten",
|
|
desc: `Gestaltung und Umsetzung von ${totalPagesCount} individuellen Seiten-Layouts (${uniquePages.join(", ")}).`,
|
|
qty: totalPagesCount,
|
|
price: totalPagesCount * pricing.PAGE,
|
|
});
|
|
|
|
if ((state.features?.length || 0) > 0 || (state.otherFeatures?.length || 0) > 0) {
|
|
const allFeatures = [
|
|
...(state.features || []).map((f: string) => FEATURE_LABELS[f] || f),
|
|
...(state.otherFeatures || []),
|
|
];
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "System-Module (Features)",
|
|
desc: `Implementierung funktionaler Bereiche: ${allFeatures.join(", ")}. Inklusive Datenstruktur und Darstellung.`,
|
|
qty: allFeatures.length,
|
|
price: allFeatures.length * pricing.FEATURE,
|
|
});
|
|
}
|
|
|
|
if ((state.functions?.length || 0) > 0 || (state.otherFunctions?.length || 0) > 0) {
|
|
const allFunctions = [
|
|
...(state.functions || []).map((f: string) => FUNCTION_LABELS[f] || f),
|
|
...(state.otherFunctions || []),
|
|
];
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Logik-Funktionen",
|
|
desc: `Implementierung technischer Logik: ${allFunctions.join(", ")}.`,
|
|
qty: allFunctions.length,
|
|
price: allFunctions.length * pricing.FUNCTION,
|
|
});
|
|
}
|
|
|
|
if ((state.apiSystems?.length || 0) > 0 || (state.otherTech?.length || 0) > 0) {
|
|
const allApis = [
|
|
...(state.apiSystems || []).map((a: string) => API_LABELS[a] || a),
|
|
...(state.otherTech || []),
|
|
];
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Schnittstellen (API)",
|
|
desc: `Anbindung externer Systeme zur Datensynchronisation: ${allApis.join(", ")}.`,
|
|
qty: allApis.length,
|
|
price: allApis.length * pricing.API_INTEGRATION,
|
|
});
|
|
}
|
|
|
|
if (state.cmsSetup) {
|
|
const totalFeatures =
|
|
(state.features?.length || 0) +
|
|
(state.otherFeatures?.length || 0) +
|
|
(state.otherFeaturesCount || 0);
|
|
const qty = Math.max(1, totalFeatures);
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Inhalts-Verwaltung",
|
|
desc: "Anbindung der System-Module an das Redaktions-System zur eigenständigen Pflege von Inhalten.",
|
|
qty: qty,
|
|
price: qty * pricing.CMS_CONNECTION_PER_FEATURE,
|
|
});
|
|
}
|
|
|
|
if (state.newDatasets > 0) {
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Inhaltliche Initial-Pflege",
|
|
desc: `Manuelle Übernahme und Aufbereitung von ${state.newDatasets} Datensätzen (Produkte, Artikel) in das Zielsystem.`,
|
|
qty: state.newDatasets,
|
|
price: state.newDatasets * pricing.NEW_DATASET,
|
|
});
|
|
}
|
|
|
|
const languagesCount = state.languagesList?.length || 1;
|
|
if (languagesCount > 1) {
|
|
const subtotal = positions.reduce((sum, p) => sum + p.price, 0);
|
|
const factorPrice = subtotal * ((languagesCount - 1) * 0.2);
|
|
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Mehrsprachigkeit",
|
|
desc: `Erweiterung des Systems auf ${languagesCount} Sprachen (Struktur & Logik).`,
|
|
qty: languagesCount,
|
|
price: Math.round(factorPrice),
|
|
});
|
|
}
|
|
|
|
const monthlyRate =
|
|
pricing.HOSTING_MONTHLY +
|
|
state.storageExpansion * pricing.STORAGE_EXPANSION_MONTHLY;
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Sorglos Betrieb (1 Jahr)",
|
|
desc: `Inklusive 1 Jahr Sicherung des technischen Betriebs, Hosting, Instandhaltung, Sicherheits-Updates und techn. Support gemäß AGB Punkt 7a.`,
|
|
qty: 1,
|
|
price: monthlyRate * 12,
|
|
});
|
|
} else {
|
|
positions.push({
|
|
pos: pos++,
|
|
title: "Web App / Software Entwicklung",
|
|
desc: "Individuelle Software-Entwicklung nach Aufwand. Abrechnung erfolgt auf Stundenbasis.",
|
|
qty: 1,
|
|
price: 0,
|
|
});
|
|
}
|
|
|
|
return positions;
|
|
}
|