Implement debtors prison features across the application: Enhance FalukantController to include debtors prison logic in various service methods. Update FalukantService to manage debtors prison state and integrate it into user data retrieval. Modify frontend components, including DashboardWidget, StatusBar, and BankView, to display debtors prison status and warnings. Add localization for debtors prison messages in English, German, and Spanish, ensuring clarity in user notifications and actions.
This commit is contained in:
@@ -48,6 +48,7 @@ import ChildRelation from '../models/falukant/data/child_relation.js';
|
||||
import Learning from '../models/falukant/data/learning.js';
|
||||
import LearnRecipient from '../models/falukant/type/learn_recipient.js';
|
||||
import Credit from '../models/falukant/data/credit.js';
|
||||
import DebtorsPrism from '../models/falukant/data/debtors_prism.js';
|
||||
import TitleRequirement from '../models/falukant/type/title_requirement.js';
|
||||
import HealthActivity from '../models/falukant/log/health_activity.js';
|
||||
import Election from '../models/falukant/data/election.js';
|
||||
@@ -808,6 +809,7 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
if (userHouse) user.setDataValue('userHouse', userHouse);
|
||||
}
|
||||
user.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(user));
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -865,6 +867,7 @@ class FalukantService extends BaseService {
|
||||
}
|
||||
if (userHouse) u.setDataValue('userHouse', userHouse);
|
||||
if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate));
|
||||
u.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(u));
|
||||
return u;
|
||||
}
|
||||
|
||||
@@ -1026,6 +1029,8 @@ class FalukantService extends BaseService {
|
||||
falukantUser.setDataValue('unreadNotifications', 0);
|
||||
}
|
||||
|
||||
falukantUser.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(falukantUser));
|
||||
|
||||
return falukantUser;
|
||||
}
|
||||
|
||||
@@ -3278,6 +3283,7 @@ class FalukantService extends BaseService {
|
||||
householdTension: householdTension.label,
|
||||
householdTensionScore: householdTension.score,
|
||||
householdTensionReasons: householdTension.reasons,
|
||||
debtorsPrison: await this.getDebtorsPrisonStateForUser(user),
|
||||
lovers,
|
||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||
@@ -4385,6 +4391,7 @@ class FalukantService extends BaseService {
|
||||
|
||||
const plainHouse = userHouse.get({ plain: true });
|
||||
plainHouse.servantSummary = this.buildServantSummary(plainHouse, falukantUser.character);
|
||||
plainHouse.debtorsPrison = await this.getDebtorsPrisonStateForUser(falukantUser);
|
||||
return plainHouse;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -5121,6 +5128,115 @@ class FalukantService extends BaseService {
|
||||
return true;
|
||||
}
|
||||
|
||||
deriveDebtNextForcedAction(status, daysOverdue) {
|
||||
if (status === 'imprisoned') return 'asset_seizure';
|
||||
if (daysOverdue >= 3) return 'debtors_prison';
|
||||
if (daysOverdue === 2) return 'final_warning';
|
||||
if (daysOverdue === 1) return 'reminder';
|
||||
return null;
|
||||
}
|
||||
|
||||
calculateCreditworthinessFromDebtState(record) {
|
||||
if (!record) return 100;
|
||||
|
||||
const penalty = Number(record.creditworthinessPenalty || 0);
|
||||
const daysOverdue = Number(record.daysOverdue || 0);
|
||||
|
||||
if (record.status === 'imprisoned') {
|
||||
return Math.max(0, 20 - penalty);
|
||||
}
|
||||
if (record.status === 'delinquent') {
|
||||
return Math.max(15, 100 - (daysOverdue * 20) - penalty);
|
||||
}
|
||||
return Math.max(0, 80 - penalty);
|
||||
}
|
||||
|
||||
serializeDebtorsPrisonRecord(record, totalDebt = 0) {
|
||||
if (!record) {
|
||||
return {
|
||||
active: false,
|
||||
inDebtorsPrison: false,
|
||||
status: null,
|
||||
daysOverdue: 0,
|
||||
debtAtEntry: null,
|
||||
remainingDebt: Number(totalDebt || 0),
|
||||
reason: null,
|
||||
creditworthinessPenalty: 0,
|
||||
creditworthiness: 100,
|
||||
nextForcedAction: null,
|
||||
publicKnown: false,
|
||||
enteredAt: null,
|
||||
releasedAt: null,
|
||||
assetsSeized: null
|
||||
};
|
||||
}
|
||||
|
||||
const remainingDebt = Number(record.remainingDebt ?? totalDebt ?? 0);
|
||||
const debtState = {
|
||||
active: record.status !== 'released' && !record.releasedAt,
|
||||
inDebtorsPrison: record.status === 'imprisoned' && !record.releasedAt,
|
||||
status: record.status || null,
|
||||
daysOverdue: Number(record.daysOverdue || 0),
|
||||
debtAtEntry: record.debtAtEntry != null ? Number(record.debtAtEntry) : null,
|
||||
remainingDebt,
|
||||
reason: record.reason || null,
|
||||
creditworthinessPenalty: Number(record.creditworthinessPenalty || 0),
|
||||
nextForcedAction: record.nextForcedAction || this.deriveDebtNextForcedAction(record.status, Number(record.daysOverdue || 0)),
|
||||
publicKnown: !!record.publicKnown,
|
||||
enteredAt: record.enteredAt || null,
|
||||
releasedAt: record.releasedAt || null,
|
||||
assetsSeized: record.assetsSeizedJson || null
|
||||
};
|
||||
debtState.creditworthiness = this.calculateCreditworthinessFromDebtState(debtState);
|
||||
return debtState;
|
||||
}
|
||||
|
||||
async getDebtorsPrisonStateForUser(falukantUser) {
|
||||
if (!falukantUser?.id) {
|
||||
return this.serializeDebtorsPrisonRecord(null);
|
||||
}
|
||||
|
||||
const character = falukantUser.character || await FalukantCharacter.findOne({
|
||||
where: { userId: falukantUser.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
|
||||
if (!character?.id) {
|
||||
return this.serializeDebtorsPrisonRecord(null);
|
||||
}
|
||||
|
||||
const records = await DebtorsPrism.findAll({
|
||||
where: { characterId: character.id },
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const activeRecord = records.find((record) => record.status !== 'released' && !record.releasedAt)
|
||||
|| records[0]
|
||||
|| null;
|
||||
|
||||
const totalDebt = await Credit.sum('remaining_amount', {
|
||||
where: { falukant_user_id: falukantUser.id }
|
||||
}) || 0;
|
||||
|
||||
return this.serializeDebtorsPrisonRecord(activeRecord, totalDebt);
|
||||
}
|
||||
|
||||
async assertActionAllowedOutsideDebtorsPrison(hashedUserId) {
|
||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||
const debtorsPrison = await this.getDebtorsPrisonStateForUser(falukantUser);
|
||||
|
||||
if (debtorsPrison.inDebtorsPrison) {
|
||||
throw {
|
||||
status: 423,
|
||||
message: 'Aktion im Schuldturm gesperrt',
|
||||
code: 'falukant.debtorsPrison.actionBlocked',
|
||||
debtorsPrison
|
||||
};
|
||||
}
|
||||
|
||||
return { falukantUser, debtorsPrison };
|
||||
}
|
||||
|
||||
async getBankOverview(hashedUserId) {
|
||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||
if (!falukantUser) throw new Error('User not found');
|
||||
@@ -5149,9 +5265,14 @@ class FalukantService extends BaseService {
|
||||
const branchCount = await Branch.count({ where: { falukantUserId: falukantUser.id } });
|
||||
const branchValue = branchCount * 1000;
|
||||
|
||||
const debtorsPrison = await this.getDebtorsPrisonStateForUser(falukantUser);
|
||||
|
||||
// 5) Maximaler Kredit und verfügbare Linie
|
||||
const maxCredit = Math.floor(houseValue + branchValue);
|
||||
const availableCredit = maxCredit - totalDebt;
|
||||
const availableCreditRaw = maxCredit - totalDebt;
|
||||
const availableCredit = debtorsPrison.inDebtorsPrison
|
||||
? 0
|
||||
: Math.max(0, availableCreditRaw);
|
||||
|
||||
// 6) aktive Kredite laden
|
||||
const activeCredits = await Credit.findAll({
|
||||
@@ -5165,7 +5286,12 @@ class FalukantService extends BaseService {
|
||||
maxCredit,
|
||||
availableCredit,
|
||||
activeCredits,
|
||||
fee: 7
|
||||
fee: 7,
|
||||
inDebtorsPrison: debtorsPrison.inDebtorsPrison,
|
||||
daysOverdue: debtorsPrison.daysOverdue,
|
||||
nextForcedAction: debtorsPrison.nextForcedAction,
|
||||
creditworthiness: debtorsPrison.creditworthiness,
|
||||
debtorsPrison
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5182,6 +5308,9 @@ class FalukantService extends BaseService {
|
||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||
if (!falukantUser) throw new Error('User not found');
|
||||
const financialData = await this.getBankOverview(hashedUserId);
|
||||
if (financialData.inDebtorsPrison) {
|
||||
throw new Error('debtorPrisonBlocksCredit');
|
||||
}
|
||||
if (financialData.availableCredit < height) {
|
||||
throw new Error('Not enough credit');
|
||||
}
|
||||
@@ -6454,7 +6583,7 @@ ORDER BY r.id`,
|
||||
const characterName = titleLabelTr ? [titleLabelTr, firstName, lastName].filter(Boolean).join(' ') : nameWithoutTitle;
|
||||
const age = character.birthdate ? calcAge(character.birthdate) : null;
|
||||
|
||||
const [unreadNotificationsCount, childrenCount] = await Promise.all([
|
||||
const [unreadNotificationsCount, childrenCount, debtorsPrison] = await Promise.all([
|
||||
Notification.count({ where: { userId: falukantUser.id, shown: false } }),
|
||||
ChildRelation.count({
|
||||
where: {
|
||||
@@ -6463,7 +6592,8 @@ ORDER BY r.id`,
|
||||
{ motherCharacterId: character.id }
|
||||
]
|
||||
}
|
||||
})
|
||||
}),
|
||||
this.getDebtorsPrisonStateForUser(falukantUser)
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -6474,7 +6604,8 @@ ORDER BY r.id`,
|
||||
age,
|
||||
money: Number(falukantUser.money ?? 0),
|
||||
unreadNotificationsCount,
|
||||
childrenCount
|
||||
childrenCount,
|
||||
debtorsPrison
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user