feat(FalukantService, CreateBranchDialog): implement dynamic branch cost calculation
All checks were successful
Deploy to production / deploy (push) Successful in 2m8s

- Added new constants and methods in FalukantService to calculate branch action costs based on owned branch types, enhancing the cost management logic.
- Updated the CreateBranchDialog to utilize the new cost calculation, ensuring accurate display of branch creation costs based on user-owned branches.
- Improved the handling of branch type costs, allowing for a more flexible and responsive user experience when creating and upgrading branches.
This commit is contained in:
Torsten Schulz (local)
2026-04-27 14:35:45 +02:00
parent d854200708
commit 7fc9b55b59
2 changed files with 67 additions and 9 deletions

View File

@@ -106,6 +106,14 @@ async function getBranchOrFail(userId, branchId) {
return branch;
}
const BRANCH_EXPANSION_EXPONENT = 1.2;
const BRANCH_TYPE_COST_PRESSURE = {
production: { same: 1.15, other: 0.45 },
store: { same: 1.1, other: 0.5 },
fullstack: { same: 1.35, other: 0.7 },
default: { same: 1.05, other: 0.5 },
};
const POLITICAL_OFFICE_RANKS = {
assessor: 1,
councillor: 1,
@@ -572,6 +580,22 @@ class FalukantService extends BaseService {
{ tr: "drunkOfLife", method: "healthDrunkOfLife", cost: 5000000 }
];
_getBranchCostPressure(targetLabel) {
return BRANCH_TYPE_COST_PRESSURE[targetLabel] || BRANCH_TYPE_COST_PRESSURE.default;
}
_calculateBranchActionCost(baseCost, targetLabel, ownedTypeLabels = []) {
const normalizedTarget = String(targetLabel || '').trim().toLowerCase();
const { same, other } = this._getBranchCostPressure(normalizedTarget);
const weightedCount = ownedTypeLabels.reduce((sum, label) => {
const normalizedLabel = String(label || '').trim().toLowerCase();
return sum + (normalizedLabel === normalizedTarget ? same : other);
}, 0);
const exponentBase = Math.max(1, 1 + weightedCount);
const rawCost = Number(baseCost || 0) * Math.pow(exponentBase, BRANCH_EXPANSION_EXPONENT);
return Math.round(rawCost * 100) / 100;
}
static RECURSIVE_REGION_SEARCH = `
WITH RECURSIVE ancestors AS (
SELECT
@@ -1258,12 +1282,15 @@ class FalukantService extends BaseService {
if (!branchType) {
throw new Error(`Unknown branchTypeId ${branchTypeId}`);
}
const existingCount = await Branch.count({
where: { falukantUserId: user.id }
const existingBranches = await Branch.findAll({
where: { falukantUserId: user.id },
include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }],
attributes: ['id']
});
const exponentBase = Math.max(existingCount, 1);
const rawCost = branchType.baseCost * Math.pow(exponentBase, 1.2);
const cost = Math.round(rawCost * 100) / 100;
const ownedTypeLabels = existingBranches
.map((branch) => branch.branchType?.labelTr)
.filter(Boolean);
const cost = this._calculateBranchActionCost(branchType.baseCost, branchType.labelTr, ownedTypeLabels);
if (user.money < cost) {
throw new PreconditionError('insufficientFunds');
}
@@ -1283,7 +1310,18 @@ class FalukantService extends BaseService {
async getBranchTypes(hashedUserId) {
const user = await getFalukantUserOrFail(hashedUserId);
const branchTypes = await BranchType.findAll();
return branchTypes;
const ownedBranches = await Branch.findAll({
where: { falukantUserId: user.id },
include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }],
attributes: ['id']
});
const ownedTypeLabels = ownedBranches
.map((branch) => branch.branchType?.labelTr)
.filter(Boolean);
return branchTypes.map((branchType) => ({
...branchType.get({ plain: true }),
createCost: this._calculateBranchActionCost(branchType.baseCost, branchType.labelTr, ownedTypeLabels),
}));
}
@@ -1311,7 +1349,7 @@ class FalukantService extends BaseService {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await Branch.findOne({
where: { id: branchId, falukantUserId: user.id },
include: [{ model: BranchType, as: 'branchType', attributes: ['id', 'labelTr'] }],
include: [{ model: BranchType, as: 'branchType', attributes: ['id', 'labelTr', 'baseCost'] }],
});
if (!branch) {
throw new Error('Branch not found');
@@ -1333,8 +1371,25 @@ class FalukantService extends BaseService {
if (!targetType) {
throw new Error(`Target branch type '${targetLabel}' not found`);
}
// Für den Moment ohne zusätzliche Kosten kann später erweitert werden
const ownedBranches = await Branch.findAll({
where: { falukantUserId: user.id },
include: [{ model: BranchType, as: 'branchType', attributes: ['labelTr'] }],
attributes: ['id']
});
const ownedTypeLabels = ownedBranches
.filter((entry) => entry.id !== branch.id)
.map((entry) => entry.branchType?.labelTr)
.filter(Boolean);
const baseUpgradeCost = Math.max(targetType.baseCost - (branch.branchType?.baseCost || 0), targetType.baseCost * 0.6);
const upgradeCost = this._calculateBranchActionCost(baseUpgradeCost, targetType.labelTr, ownedTypeLabels);
if (user.money < upgradeCost) {
throw new PreconditionError('insufficientFunds');
}
await updateFalukantUserMoney(
user.id,
-upgradeCost,
'upgrade_branch'
);
branch.branchTypeId = targetType.id;
await branch.save();

View File

@@ -257,6 +257,9 @@
},
computeBranchCost(type) {
if (Number.isFinite(Number(type?.createCost))) {
return Math.round(Number(type.createCost) * 100) / 100;
}
const total = this.cities.reduce((sum, city) => sum + city.branches.length, 0);
const factor = Math.pow(Math.max(total, 1), 1.2);
const raw = type.baseCost * factor;