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:
@@ -29,30 +29,30 @@ class FalukantController {
|
|||||||
// Dashboard widget: originaler Endpoint (siehe Commit 62d8cd7)
|
// Dashboard widget: originaler Endpoint (siehe Commit 62d8cd7)
|
||||||
this.getDashboardWidget = this._wrapWithUser((userId) => this.service.getDashboardWidget(userId));
|
this.getDashboardWidget = this._wrapWithUser((userId) => this.service.getDashboardWidget(userId));
|
||||||
this.getBranches = this._wrapWithUser((userId) => this.service.getBranches(userId));
|
this.getBranches = this._wrapWithUser((userId) => this.service.getBranches(userId));
|
||||||
this.createBranch = this._wrapWithUser((userId, req) => this.service.createBranch(userId, req.body.cityId, req.body.branchTypeId));
|
this.createBranch = this._wrapWithUser((userId, req) => this.service.createBranch(userId, req.body.cityId, req.body.branchTypeId), { blockInDebtorsPrison: true });
|
||||||
this.getBranchTypes = this._wrapWithUser((userId) => this.service.getBranchTypes(userId));
|
this.getBranchTypes = this._wrapWithUser((userId) => this.service.getBranchTypes(userId));
|
||||||
this.getBranch = this._wrapWithUser((userId, req) => this.service.getBranch(userId, req.params.branch));
|
this.getBranch = this._wrapWithUser((userId, req) => this.service.getBranch(userId, req.params.branch));
|
||||||
this.upgradeBranch = this._wrapWithUser((userId, req) => this.service.upgradeBranch(userId, req.body.branchId));
|
this.upgradeBranch = this._wrapWithUser((userId, req) => this.service.upgradeBranch(userId, req.body.branchId), { blockInDebtorsPrison: true });
|
||||||
this.createProduction = this._wrapWithUser((userId, req) => {
|
this.createProduction = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, productId, quantity } = req.body;
|
const { branchId, productId, quantity } = req.body;
|
||||||
return this.service.createProduction(userId, branchId, productId, quantity);
|
return this.service.createProduction(userId, branchId, productId, quantity);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.getProduction = this._wrapWithUser((userId, req) => this.service.getProduction(userId, req.params.branchId));
|
this.getProduction = this._wrapWithUser((userId, req) => this.service.getProduction(userId, req.params.branchId));
|
||||||
this.getStock = this._wrapWithUser((userId, req) => this.service.getStock(userId, req.params.branchId || null));
|
this.getStock = this._wrapWithUser((userId, req) => this.service.getStock(userId, req.params.branchId || null));
|
||||||
this.createStock = this._wrapWithUser((userId, req) => {
|
this.createStock = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, stockTypeId, stockSize } = req.body;
|
const { branchId, stockTypeId, stockSize } = req.body;
|
||||||
return this.service.createStock(userId, branchId, stockTypeId, stockSize);
|
return this.service.createStock(userId, branchId, stockTypeId, stockSize);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.getProducts = this._wrapWithUser((userId) => this.service.getProducts(userId));
|
this.getProducts = this._wrapWithUser((userId) => this.service.getProducts(userId));
|
||||||
this.getInventory = this._wrapWithUser((userId, req) => this.service.getInventory(userId, req.params.branchId));
|
this.getInventory = this._wrapWithUser((userId, req) => this.service.getInventory(userId, req.params.branchId));
|
||||||
this.sellProduct = this._wrapWithUser((userId, req) => {
|
this.sellProduct = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, productId, quality, quantity } = req.body;
|
const { branchId, productId, quality, quantity } = req.body;
|
||||||
return this.service.sellProduct(userId, branchId, productId, quality, quantity);
|
return this.service.sellProduct(userId, branchId, productId, quality, quantity);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.sellAllProducts = this._wrapWithUser((userId, req) => {
|
this.sellAllProducts = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId } = req.body;
|
const { branchId } = req.body;
|
||||||
return this.service.sellAllProducts(userId, branchId);
|
return this.service.sellAllProducts(userId, branchId);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.moneyHistory = this._wrapWithUser((userId, req) => {
|
this.moneyHistory = this._wrapWithUser((userId, req) => {
|
||||||
let { page, filter } = req.body;
|
let { page, filter } = req.body;
|
||||||
if (!page) page = 1;
|
if (!page) page = 1;
|
||||||
@@ -66,11 +66,11 @@ class FalukantController {
|
|||||||
this.buyStorage = this._wrapWithUser((userId, req) => {
|
this.buyStorage = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, amount, stockTypeId } = req.body;
|
const { branchId, amount, stockTypeId } = req.body;
|
||||||
return this.service.buyStorage(userId, branchId, amount, stockTypeId);
|
return this.service.buyStorage(userId, branchId, amount, stockTypeId);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.sellStorage = this._wrapWithUser((userId, req) => {
|
this.sellStorage = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, amount, stockTypeId } = req.body;
|
const { branchId, amount, stockTypeId } = req.body;
|
||||||
return this.service.sellStorage(userId, branchId, amount, stockTypeId);
|
return this.service.sellStorage(userId, branchId, amount, stockTypeId);
|
||||||
}, { successStatus: 202 });
|
}, { successStatus: 202, blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getStockTypes = this._wrapSimple(() => this.service.getStockTypes());
|
this.getStockTypes = this._wrapSimple(() => this.service.getStockTypes());
|
||||||
this.getStockOverview = this._wrapSimple(() => this.service.getStockOverview());
|
this.getStockOverview = this._wrapSimple(() => this.service.getStockOverview());
|
||||||
@@ -80,18 +80,18 @@ class FalukantController {
|
|||||||
console.log('🔍 getDirectorProposals called with userId:', userId, 'branchId:', req.body.branchId);
|
console.log('🔍 getDirectorProposals called with userId:', userId, 'branchId:', req.body.branchId);
|
||||||
return this.service.getDirectorProposals(userId, req.body.branchId);
|
return this.service.getDirectorProposals(userId, req.body.branchId);
|
||||||
});
|
});
|
||||||
this.convertProposalToDirector = this._wrapWithUser((userId, req) => this.service.convertProposalToDirector(userId, req.body.proposalId));
|
this.convertProposalToDirector = this._wrapWithUser((userId, req) => this.service.convertProposalToDirector(userId, req.body.proposalId), { blockInDebtorsPrison: true });
|
||||||
this.getDirectorForBranch = this._wrapWithUser((userId, req) => this.service.getDirectorForBranch(userId, req.params.branchId));
|
this.getDirectorForBranch = this._wrapWithUser((userId, req) => this.service.getDirectorForBranch(userId, req.params.branchId));
|
||||||
this.getAllDirectors = this._wrapWithUser((userId) => this.service.getAllDirectors(userId));
|
this.getAllDirectors = this._wrapWithUser((userId) => this.service.getAllDirectors(userId));
|
||||||
this.updateDirector = this._wrapWithUser((userId, req) => {
|
this.updateDirector = this._wrapWithUser((userId, req) => {
|
||||||
const { directorId, income } = req.body;
|
const { directorId, income } = req.body;
|
||||||
return this.service.updateDirector(userId, directorId, income);
|
return this.service.updateDirector(userId, directorId, income);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.setSetting = this._wrapWithUser((userId, req) => {
|
this.setSetting = this._wrapWithUser((userId, req) => {
|
||||||
const { branchId, directorId, settingKey, value } = req.body;
|
const { branchId, directorId, settingKey, value } = req.body;
|
||||||
return this.service.setSetting(userId, branchId, directorId, settingKey, value);
|
return this.service.setSetting(userId, branchId, directorId, settingKey, value);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getFamily = this._wrapWithUser(async (userId) => {
|
this.getFamily = this._wrapWithUser(async (userId) => {
|
||||||
const result = await this.service.getFamily(userId);
|
const result = await this.service.getFamily(userId);
|
||||||
@@ -99,9 +99,9 @@ class FalukantController {
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
|
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
|
||||||
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId));
|
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId), { blockInDebtorsPrison: true });
|
||||||
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
|
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId), { blockInDebtorsPrison: true });
|
||||||
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
|
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId), { blockInDebtorsPrison: true });
|
||||||
this.cancelWooing = this._wrapWithUser(async (userId) => {
|
this.cancelWooing = this._wrapWithUser(async (userId) => {
|
||||||
try {
|
try {
|
||||||
return await this.service.cancelWooing(userId);
|
return await this.service.cancelWooing(userId);
|
||||||
@@ -111,25 +111,25 @@ class FalukantController {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}, { successStatus: 202 });
|
}, { successStatus: 202, blockInDebtorsPrison: true });
|
||||||
this.getGifts = this._wrapWithUser((userId) => {
|
this.getGifts = this._wrapWithUser((userId) => {
|
||||||
console.log('🔍 getGifts called with userId:', userId);
|
console.log('🔍 getGifts called with userId:', userId);
|
||||||
return this.service.getGifts(userId);
|
return this.service.getGifts(userId);
|
||||||
});
|
});
|
||||||
this.setLoverMaintenance = this._wrapWithUser((userId, req) =>
|
this.setLoverMaintenance = this._wrapWithUser((userId, req) =>
|
||||||
this.service.setLoverMaintenance(userId, req.params.relationshipId, req.body?.maintenanceLevel));
|
this.service.setLoverMaintenance(userId, req.params.relationshipId, req.body?.maintenanceLevel), { blockInDebtorsPrison: true });
|
||||||
this.createLoverRelationship = this._wrapWithUser((userId, req) =>
|
this.createLoverRelationship = this._wrapWithUser((userId, req) =>
|
||||||
this.service.createLoverRelationship(userId, req.body?.targetCharacterId, req.body?.loverRole), { successStatus: 201 });
|
this.service.createLoverRelationship(userId, req.body?.targetCharacterId, req.body?.loverRole), { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.spendTimeWithSpouse = this._wrapWithUser((userId) =>
|
this.spendTimeWithSpouse = this._wrapWithUser((userId) =>
|
||||||
this.service.spendTimeWithSpouse(userId));
|
this.service.spendTimeWithSpouse(userId), { blockInDebtorsPrison: true });
|
||||||
this.giftToSpouse = this._wrapWithUser((userId, req) =>
|
this.giftToSpouse = this._wrapWithUser((userId, req) =>
|
||||||
this.service.giftToSpouse(userId, req.body?.giftLevel));
|
this.service.giftToSpouse(userId, req.body?.giftLevel), { blockInDebtorsPrison: true });
|
||||||
this.reconcileMarriage = this._wrapWithUser((userId) =>
|
this.reconcileMarriage = this._wrapWithUser((userId) =>
|
||||||
this.service.reconcileMarriage(userId));
|
this.service.reconcileMarriage(userId), { blockInDebtorsPrison: true });
|
||||||
this.acknowledgeLover = this._wrapWithUser((userId, req) =>
|
this.acknowledgeLover = this._wrapWithUser((userId, req) =>
|
||||||
this.service.acknowledgeLover(userId, req.params.relationshipId));
|
this.service.acknowledgeLover(userId, req.params.relationshipId), { blockInDebtorsPrison: true });
|
||||||
this.endLoverRelationship = this._wrapWithUser((userId, req) =>
|
this.endLoverRelationship = this._wrapWithUser((userId, req) =>
|
||||||
this.service.endLoverRelationship(userId, req.params.relationshipId));
|
this.service.endLoverRelationship(userId, req.params.relationshipId), { blockInDebtorsPrison: true });
|
||||||
this.getChildren = this._wrapWithUser((userId) => this.service.getChildren(userId));
|
this.getChildren = this._wrapWithUser((userId) => this.service.getChildren(userId));
|
||||||
this.sendGift = this._wrapWithUser(async (userId, req) => {
|
this.sendGift = this._wrapWithUser(async (userId, req) => {
|
||||||
try {
|
try {
|
||||||
@@ -140,59 +140,59 @@ class FalukantController {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getTitlesOfNobility = this._wrapWithUser((userId) => this.service.getTitlesOfNobility(userId));
|
this.getTitlesOfNobility = this._wrapWithUser((userId) => this.service.getTitlesOfNobility(userId));
|
||||||
this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId));
|
this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId));
|
||||||
this.executeReputationAction = this._wrapWithUser((userId, req) =>
|
this.executeReputationAction = this._wrapWithUser((userId, req) =>
|
||||||
this.service.executeReputationAction(userId, req.body?.actionTypeId), { successStatus: 201 });
|
this.service.executeReputationAction(userId, req.body?.actionTypeId), { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId));
|
this.getHouseTypes = this._wrapWithUser((userId) => this.service.getHouseTypes(userId));
|
||||||
this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId));
|
this.getMoodAffect = this._wrapWithUser((userId) => this.service.getMoodAffect(userId));
|
||||||
this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId));
|
this.getCharacterAffect = this._wrapWithUser((userId) => this.service.getCharacterAffect(userId));
|
||||||
this.getUserHouse = this._wrapWithUser((userId) => this.service.getUserHouse(userId));
|
this.getUserHouse = this._wrapWithUser((userId) => this.service.getUserHouse(userId));
|
||||||
this.getBuyableHouses = this._wrapWithUser((userId) => this.service.getBuyableHouses(userId));
|
this.getBuyableHouses = this._wrapWithUser((userId) => this.service.getBuyableHouses(userId));
|
||||||
this.buyUserHouse = this._wrapWithUser((userId, req) => this.service.buyUserHouse(userId, req.body.houseId), { successStatus: 201 });
|
this.buyUserHouse = this._wrapWithUser((userId, req) => this.service.buyUserHouse(userId, req.body.houseId), { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.hireServants = this._wrapWithUser((userId, req) => this.service.hireServants(userId, req.body?.amount), { successStatus: 201 });
|
this.hireServants = this._wrapWithUser((userId, req) => this.service.hireServants(userId, req.body?.amount), { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.dismissServants = this._wrapWithUser((userId, req) => this.service.dismissServants(userId, req.body?.amount));
|
this.dismissServants = this._wrapWithUser((userId, req) => this.service.dismissServants(userId, req.body?.amount), { blockInDebtorsPrison: true });
|
||||||
this.setServantPayLevel = this._wrapWithUser((userId, req) => this.service.setServantPayLevel(userId, req.body?.payLevel));
|
this.setServantPayLevel = this._wrapWithUser((userId, req) => this.service.setServantPayLevel(userId, req.body?.payLevel), { blockInDebtorsPrison: true });
|
||||||
this.tidyHousehold = this._wrapWithUser((userId) => this.service.tidyHousehold(userId));
|
this.tidyHousehold = this._wrapWithUser((userId) => this.service.tidyHousehold(userId), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getPartyTypes = this._wrapWithUser((userId) => this.service.getPartyTypes(userId));
|
this.getPartyTypes = this._wrapWithUser((userId) => this.service.getPartyTypes(userId));
|
||||||
this.createParty = this._wrapWithUser((userId, req) => {
|
this.createParty = this._wrapWithUser((userId, req) => {
|
||||||
const { partyTypeId, musicId, banquetteId, nobilityIds, servantRatio } = req.body;
|
const { partyTypeId, musicId, banquetteId, nobilityIds, servantRatio } = req.body;
|
||||||
return this.service.createParty(userId, partyTypeId, musicId, banquetteId, nobilityIds, servantRatio);
|
return this.service.createParty(userId, partyTypeId, musicId, banquetteId, nobilityIds, servantRatio);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
this.getParties = this._wrapWithUser((userId) => this.service.getParties(userId));
|
this.getParties = this._wrapWithUser((userId) => this.service.getParties(userId));
|
||||||
|
|
||||||
this.getNotBaptisedChildren = this._wrapWithUser((userId) => this.service.getNotBaptisedChildren(userId));
|
this.getNotBaptisedChildren = this._wrapWithUser((userId) => this.service.getNotBaptisedChildren(userId));
|
||||||
this.baptise = this._wrapWithUser((userId, req) => {
|
this.baptise = this._wrapWithUser((userId, req) => {
|
||||||
const { characterId: childId, firstName } = req.body;
|
const { characterId: childId, firstName } = req.body;
|
||||||
return this.service.baptise(userId, childId, firstName);
|
return this.service.baptise(userId, childId, firstName);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
this.getChurchOverview = this._wrapWithUser((userId) => this.service.getChurchOverview(userId));
|
this.getChurchOverview = this._wrapWithUser((userId) => this.service.getChurchOverview(userId));
|
||||||
this.getAvailableChurchPositions = this._wrapWithUser((userId) => this.service.getAvailableChurchPositions(userId));
|
this.getAvailableChurchPositions = this._wrapWithUser((userId) => this.service.getAvailableChurchPositions(userId));
|
||||||
this.getSupervisedApplications = this._wrapWithUser((userId) => this.service.getSupervisedApplications(userId));
|
this.getSupervisedApplications = this._wrapWithUser((userId) => this.service.getSupervisedApplications(userId));
|
||||||
this.applyForChurchPosition = this._wrapWithUser((userId, req) => {
|
this.applyForChurchPosition = this._wrapWithUser((userId, req) => {
|
||||||
const { officeTypeId, regionId } = req.body;
|
const { officeTypeId, regionId } = req.body;
|
||||||
return this.service.applyForChurchPosition(userId, officeTypeId, regionId);
|
return this.service.applyForChurchPosition(userId, officeTypeId, regionId);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
|
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
|
||||||
const { applicationId, decision } = req.body;
|
const { applicationId, decision } = req.body;
|
||||||
return this.service.decideOnChurchApplication(userId, applicationId, decision);
|
return this.service.decideOnChurchApplication(userId, applicationId, decision);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId));
|
this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId));
|
||||||
this.sendToSchool = this._wrapWithUser((userId, req) => {
|
this.sendToSchool = this._wrapWithUser((userId, req) => {
|
||||||
const { item, student, studentId } = req.body;
|
const { item, student, studentId } = req.body;
|
||||||
return this.service.sendToSchool(userId, item, student, studentId);
|
return this.service.sendToSchool(userId, item, student, studentId);
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getBankOverview = this._wrapWithUser((userId) => this.service.getBankOverview(userId));
|
this.getBankOverview = this._wrapWithUser((userId) => this.service.getBankOverview(userId));
|
||||||
this.getBankCredits = this._wrapWithUser((userId) => this.service.getBankCredits(userId));
|
this.getBankCredits = this._wrapWithUser((userId) => this.service.getBankCredits(userId));
|
||||||
this.takeBankCredits = this._wrapWithUser((userId, req) => this.service.takeBankCredits(userId, req.body.height));
|
this.takeBankCredits = this._wrapWithUser((userId, req) => this.service.takeBankCredits(userId, req.body.height), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getNobility = this._wrapWithUser((userId) => this.service.getNobility(userId));
|
this.getNobility = this._wrapWithUser((userId) => this.service.getNobility(userId));
|
||||||
this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId));
|
this.advanceNobility = this._wrapWithUser((userId) => this.service.advanceNobility(userId), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId));
|
this.getHealth = this._wrapWithUser((userId) => this.service.getHealth(userId));
|
||||||
this.healthActivity = this._wrapWithUser(async (userId, req) => {
|
this.healthActivity = this._wrapWithUser(async (userId, req) => {
|
||||||
@@ -204,13 +204,13 @@ class FalukantController {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
}, { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
|
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
|
||||||
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
|
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
|
||||||
this.getElections = this._wrapWithUser((userId) => this.service.getElections(userId));
|
this.getElections = this._wrapWithUser((userId) => this.service.getElections(userId));
|
||||||
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes));
|
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes), { blockInDebtorsPrison: true });
|
||||||
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
|
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
|
this.getRegions = this._wrapWithUser((userId) => this.service.getRegions(userId));
|
||||||
this.getBranchTaxes = this._wrapWithUser((userId, req) => this.service.getBranchTaxes(userId, req.params.branchId));
|
this.getBranchTaxes = this._wrapWithUser((userId, req) => this.service.getBranchTaxes(userId, req.params.branchId));
|
||||||
@@ -248,8 +248,8 @@ class FalukantController {
|
|||||||
})).filter(i => !Number.isNaN(i.productId) && !Number.isNaN(i.currentPrice));
|
})).filter(i => !Number.isNaN(i.productId) && !Number.isNaN(i.currentPrice));
|
||||||
return this.service.getProductPricesInCitiesBatch(userId, valid, Number.isNaN(currentRegionId) ? null : currentRegionId);
|
return this.service.getProductPricesInCitiesBatch(userId, valid, Number.isNaN(currentRegionId) ? null : currentRegionId);
|
||||||
});
|
});
|
||||||
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element));
|
this.renovate = this._wrapWithUser((userId, req) => this.service.renovate(userId, req.body.element), { blockInDebtorsPrison: true });
|
||||||
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId));
|
this.renovateAll = this._wrapWithUser((userId) => this.service.renovateAll(userId), { blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getUndergroundTypes = this._wrapWithUser((userId) => this.service.getUndergroundTypes(userId));
|
this.getUndergroundTypes = this._wrapWithUser((userId) => this.service.getUndergroundTypes(userId));
|
||||||
this.getUndergroundActivities = this._wrapWithUser((userId) => this.service.getUndergroundActivities(userId));
|
this.getUndergroundActivities = this._wrapWithUser((userId) => this.service.getUndergroundActivities(userId));
|
||||||
@@ -277,7 +277,7 @@ class FalukantController {
|
|||||||
throw { status: 400, message: 'goal is required for corrupt_politician' };
|
throw { status: 400, message: 'goal is required for corrupt_politician' };
|
||||||
}
|
}
|
||||||
return this.service.createUndergroundActivity(userId, payload);
|
return this.service.createUndergroundActivity(userId, payload);
|
||||||
}, { successStatus: 201 });
|
}, { successStatus: 201, blockInDebtorsPrison: true });
|
||||||
|
|
||||||
this.getUndergroundAttacks = this._wrapWithUser((userId, req) => {
|
this.getUndergroundAttacks = this._wrapWithUser((userId, req) => {
|
||||||
const direction = (req.query.direction || '').toLowerCase();
|
const direction = (req.query.direction || '').toLowerCase();
|
||||||
@@ -291,14 +291,14 @@ class FalukantController {
|
|||||||
this.getVehicleTypes = this._wrapWithUser((userId) => this.service.getVehicleTypes(userId));
|
this.getVehicleTypes = this._wrapWithUser((userId) => this.service.getVehicleTypes(userId));
|
||||||
this.buyVehicles = this._wrapWithUser(
|
this.buyVehicles = this._wrapWithUser(
|
||||||
(userId, req) => this.service.buyVehicles(userId, req.body),
|
(userId, req) => this.service.buyVehicles(userId, req.body),
|
||||||
{ successStatus: 201 }
|
{ successStatus: 201, blockInDebtorsPrison: true }
|
||||||
);
|
);
|
||||||
this.getVehicles = this._wrapWithUser(
|
this.getVehicles = this._wrapWithUser(
|
||||||
(userId, req) => this.service.getVehicles(userId, req.query.regionId)
|
(userId, req) => this.service.getVehicles(userId, req.query.regionId)
|
||||||
);
|
);
|
||||||
this.createTransport = this._wrapWithUser(
|
this.createTransport = this._wrapWithUser(
|
||||||
(userId, req) => this.service.createTransport(userId, req.body),
|
(userId, req) => this.service.createTransport(userId, req.body),
|
||||||
{ successStatus: 201 }
|
{ successStatus: 201, blockInDebtorsPrison: true }
|
||||||
);
|
);
|
||||||
this.getTransportRoute = this._wrapWithUser(
|
this.getTransportRoute = this._wrapWithUser(
|
||||||
(userId, req) => this.service.getTransportRoute(userId, req.query)
|
(userId, req) => this.service.getTransportRoute(userId, req.query)
|
||||||
@@ -308,23 +308,26 @@ class FalukantController {
|
|||||||
);
|
);
|
||||||
this.repairVehicle = this._wrapWithUser(
|
this.repairVehicle = this._wrapWithUser(
|
||||||
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
|
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
|
||||||
{ successStatus: 200 }
|
{ successStatus: 200, blockInDebtorsPrison: true }
|
||||||
);
|
);
|
||||||
this.repairAllVehicles = this._wrapWithUser(
|
this.repairAllVehicles = this._wrapWithUser(
|
||||||
(userId, req) => this.service.repairAllVehicles(userId, req.body.vehicleIds),
|
(userId, req) => this.service.repairAllVehicles(userId, req.body.vehicleIds),
|
||||||
{ successStatus: 200 }
|
{ successStatus: 200, blockInDebtorsPrison: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_wrapWithUser(fn, { successStatus = 200, postProcess } = {}) {
|
_wrapWithUser(fn, { successStatus = 200, postProcess, blockInDebtorsPrison = false } = {}) {
|
||||||
return async (req, res) => {
|
return async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const hashedUserId = extractHashedUserId(req);
|
const hashedUserId = extractHashedUserId(req);
|
||||||
if (!hashedUserId) {
|
if (!hashedUserId) {
|
||||||
return res.status(400).json({ error: 'Missing user identifier' });
|
return res.status(400).json({ error: 'Missing user identifier' });
|
||||||
}
|
}
|
||||||
|
if (blockInDebtorsPrison) {
|
||||||
|
await this.service.assertActionAllowedOutsideDebtorsPrison(hashedUserId);
|
||||||
|
}
|
||||||
const result = await fn(hashedUserId, req, res);
|
const result = await fn(hashedUserId, req, res);
|
||||||
const toSend = postProcess ? postProcess(result) : result;
|
const toSend = postProcess ? postProcess(result) : result;
|
||||||
res.status(successStatus).json(toSend);
|
res.status(successStatus).json(toSend);
|
||||||
|
|||||||
83
backend/migrations/20260323010000-expand-debtors-prism.cjs
Normal file
83
backend/migrations/20260323010000-expand-debtors-prism.cjs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {import('sequelize-cli').Migration} */
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
const table = { schema: 'falukant_data', tableName: 'debtors_prism' };
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'status', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'delinquent'
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'entered_at', {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'released_at', {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'debt_at_entry', {
|
||||||
|
type: Sequelize.DECIMAL(14, 2),
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'remaining_debt', {
|
||||||
|
type: Sequelize.DECIMAL(14, 2),
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'days_overdue', {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'reason', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'creditworthiness_penalty', {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'next_forced_action', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'assets_seized_json', {
|
||||||
|
type: Sequelize.JSONB,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn(table, 'public_known', {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface) {
|
||||||
|
const table = { schema: 'falukant_data', tableName: 'debtors_prism' };
|
||||||
|
|
||||||
|
await queryInterface.removeColumn(table, 'public_known');
|
||||||
|
await queryInterface.removeColumn(table, 'assets_seized_json');
|
||||||
|
await queryInterface.removeColumn(table, 'next_forced_action');
|
||||||
|
await queryInterface.removeColumn(table, 'creditworthiness_penalty');
|
||||||
|
await queryInterface.removeColumn(table, 'reason');
|
||||||
|
await queryInterface.removeColumn(table, 'days_overdue');
|
||||||
|
await queryInterface.removeColumn(table, 'remaining_debt');
|
||||||
|
await queryInterface.removeColumn(table, 'debt_at_entry');
|
||||||
|
await queryInterface.removeColumn(table, 'released_at');
|
||||||
|
await queryInterface.removeColumn(table, 'entered_at');
|
||||||
|
await queryInterface.removeColumn(table, 'status');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -7,7 +7,57 @@ DebtorsPrism.init({
|
|||||||
// Verknüpfung auf FalukantCharacter
|
// Verknüpfung auf FalukantCharacter
|
||||||
characterId: {
|
characterId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false}}, {
|
allowNull: false
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'delinquent'
|
||||||
|
},
|
||||||
|
enteredAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
releasedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
debtAtEntry: {
|
||||||
|
type: DataTypes.DECIMAL(14, 2),
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
remainingDebt: {
|
||||||
|
type: DataTypes.DECIMAL(14, 2),
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
daysOverdue: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
},
|
||||||
|
reason: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
creditworthinessPenalty: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
|
},
|
||||||
|
nextForcedAction: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
assetsSeizedJson: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
publicKnown: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
sequelize,
|
sequelize,
|
||||||
modelName: 'DebtorsPrism',
|
modelName: 'DebtorsPrism',
|
||||||
tableName: 'debtors_prism',
|
tableName: 'debtors_prism',
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import ChildRelation from '../models/falukant/data/child_relation.js';
|
|||||||
import Learning from '../models/falukant/data/learning.js';
|
import Learning from '../models/falukant/data/learning.js';
|
||||||
import LearnRecipient from '../models/falukant/type/learn_recipient.js';
|
import LearnRecipient from '../models/falukant/type/learn_recipient.js';
|
||||||
import Credit from '../models/falukant/data/credit.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 TitleRequirement from '../models/falukant/type/title_requirement.js';
|
||||||
import HealthActivity from '../models/falukant/log/health_activity.js';
|
import HealthActivity from '../models/falukant/log/health_activity.js';
|
||||||
import Election from '../models/falukant/data/election.js';
|
import Election from '../models/falukant/data/election.js';
|
||||||
@@ -808,6 +809,7 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
if (userHouse) user.setDataValue('userHouse', userHouse);
|
if (userHouse) user.setDataValue('userHouse', userHouse);
|
||||||
}
|
}
|
||||||
|
user.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(user));
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,6 +867,7 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
if (userHouse) u.setDataValue('userHouse', userHouse);
|
if (userHouse) u.setDataValue('userHouse', userHouse);
|
||||||
if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate));
|
if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate));
|
||||||
|
u.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(u));
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,6 +1029,8 @@ class FalukantService extends BaseService {
|
|||||||
falukantUser.setDataValue('unreadNotifications', 0);
|
falukantUser.setDataValue('unreadNotifications', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
falukantUser.setDataValue('debtorsPrison', await this.getDebtorsPrisonStateForUser(falukantUser));
|
||||||
|
|
||||||
return falukantUser;
|
return falukantUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3278,6 +3283,7 @@ class FalukantService extends BaseService {
|
|||||||
householdTension: householdTension.label,
|
householdTension: householdTension.label,
|
||||||
householdTensionScore: householdTension.score,
|
householdTensionScore: householdTension.score,
|
||||||
householdTensionReasons: householdTension.reasons,
|
householdTensionReasons: householdTension.reasons,
|
||||||
|
debtorsPrison: await this.getDebtorsPrisonStateForUser(user),
|
||||||
lovers,
|
lovers,
|
||||||
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
deathPartners: relationships.filter(r => r.relationshipType === 'widowed'),
|
||||||
children: children.map(({ _createdAt, ...rest }) => rest),
|
children: children.map(({ _createdAt, ...rest }) => rest),
|
||||||
@@ -4385,6 +4391,7 @@ class FalukantService extends BaseService {
|
|||||||
|
|
||||||
const plainHouse = userHouse.get({ plain: true });
|
const plainHouse = userHouse.get({ plain: true });
|
||||||
plainHouse.servantSummary = this.buildServantSummary(plainHouse, falukantUser.character);
|
plainHouse.servantSummary = this.buildServantSummary(plainHouse, falukantUser.character);
|
||||||
|
plainHouse.debtorsPrison = await this.getDebtorsPrisonStateForUser(falukantUser);
|
||||||
return plainHouse;
|
return plainHouse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -5121,6 +5128,115 @@ class FalukantService extends BaseService {
|
|||||||
return true;
|
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) {
|
async getBankOverview(hashedUserId) {
|
||||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||||
if (!falukantUser) throw new Error('User not found');
|
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 branchCount = await Branch.count({ where: { falukantUserId: falukantUser.id } });
|
||||||
const branchValue = branchCount * 1000;
|
const branchValue = branchCount * 1000;
|
||||||
|
|
||||||
|
const debtorsPrison = await this.getDebtorsPrisonStateForUser(falukantUser);
|
||||||
|
|
||||||
// 5) Maximaler Kredit und verfügbare Linie
|
// 5) Maximaler Kredit und verfügbare Linie
|
||||||
const maxCredit = Math.floor(houseValue + branchValue);
|
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
|
// 6) aktive Kredite laden
|
||||||
const activeCredits = await Credit.findAll({
|
const activeCredits = await Credit.findAll({
|
||||||
@@ -5165,7 +5286,12 @@ class FalukantService extends BaseService {
|
|||||||
maxCredit,
|
maxCredit,
|
||||||
availableCredit,
|
availableCredit,
|
||||||
activeCredits,
|
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);
|
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||||
if (!falukantUser) throw new Error('User not found');
|
if (!falukantUser) throw new Error('User not found');
|
||||||
const financialData = await this.getBankOverview(hashedUserId);
|
const financialData = await this.getBankOverview(hashedUserId);
|
||||||
|
if (financialData.inDebtorsPrison) {
|
||||||
|
throw new Error('debtorPrisonBlocksCredit');
|
||||||
|
}
|
||||||
if (financialData.availableCredit < height) {
|
if (financialData.availableCredit < height) {
|
||||||
throw new Error('Not enough credit');
|
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 characterName = titleLabelTr ? [titleLabelTr, firstName, lastName].filter(Boolean).join(' ') : nameWithoutTitle;
|
||||||
const age = character.birthdate ? calcAge(character.birthdate) : null;
|
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 } }),
|
Notification.count({ where: { userId: falukantUser.id, shown: false } }),
|
||||||
ChildRelation.count({
|
ChildRelation.count({
|
||||||
where: {
|
where: {
|
||||||
@@ -6463,7 +6592,8 @@ ORDER BY r.id`,
|
|||||||
{ motherCharacterId: character.id }
|
{ motherCharacterId: character.id }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
this.getDebtorsPrisonStateForUser(falukantUser)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -6474,7 +6604,8 @@ ORDER BY r.id`,
|
|||||||
age,
|
age,
|
||||||
money: Number(falukantUser.money ?? 0),
|
money: Number(falukantUser.money ?? 0),
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
childrenCount
|
childrenCount,
|
||||||
|
debtorsPrison
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
backend/sql/expand_debtors_prism.sql
Normal file
32
backend/sql/expand_debtors_prism.sql
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS status varchar(255) NOT NULL DEFAULT 'delinquent';
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS entered_at timestamp with time zone NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS released_at timestamp with time zone NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS debt_at_entry numeric(14,2) NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS remaining_debt numeric(14,2) NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS days_overdue integer NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS reason varchar(255) NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS creditworthiness_penalty integer NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS next_forced_action varchar(255) NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS assets_seized_json jsonb NULL;
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.debtors_prism
|
||||||
|
ADD COLUMN IF NOT EXISTS public_known boolean NOT NULL DEFAULT false;
|
||||||
447
docs/FALUKANT_DEBTORS_PRISON_DAEMON_SPEC.md
Normal file
447
docs/FALUKANT_DEBTORS_PRISON_DAEMON_SPEC.md
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
# Falukant: Schuldturm und Pfändung - Daemon-Spezifikation
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt die Umsetzung des **Schuldturm-Systems** im externen Daemon.
|
||||||
|
|
||||||
|
Wichtig:
|
||||||
|
|
||||||
|
- Die projektseitigen DB-Felder, API-Erweiterungen, UI-Warnungen und Aktionssperren sind bereits umgesetzt.
|
||||||
|
- Der Daemon ist die führende Quelle für:
|
||||||
|
- Verzugstage
|
||||||
|
- Eintritt in den Schuldturm
|
||||||
|
- Pfändung und Verwertung
|
||||||
|
- soziale Folgen
|
||||||
|
- Freilassung
|
||||||
|
|
||||||
|
## 1. Bereits vorhandene Datenbasis
|
||||||
|
|
||||||
|
Bereits im Projekt vorhanden:
|
||||||
|
|
||||||
|
- `falukant_data.credit`
|
||||||
|
- `falukant_data.debtors_prism`
|
||||||
|
- `falukant_data.user_house`
|
||||||
|
- inkl. `household_tension_score`
|
||||||
|
- inkl. `household_tension_reasons_json`
|
||||||
|
- Familien-/Liebschaftsdaten in:
|
||||||
|
- `falukant_data.relationship`
|
||||||
|
- `falukant_data.relationship_state`
|
||||||
|
- `falukant_data.child_relation`
|
||||||
|
|
||||||
|
Bereits erweitert:
|
||||||
|
|
||||||
|
- `debtors_prism.status`
|
||||||
|
- `debtors_prism.entered_at`
|
||||||
|
- `debtors_prism.released_at`
|
||||||
|
- `debtors_prism.debt_at_entry`
|
||||||
|
- `debtors_prism.remaining_debt`
|
||||||
|
- `debtors_prism.days_overdue`
|
||||||
|
- `debtors_prism.reason`
|
||||||
|
- `debtors_prism.creditworthiness_penalty`
|
||||||
|
- `debtors_prism.next_forced_action`
|
||||||
|
- `debtors_prism.assets_seized_json`
|
||||||
|
- `debtors_prism.public_known`
|
||||||
|
|
||||||
|
Es sind für den Daemon derzeit keine weiteren DB-Änderungen nötig.
|
||||||
|
|
||||||
|
## 2. Grundregel
|
||||||
|
|
||||||
|
Ein Charakter kommt in den Schuldturm, wenn:
|
||||||
|
|
||||||
|
- mindestens ein aktiver Kredit offen ist
|
||||||
|
- fällige Kreditbedienung ausbleibt
|
||||||
|
- und `days_overdue >= 3`
|
||||||
|
|
||||||
|
Der Daemon prüft dies im Daily-Tick.
|
||||||
|
|
||||||
|
## 3. Zustände
|
||||||
|
|
||||||
|
`debtors_prism.status` verwendet mindestens:
|
||||||
|
|
||||||
|
- `delinquent`
|
||||||
|
- `imprisoned`
|
||||||
|
- `released`
|
||||||
|
|
||||||
|
Bedeutung:
|
||||||
|
|
||||||
|
- `delinquent`: Kreditverzug, aber noch nicht im Schuldturm
|
||||||
|
- `imprisoned`: im Schuldturm, Verwertung läuft
|
||||||
|
- `released`: historischer abgeschlossener Fall
|
||||||
|
|
||||||
|
## 4. Daily-Tick
|
||||||
|
|
||||||
|
Der Daily-Tick prüft pro Falukant-Nutzer:
|
||||||
|
|
||||||
|
1. aktive Kredite
|
||||||
|
2. verbleibende Schuld
|
||||||
|
3. geleistete Bedienung seit letztem Tick
|
||||||
|
4. neue Verzugstage
|
||||||
|
5. Schuldturm-Eintritt
|
||||||
|
6. laufende soziale Folgen
|
||||||
|
7. Verwertungsschritt
|
||||||
|
|
||||||
|
### 4.1 Verzugstage
|
||||||
|
|
||||||
|
Regel:
|
||||||
|
|
||||||
|
- wenn offene Schuld vorhanden und fällige Bedienung ausbleibt:
|
||||||
|
- `days_overdue += 1`
|
||||||
|
- wenn Kreditpflicht erfüllt wurde:
|
||||||
|
- `days_overdue = 0`
|
||||||
|
- falls nicht im Schuldturm
|
||||||
|
|
||||||
|
Wenn noch kein aktiver `debtors_prism`-Eintrag existiert:
|
||||||
|
|
||||||
|
- bei erstem Verzug `debtors_prism` anlegen mit
|
||||||
|
- `status = 'delinquent'`
|
||||||
|
- `days_overdue = 1`
|
||||||
|
- `remaining_debt = aktuelle offene Schuld`
|
||||||
|
- `next_forced_action = 'reminder'`
|
||||||
|
|
||||||
|
### 4.2 Warnstufen
|
||||||
|
|
||||||
|
Bei Verzug:
|
||||||
|
|
||||||
|
- Tag 1:
|
||||||
|
- `next_forced_action = 'reminder'`
|
||||||
|
- Event `falukantUpdateDebt` mit `reason: 'delinquency'`
|
||||||
|
- Tag 2:
|
||||||
|
- `next_forced_action = 'final_warning'`
|
||||||
|
- Event `falukantUpdateDebt` mit `reason: 'delinquency'`
|
||||||
|
- Tag 3:
|
||||||
|
- Schuldturm-Eintritt
|
||||||
|
|
||||||
|
Für Warnstufen senden:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- zusätzlich `falukantUpdateStatus`
|
||||||
|
|
||||||
|
## 5. Eintritt in den Schuldturm
|
||||||
|
|
||||||
|
Bei `days_overdue >= 3`:
|
||||||
|
|
||||||
|
- `status = 'imprisoned'`
|
||||||
|
- `entered_at = now()`
|
||||||
|
- `released_at = null`
|
||||||
|
- `debt_at_entry = aktuelle offene Schuld`
|
||||||
|
- `remaining_debt = aktuelle offene Schuld`
|
||||||
|
- `reason = 'credit_default'`
|
||||||
|
- `creditworthiness_penalty += 45`
|
||||||
|
- `next_forced_action = 'asset_seizure'`
|
||||||
|
- `public_known = true`
|
||||||
|
|
||||||
|
### 5.1 Sofortfolgen bei Eintritt
|
||||||
|
|
||||||
|
Einmalig anwenden:
|
||||||
|
|
||||||
|
- Reputation deutlich senken
|
||||||
|
- Empfehlung: `-12`
|
||||||
|
- `marriage_satisfaction` senken
|
||||||
|
- Empfehlung: `-10`
|
||||||
|
- `household_tension_score` erhöhen
|
||||||
|
- Empfehlung: `+15`
|
||||||
|
- `household_tension_reasons_json` um `debtorsPrison` ergänzen
|
||||||
|
|
||||||
|
Zusätzlich:
|
||||||
|
|
||||||
|
- aktive Liebhaber/Mätressen sichtbar destabilisieren
|
||||||
|
- mindestens `affection -= 4`
|
||||||
|
- Kreditaufnahme und aktive Falukant-Aktionen bleiben projektseitig bereits gesperrt
|
||||||
|
|
||||||
|
Bei Eintritt senden:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'debtors_prison_entered'`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `reason: 'daily'`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
|
||||||
|
## 6. Verwertung / Pfändung
|
||||||
|
|
||||||
|
Die Verwertung läuft nicht alles auf einmal, sondern schrittweise pro Tick.
|
||||||
|
|
||||||
|
Reihenfolge:
|
||||||
|
|
||||||
|
1. freies Geld
|
||||||
|
2. Fahrzeuge
|
||||||
|
3. Waren / Lagerbestände
|
||||||
|
4. Haus
|
||||||
|
5. Niederlassungen
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
|
||||||
|
- `remaining_debt` schrittweise senken
|
||||||
|
- Fortschritt im UI sichtbar machen
|
||||||
|
|
||||||
|
### 6.1 Geld
|
||||||
|
|
||||||
|
Wenn `falukant_user.money > 0`:
|
||||||
|
|
||||||
|
- direkt zur Schuld tilgen
|
||||||
|
- `remaining_debt -= eingezogener_betrag`
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'asset_seizure'`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
|
### 6.2 Fahrzeuge
|
||||||
|
|
||||||
|
Verkaufe zuerst:
|
||||||
|
|
||||||
|
- freie Fahrzeuge
|
||||||
|
- dann weniger wertvolle Typen
|
||||||
|
- keine Fahrzeuge in aktiven Transporten im selben Tick anfassen, falls technisch problematisch
|
||||||
|
|
||||||
|
Erlös:
|
||||||
|
|
||||||
|
- Empfehlung: `vehicle_type.cost * condition_factor * 0.55`
|
||||||
|
|
||||||
|
Zusätzlich in `assets_seized_json` protokollieren:
|
||||||
|
|
||||||
|
- Typ
|
||||||
|
- Anzahl
|
||||||
|
- Erlös
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'vehicle_liquidation'`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
|
### 6.3 Waren / Lager
|
||||||
|
|
||||||
|
Verwertbare Güter:
|
||||||
|
|
||||||
|
- Lagerbestände
|
||||||
|
- Inventar
|
||||||
|
- handelbare Waren
|
||||||
|
|
||||||
|
Erlös:
|
||||||
|
|
||||||
|
- Empfehlung: Marktwert mit Abschlag von `35% bis 50%`
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'asset_seizure'`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
|
||||||
|
### 6.4 Haus
|
||||||
|
|
||||||
|
Wenn Restschuld nach Geld/Fahrzeugen/Waren weiter hoch ist:
|
||||||
|
|
||||||
|
- Haus pfänden
|
||||||
|
- Spieler auf niedrigeres Haus oder Minimalhaus zurücksetzen
|
||||||
|
- Dienerschaft reduzieren
|
||||||
|
- `household_order` senken
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'house_seizure'`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `reason: 'daily'`
|
||||||
|
|
||||||
|
### 6.5 Niederlassungen
|
||||||
|
|
||||||
|
Wenn weiter nicht gedeckt:
|
||||||
|
|
||||||
|
- Niederlassungen schließen
|
||||||
|
- zuerst niedrige Stufe / niedriger Wert
|
||||||
|
- Hauptniederlassung nur als letzter Schritt
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'branch_closure'`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
|
## 7. Laufende soziale Folgen im Schuldturm
|
||||||
|
|
||||||
|
Solange `status = 'imprisoned'`:
|
||||||
|
|
||||||
|
- täglicher Reputationsmalus
|
||||||
|
- Empfehlung: `-2`
|
||||||
|
- zusätzliche `creditworthiness_penalty += 1` pro Tag
|
||||||
|
- `marriage_satisfaction -= 1`
|
||||||
|
- `household_tension_score += 2`
|
||||||
|
|
||||||
|
Wenn aktive Liebschaften bestehen:
|
||||||
|
|
||||||
|
- `affection -= 2`
|
||||||
|
- bei niedriger Zuneigung oder hoher Sichtbarkeit kann Beziehung enden
|
||||||
|
|
||||||
|
Empfohlene Absprungregel:
|
||||||
|
|
||||||
|
- wenn `affection <= 30` oder `months_underfunded >= 2`
|
||||||
|
- Chance auf Beziehungsende prüfen
|
||||||
|
- bei repräsentativen Beziehungen zusätzlich höhere Absprungchance, wenn `public_known = true`
|
||||||
|
|
||||||
|
Events bei sozialen Folgewirkungen:
|
||||||
|
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `reason: 'daily'`
|
||||||
|
- zusätzlich `falukantUpdateStatus`
|
||||||
|
|
||||||
|
## 8. Kreditwürdigkeit
|
||||||
|
|
||||||
|
Die UI rechnet bereits aus `creditworthiness_penalty` und Status einen sichtbaren Wert.
|
||||||
|
|
||||||
|
Der Daemon muss pflegen:
|
||||||
|
|
||||||
|
- `creditworthiness_penalty`
|
||||||
|
- `status`
|
||||||
|
- `days_overdue`
|
||||||
|
|
||||||
|
Empfehlung:
|
||||||
|
|
||||||
|
- Eintritt Schuldturm: `+45`
|
||||||
|
- pro weiterem Hafttag: `+1`
|
||||||
|
- Hauspfändung: zusätzlich `+10`
|
||||||
|
- Niederlassungsschließung: zusätzlich `+8`
|
||||||
|
|
||||||
|
## 9. Freilassung
|
||||||
|
|
||||||
|
Freilassung, wenn:
|
||||||
|
|
||||||
|
- keine relevante Restschuld mehr offen ist
|
||||||
|
oder
|
||||||
|
- ein definierter Restwert unterschritten wird, falls ihr einen Bagatellgrenzwert wollt
|
||||||
|
|
||||||
|
Dann:
|
||||||
|
|
||||||
|
- `status = 'released'`
|
||||||
|
- `released_at = now()`
|
||||||
|
- `next_forced_action = null`
|
||||||
|
- `days_overdue = 0`
|
||||||
|
- `remaining_debt = 0`
|
||||||
|
|
||||||
|
Events:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `reason: 'debtors_prison_released'`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `reason: 'daily'`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
|
||||||
|
Keine automatische vollständige soziale Heilung:
|
||||||
|
|
||||||
|
- Reputation bleibt reduziert
|
||||||
|
- Kreditwürdigkeit bleibt reduziert
|
||||||
|
- Familie/Haus bleiben in Folgezuständen
|
||||||
|
|
||||||
|
## 10. Event-Kommunikation zur UI
|
||||||
|
|
||||||
|
Der Daemon sendet als Primärevent:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantUpdateDebt",
|
||||||
|
"user_id": 123,
|
||||||
|
"reason": "delinquency"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mögliche `reason`:
|
||||||
|
|
||||||
|
- `delinquency`
|
||||||
|
- `debtors_prison_entered`
|
||||||
|
- `asset_seizure`
|
||||||
|
- `vehicle_liquidation`
|
||||||
|
- `house_seizure`
|
||||||
|
- `branch_closure`
|
||||||
|
- `debtors_prison_released`
|
||||||
|
|
||||||
|
### 10.1 Begleitevents
|
||||||
|
|
||||||
|
Je nach Folge zusätzlich:
|
||||||
|
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
|
||||||
|
### 10.2 Empfohlene Minimalregeln
|
||||||
|
|
||||||
|
- `delinquency`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `debtors_prison_entered`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
- `asset_seizure`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- optional `falukantBranchUpdate`
|
||||||
|
- `vehicle_liquidation`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `house_seizure`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `branch_closure`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
- `debtors_prison_released`:
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- `falukantUpdateStatus`
|
||||||
|
- `falukantUpdateFamily`
|
||||||
|
- `falukantHouseUpdate`
|
||||||
|
- `falukantBranchUpdate`
|
||||||
|
|
||||||
|
## 11. Idempotenz
|
||||||
|
|
||||||
|
Der Worker muss idempotent arbeiten.
|
||||||
|
|
||||||
|
Wichtig:
|
||||||
|
|
||||||
|
- Eintritt in den Schuldturm nicht mehrfach für denselben aktiven Fall auslösen
|
||||||
|
- Verwertungsschritte nur einmal je Asset anwenden
|
||||||
|
- `released` nicht erneut freisetzen
|
||||||
|
|
||||||
|
Empfehlung:
|
||||||
|
|
||||||
|
- pro Tick Transaktion
|
||||||
|
- pro Nutzer eine klare Reihenfolge
|
||||||
|
- Änderungen in `assets_seized_json` protokollieren
|
||||||
|
|
||||||
|
## 12. Mindestumsetzung für Version 1
|
||||||
|
|
||||||
|
Pflicht:
|
||||||
|
|
||||||
|
1. Verzugstage pflegen
|
||||||
|
2. Eintritt nach 3 Tagen
|
||||||
|
3. Status und Penalty schreiben
|
||||||
|
4. Geld zuerst einziehen
|
||||||
|
5. danach Fahrzeuge
|
||||||
|
6. Events senden
|
||||||
|
|
||||||
|
Danach:
|
||||||
|
|
||||||
|
7. Hauspfändung
|
||||||
|
8. Niederlassungsschließung
|
||||||
|
9. volle Familienfolgen
|
||||||
|
|
||||||
|
## 13. Hinweis an den Daemon
|
||||||
|
|
||||||
|
Die projektseitigen Grundlagen sind bereits umgesetzt:
|
||||||
|
|
||||||
|
- `debtors_prism` ist erweitert
|
||||||
|
- Bank-/Haus-/Familien-/Übersichts-UI reagiert auf den Status
|
||||||
|
- aktive Falukant-Aktionen werden im Backend bereits gesperrt, sobald `inDebtorsPrison = true`
|
||||||
|
|
||||||
|
Der Daemon muss daher vor allem die Zustände und Folgen zuverlässig schreiben und die dokumentierten Events senden.
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# Falukant: UI-Anpassung – WebSocket & Familie / Liebschaften
|
# Falukant: UI-Anpassung - WebSocket & Familie / Liebschaften
|
||||||
|
|
||||||
Dieses Dokument beschreibt die Nachrichten, die der externe Falukant-Daemon für Familien-, Ehe- und Liebschaftsänderungen sendet, damit die UI gezielt reagieren kann.
|
Dieses Dokument beschreibt die Nachrichten, die der externe Falukant-Daemon über den WebSocket-Broadcast sendet, damit die UI gezielt reagieren kann.
|
||||||
|
|
||||||
Transport:
|
Transport:
|
||||||
|
|
||||||
- Alle Clients erhalten denselben Broadcast.
|
- Alle Clients erhalten denselben Broadcast.
|
||||||
- Die UI muss nach `user_id` filtern und nur Events für die eingeloggte Session verarbeiten.
|
- Die UI muss nach `user_id` filtern und nur Events für die eingeloggte Session verarbeiten.
|
||||||
|
|
||||||
@@ -10,10 +11,13 @@ Transport:
|
|||||||
|
|
||||||
| `event` | Pflichtfelder | Typische UI-Reaktion |
|
| `event` | Pflichtfelder | Typische UI-Reaktion |
|
||||||
|---------|----------------|----------------------|
|
|---------|----------------|----------------------|
|
||||||
| `falukantUpdateFamily` | `user_id`, `reason` | Gezielter Refresh von Familie, Liebschaften und je nach `reason` auch Geld oder Ruf |
|
| `falukantUpdateFamily` | `user_id`, `reason` | Gezielter Refresh Familie/Liebe/Geld je nach `reason` |
|
||||||
| `falukantUpdateStatus` | `user_id` | Allgemeiner Falukant-Status-/Spielstands-Refresh |
|
| `falukantUpdateStatus` | `user_id` | Allgemeiner Status-/Spielstands-Refresh |
|
||||||
| `children_update` | `user_id` | Kinderliste und Familienansicht aktualisieren |
|
| `falukantUpdateProductionCertificate` | `user_id`, `reason`, `old_certificate`, `new_certificate` | Produkte / Produktions-UI / Zertifikat neu laden |
|
||||||
| `falukant_family_scandal_hint` | `relationship_id` | Optionaler Hinweis oder Logeintrag; kein `user_id` |
|
| `children_update` | `user_id` | Kinderliste / FamilyView aktualisieren |
|
||||||
|
| `falukant_family_scandal_hint` | `relationship_id` | Optionaler Toast oder Log; kein `user_id` |
|
||||||
|
| `falukantUpdateChurch` | `user_id`, `reason` | Kirchenämter, Bewerbungen, Ernennungen |
|
||||||
|
| `falukantUpdateDebt` | `user_id`, `reason` | Schuldturm, Verzug, Pfändung, Freilassung |
|
||||||
|
|
||||||
## 2. JSON-Payloads
|
## 2. JSON-Payloads
|
||||||
|
|
||||||
@@ -28,14 +32,32 @@ Transport:
|
|||||||
```
|
```
|
||||||
|
|
||||||
`reason` ist immer genau einer dieser festen Strings:
|
`reason` ist immer genau einer dieser festen Strings:
|
||||||
|
|
||||||
- `daily`
|
- `daily`
|
||||||
- `monthly`
|
- `monthly`
|
||||||
|
- `lover_installment`
|
||||||
- `scandal`
|
- `scandal`
|
||||||
- `lover_birth`
|
- `lover_birth`
|
||||||
|
|
||||||
Es gibt keine weiteren `reason`-Werte.
|
### 2.2 `falukantUpdateChurch`
|
||||||
|
|
||||||
### 2.2 `falukantUpdateStatus`
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantUpdateChurch",
|
||||||
|
"user_id": 123,
|
||||||
|
"reason": "applications"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mögliche `reason`:
|
||||||
|
|
||||||
|
- `applications`
|
||||||
|
- `npc_decision`
|
||||||
|
- `appointment`
|
||||||
|
- `vacancy_fill`
|
||||||
|
- `promotion`
|
||||||
|
|
||||||
|
### 2.3 `falukantUpdateStatus`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -44,9 +66,21 @@ Es gibt keine weiteren `reason`-Werte.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Dieses Event wird typischerweise direkt nach `falukantUpdateFamily` mit derselben `user_id` gesendet.
|
Dieses Event wird typischerweise direkt nach einem fachlichen Falukant-Event mit derselben `user_id` gesendet.
|
||||||
|
|
||||||
### 2.3 `children_update`
|
### 2.4 `falukantUpdateProductionCertificate`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantUpdateProductionCertificate",
|
||||||
|
"user_id": 123,
|
||||||
|
"reason": "daily_recalculation",
|
||||||
|
"old_certificate": 2,
|
||||||
|
"new_certificate": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 `children_update`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -56,10 +90,11 @@ Dieses Event wird typischerweise direkt nach `falukantUpdateFamily` mit derselbe
|
|||||||
```
|
```
|
||||||
|
|
||||||
Dieses Event tritt bei Geburt aus einer Liebschaft auf, meist zusammen mit:
|
Dieses Event tritt bei Geburt aus einer Liebschaft auf, meist zusammen mit:
|
||||||
|
|
||||||
- `falukantUpdateFamily` mit `reason: "lover_birth"`
|
- `falukantUpdateFamily` mit `reason: "lover_birth"`
|
||||||
- `falukantUpdateStatus`
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
### 2.4 `falukant_family_scandal_hint`
|
### 2.6 `falukant_family_scandal_hint`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -69,56 +104,118 @@ Dieses Event tritt bei Geburt aus einer Liebschaft auf, meist zusammen mit:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Hinweis:
|
Hinweis:
|
||||||
|
|
||||||
- Dieses Event enthält kein `user_id`.
|
- Dieses Event enthält kein `user_id`.
|
||||||
- Die UI kann es ignorieren oder optional nur für Log-/Toast-Zwecke verwenden.
|
- Die UI kann es ignorieren oder optional nur für Log-/Toast-Zwecke verwenden.
|
||||||
- Die eigentliche nutzerbezogene Aktualisierung läuft über `falukantUpdateFamily` mit `reason: "scandal"`.
|
- Die eigentliche nutzerbezogene Aktualisierung läuft über `falukantUpdateFamily` mit `reason: "scandal"`.
|
||||||
|
|
||||||
|
### 2.7 `falukantUpdateDebt`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "falukantUpdateDebt",
|
||||||
|
"user_id": 123,
|
||||||
|
"reason": "debtors_prison_entered"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mögliche `reason`:
|
||||||
|
|
||||||
|
- `delinquency`
|
||||||
|
- `debtors_prison_entered`
|
||||||
|
- `asset_seizure`
|
||||||
|
- `vehicle_liquidation`
|
||||||
|
- `house_seizure`
|
||||||
|
- `branch_closure`
|
||||||
|
- `debtors_prison_released`
|
||||||
|
|
||||||
## 3. Fachliche Bedeutung von `reason`
|
## 3. Fachliche Bedeutung von `reason`
|
||||||
|
|
||||||
### `reason: "daily"`
|
### 3.1 `falukantUpdateFamily`
|
||||||
|
|
||||||
|
#### `reason: "daily"`
|
||||||
|
|
||||||
`daily` ist der Sammelgrund für tägliche Änderungen im Familien- und Liebschaftssystem.
|
`daily` ist der Sammelgrund für tägliche Änderungen im Familien- und Liebschaftssystem.
|
||||||
|
|
||||||
Darunter fallen insbesondere:
|
Darunter fallen insbesondere:
|
||||||
|
|
||||||
- tägliche Drift und Änderung der Ehezufriedenheit
|
- tägliche Drift und Änderung der Ehezufriedenheit
|
||||||
|
- `marriage_public_stability`
|
||||||
|
- `household_tension_score`
|
||||||
- Ehe-Buffs und temporäre Zähler wie Geschenk-, Fest- oder Haus-Effekte
|
- Ehe-Buffs und temporäre Zähler wie Geschenk-, Fest- oder Haus-Effekte
|
||||||
- tägliche Liebschaftslogik für aktive Beziehungen
|
- tägliche Liebschaftslogik für aktive Beziehungen
|
||||||
- Rufverlust bei zwei oder mehr sichtbaren Liebschaften
|
- Rufverlust bei zwei oder mehr sichtbaren Liebschaften
|
||||||
- Zufalls-Mali wie Gerücht oder Tadel
|
- Zufalls-Mali wie Gerücht oder Tadel
|
||||||
|
|
||||||
Wichtig:
|
Wichtig:
|
||||||
|
|
||||||
- Es gibt kein separates Event für „nur Ehe-Buff“.
|
- Es gibt kein separates Event für „nur Ehe-Buff“.
|
||||||
- Es gibt kein separates Event für „nur zwei sichtbare Liebschaften“.
|
- Es gibt kein separates Event für „nur zwei sichtbare Liebschaften“.
|
||||||
- Es gibt kein separates Event für „nur Gerücht/Tadel“.
|
- Es gibt kein separates Event für „nur Gerücht/Tadel“.
|
||||||
- Alles davon erscheint in der UI ausschließlich als `falukantUpdateFamily` mit `reason: "daily"`.
|
- Alles davon erscheint in der UI ausschließlich als `falukantUpdateFamily` mit `reason: "daily"`.
|
||||||
|
|
||||||
### `reason: "scandal"`
|
#### `reason: "monthly"`
|
||||||
|
|
||||||
|
`monthly` steht für monatliche Verarbeitung, insbesondere:
|
||||||
|
|
||||||
|
- Dienerschaftskosten
|
||||||
|
- laufende Kosten
|
||||||
|
- Unterversorgung
|
||||||
|
- Geldänderungen
|
||||||
|
|
||||||
|
#### `reason: "lover_installment"`
|
||||||
|
|
||||||
|
`lover_installment` steht für die 2-Stunden-Unterhaltsbelastung von Liebschaften.
|
||||||
|
|
||||||
|
Die UI sollte dafür mindestens:
|
||||||
|
|
||||||
|
- Geld neu laden
|
||||||
|
- Family-/Liebschaftsstatus neu laden
|
||||||
|
|
||||||
|
#### `reason: "scandal"`
|
||||||
|
|
||||||
`scandal` wird zusätzlich zu einem gelungenen Skandalwurf gesendet.
|
`scandal` wird zusätzlich zu einem gelungenen Skandalwurf gesendet.
|
||||||
|
|
||||||
Typischer Ablauf:
|
Typischer Ablauf:
|
||||||
|
|
||||||
- optional `falukant_family_scandal_hint`
|
- optional `falukant_family_scandal_hint`
|
||||||
- `falukantUpdateFamily` mit `reason: "scandal"`
|
- `falukantUpdateFamily` mit `reason: "scandal"`
|
||||||
- `falukantUpdateStatus`
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
Danach kann für denselben Nutzer am selben Tag zusätzlich noch `daily` folgen.
|
Danach kann für denselben Nutzer am selben Tag zusätzlich noch `daily` folgen.
|
||||||
|
|
||||||
### `reason: "monthly"`
|
#### `reason: "lover_birth"`
|
||||||
|
|
||||||
`monthly` steht für Monatsverarbeitung, insbesondere:
|
|
||||||
- laufende Kosten
|
|
||||||
- Unterversorgung
|
|
||||||
- Geldänderungen
|
|
||||||
|
|
||||||
### `reason: "lover_birth"`
|
|
||||||
|
|
||||||
`lover_birth` signalisiert ein neues Kind aus einer Liebschaft.
|
`lover_birth` signalisiert ein neues Kind aus einer Liebschaft.
|
||||||
|
|
||||||
Meist folgen zusammen:
|
Meist folgen zusammen:
|
||||||
|
|
||||||
- `falukantUpdateFamily` mit `reason: "lover_birth"`
|
- `falukantUpdateFamily` mit `reason: "lover_birth"`
|
||||||
- `children_update`
|
- `children_update`
|
||||||
- `falukantUpdateStatus`
|
- `falukantUpdateStatus`
|
||||||
|
|
||||||
|
### 3.2 `falukantUpdateChurch`
|
||||||
|
|
||||||
|
- `applications`: Spieler ist kirchlicher Vorgesetzter; offene Bewerbungen warten
|
||||||
|
- `npc_decision`: NPC-Vorgesetzter hat entschieden
|
||||||
|
- `appointment`: automatische Annahme älterer Bewerbung
|
||||||
|
- `vacancy_fill`: Interimsbesetzung
|
||||||
|
- `promotion`: reserviert / zukünftig
|
||||||
|
|
||||||
|
### 3.3 `falukantUpdateProductionCertificate`
|
||||||
|
|
||||||
|
- `daily_recalculation`: Zertifikat nach täglicher Prüfung geändert
|
||||||
|
|
||||||
|
### 3.4 `falukantUpdateDebt`
|
||||||
|
|
||||||
|
- `delinquency`: Mahnstufe oder Verzug aktualisiert
|
||||||
|
- `debtors_prison_entered`: Eintritt in den Schuldturm
|
||||||
|
- `asset_seizure`: Geld, Waren oder sonstige Vermögenswerte eingezogen
|
||||||
|
- `vehicle_liquidation`: Fahrzeuge zwangsverkauft
|
||||||
|
- `house_seizure`: Haus gepfändet
|
||||||
|
- `branch_closure`: Niederlassung geschlossen
|
||||||
|
- `debtors_prison_released`: Freilassung
|
||||||
|
|
||||||
## 4. Empfohlene Handler-Logik
|
## 4. Empfohlene Handler-Logik
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -131,10 +228,22 @@ onMessage(json):
|
|||||||
refreshPlayerStatus()
|
refreshPlayerStatus()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
case "falukantUpdateProductionCertificate":
|
||||||
|
refreshProductsAndProductionUi()
|
||||||
|
return
|
||||||
|
|
||||||
case "children_update":
|
case "children_update":
|
||||||
refreshChildrenAndFamilyView()
|
refreshChildrenAndFamilyView()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
case "falukantUpdateChurch":
|
||||||
|
refreshChurchContextByReason(json.reason)
|
||||||
|
return
|
||||||
|
|
||||||
|
case "falukantUpdateDebt":
|
||||||
|
refreshDebtAndAffectedViews(json.reason)
|
||||||
|
return
|
||||||
|
|
||||||
case "falukantUpdateFamily":
|
case "falukantUpdateFamily":
|
||||||
switch json.reason:
|
switch json.reason:
|
||||||
case "daily":
|
case "daily":
|
||||||
@@ -145,6 +254,10 @@ onMessage(json):
|
|||||||
refreshMoney()
|
refreshMoney()
|
||||||
refreshFamilyAndRelationships()
|
refreshFamilyAndRelationships()
|
||||||
break
|
break
|
||||||
|
case "lover_installment":
|
||||||
|
refreshMoney()
|
||||||
|
refreshFamilyAndRelationships()
|
||||||
|
break
|
||||||
case "scandal":
|
case "scandal":
|
||||||
showScandalToastOptional()
|
showScandalToastOptional()
|
||||||
refreshFamilyAndRelationships()
|
refreshFamilyAndRelationships()
|
||||||
@@ -162,15 +275,23 @@ onMessage(json):
|
|||||||
|
|
||||||
## 5. Deduplizierung
|
## 5. Deduplizierung
|
||||||
|
|
||||||
Am selben Tag kann ein Nutzer mehrere relevante Events erhalten, zum Beispiel:
|
Ein Nutzer kann kurz hintereinander mehrere relevante Events erhalten, zum Beispiel:
|
||||||
|
|
||||||
- `scandal`
|
- `scandal`
|
||||||
- danach `daily`
|
- danach `daily`
|
||||||
- danach `falukantUpdateStatus`
|
- danach `falukantUpdateStatus`
|
||||||
|
|
||||||
|
oder:
|
||||||
|
|
||||||
|
- `falukantUpdateDebt`
|
||||||
|
- direkt danach `falukantUpdateStatus`
|
||||||
|
- zusätzlich `falukantUpdateFamily`
|
||||||
|
|
||||||
Die UI sollte deshalb:
|
Die UI sollte deshalb:
|
||||||
|
|
||||||
- Refreshes bündeln oder entprellen
|
- Refreshes bündeln oder entprellen
|
||||||
- idempotente Reloads verwenden
|
- idempotente Reloads verwenden
|
||||||
- nicht davon ausgehen, dass jeder fachliche Effekt einen eigenen `reason` hat
|
- nicht davon ausgehen, dass jeder fachliche Effekt einen eigenen Spezial-Eventpfad hat
|
||||||
|
|
||||||
## 6. Welche Daten sollten neu geladen werden?
|
## 6. Welche Daten sollten neu geladen werden?
|
||||||
|
|
||||||
@@ -178,15 +299,18 @@ Die UI sollte deshalb:
|
|||||||
|-----------|--------------------|
|
|-----------|--------------------|
|
||||||
| Jede `falukantUpdateFamily` | Family-/Relationship-Daten neu laden |
|
| Jede `falukantUpdateFamily` | Family-/Relationship-Daten neu laden |
|
||||||
| `reason: "monthly"` | Family-Daten plus Geld/Status neu laden |
|
| `reason: "monthly"` | Family-Daten plus Geld/Status neu laden |
|
||||||
|
| `reason: "lover_installment"` | Geld plus Family-Daten neu laden |
|
||||||
| `reason: "daily"` | Family-Daten neu laden, bei Bedarf auch Ruf-/Statusdaten |
|
| `reason: "daily"` | Family-Daten neu laden, bei Bedarf auch Ruf-/Statusdaten |
|
||||||
| `reason: "scandal"` | Family-Daten plus Ruf-/Statusdaten neu laden |
|
| `reason: "scandal"` | Family-Daten plus Ruf-/Statusdaten neu laden |
|
||||||
| `children_update` / `lover_birth` | Kinderdaten und FamilyView neu laden |
|
| `children_update` / `lover_birth` | Kinderdaten und FamilyView neu laden |
|
||||||
|
| `falukantUpdateChurch` | Kirchenämter, Bewerbungen, freie Positionen je nach `reason` |
|
||||||
|
| `falukantUpdateProductionCertificate` | User-Status, Zertifikat, Produkte, Produktions-UI |
|
||||||
|
| `falukantUpdateDebt` | Bank, Overview, House, Branch, ggf. Family |
|
||||||
|
|
||||||
## 7. Sonderfälle
|
## 7. Sonderfälle
|
||||||
|
|
||||||
| Fall | Verhalten |
|
| Fall | Verhalten |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
| NPC ohne `user_id` | Keine nutzerbezogenen Family-Socket-Events |
|
| NPC ohne `user_id` | Keine nutzerbezogenen Socket-Events |
|
||||||
| Mehrere Events kurz hintereinander | Normal; UI sollte damit robust umgehen |
|
| Mehrere Events kurz hintereinander | Normal; UI sollte damit robust umgehen |
|
||||||
| Nur `falukantUpdateStatus` ohne Family-Event | Kann von anderen Falukant-Workern kommen |
|
| Nur `falukantUpdateStatus` ohne Fach-Event | Kann von anderen Falukant-Workern kommen |
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default {
|
|||||||
},
|
},
|
||||||
setupSocketListeners() {
|
setupSocketListeners() {
|
||||||
this.teardownSocketListeners();
|
this.teardownSocketListeners();
|
||||||
const daemonEvents = ['falukantUpdateStatus', 'falukantUpdateFamily', 'falukantUpdateChurch', 'children_update', 'falukantUpdateProductionCertificate', 'stock_change', 'familychanged'];
|
const daemonEvents = ['falukantUpdateStatus', 'falukantUpdateFamily', 'falukantUpdateChurch', 'falukantUpdateDebt', 'children_update', 'falukantUpdateProductionCertificate', 'stock_change', 'familychanged'];
|
||||||
if (this.daemonSocket) {
|
if (this.daemonSocket) {
|
||||||
this._daemonMessageHandler = (event) => {
|
this._daemonMessageHandler = (event) => {
|
||||||
if (event.data === 'ping') return;
|
if (event.data === 'ping') return;
|
||||||
@@ -129,6 +129,9 @@ export default {
|
|||||||
this._churchSocketHandler = (data) => {
|
this._churchSocketHandler = (data) => {
|
||||||
if (this.matchesCurrentUser(data)) this.queueFetchData();
|
if (this.matchesCurrentUser(data)) this.queueFetchData();
|
||||||
};
|
};
|
||||||
|
this._debtSocketHandler = (data) => {
|
||||||
|
if (this.matchesCurrentUser(data)) this.queueFetchData();
|
||||||
|
};
|
||||||
this._childrenSocketHandler = (data) => {
|
this._childrenSocketHandler = (data) => {
|
||||||
if (this.matchesCurrentUser(data)) this.queueFetchData();
|
if (this.matchesCurrentUser(data)) this.queueFetchData();
|
||||||
};
|
};
|
||||||
@@ -140,6 +143,7 @@ export default {
|
|||||||
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
||||||
this.socket.on('falukantUpdateChurch', this._churchSocketHandler);
|
this.socket.on('falukantUpdateChurch', this._churchSocketHandler);
|
||||||
|
this.socket.on('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
this.socket.on('children_update', this._childrenSocketHandler);
|
this.socket.on('children_update', this._childrenSocketHandler);
|
||||||
this.socket.on('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
this.socket.on('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
||||||
this.socket.on('falukantBranchUpdate', this._branchSocketHandler);
|
this.socket.on('falukantBranchUpdate', this._branchSocketHandler);
|
||||||
@@ -154,6 +158,7 @@ export default {
|
|||||||
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
||||||
if (this._churchSocketHandler) this.socket.off('falukantUpdateChurch', this._churchSocketHandler);
|
if (this._churchSocketHandler) this.socket.off('falukantUpdateChurch', this._churchSocketHandler);
|
||||||
|
if (this._debtSocketHandler) this.socket.off('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
if (this._childrenSocketHandler) this.socket.off('children_update', this._childrenSocketHandler);
|
if (this._childrenSocketHandler) this.socket.off('children_update', this._childrenSocketHandler);
|
||||||
if (this._productionCertificateSocketHandler) this.socket.off('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
if (this._productionCertificateSocketHandler) this.socket.off('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
||||||
if (this._branchSocketHandler) this.socket.off('falukantBranchUpdate', this._branchSocketHandler);
|
if (this._branchSocketHandler) this.socket.off('falukantBranchUpdate', this._branchSocketHandler);
|
||||||
|
|||||||
@@ -18,15 +18,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button @click="openCreateBranchDialog">
|
<button
|
||||||
|
@click="openCreateBranchDialog"
|
||||||
|
:disabled="blocked"
|
||||||
|
>
|
||||||
{{ $t('falukant.branch.actions.create') }}
|
{{ $t('falukant.branch.actions.create') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="$emit('upgradeBranch')"
|
@click="$emit('upgradeBranch')"
|
||||||
:disabled="!localSelectedBranch"
|
:disabled="!localSelectedBranch || blocked"
|
||||||
>
|
>
|
||||||
{{ $t('falukant.branch.actions.upgrade') }}
|
{{ $t('falukant.branch.actions.upgrade') }}
|
||||||
</button>
|
</button>
|
||||||
|
<span v-if="blocked && blockedReason" class="blocked-hint">{{ blockedReason }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,6 +55,8 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
branches: { type: Array, required: true },
|
branches: { type: Array, required: true },
|
||||||
selectedBranch: { type: Object, default: null },
|
selectedBranch: { type: Object, default: null },
|
||||||
|
blocked: { type: Boolean, default: false },
|
||||||
|
blockedReason: { type: String, default: '' },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -82,6 +88,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
openCreateBranchDialog() {
|
openCreateBranchDialog() {
|
||||||
|
if (this.blocked) return;
|
||||||
this.$refs.createBranchDialog.open();
|
this.$refs.createBranchDialog.open();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -131,4 +138,13 @@ button {
|
|||||||
.weather-value {
|
.weather-value {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blocked-hint {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #8b2f23;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -36,6 +36,18 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="debtorsPrison.active" class="statusbar-warning" :class="{ 'is-prison': debtorsPrison.inDebtorsPrison }">
|
||||||
|
<strong>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</strong>
|
||||||
|
<span>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.debtorsPrison.globalLocked')
|
||||||
|
: $t('falukant.debtorsPrison.globalWarning') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<MessagesDialog ref="msgs" />
|
<MessagesDialog ref="msgs" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,6 +72,10 @@ export default {
|
|||||||
{ key: "children", icon: "👶", value: null },
|
{ key: "children", icon: "👶", value: null },
|
||||||
],
|
],
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
|
debtorsPrison: {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
},
|
||||||
pendingStatusRefresh: null,
|
pendingStatusRefresh: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -146,6 +162,10 @@ export default {
|
|||||||
const childCount = Number(response.data.childrenCount) || 0;
|
const childCount = Number(response.data.childrenCount) || 0;
|
||||||
const unbaptisedCount = Number(response.data.unbaptisedChildrenCount) || 0;
|
const unbaptisedCount = Number(response.data.unbaptisedChildrenCount) || 0;
|
||||||
this.unreadCount = Number(response.data.unreadNotifications) || 0;
|
this.unreadCount = Number(response.data.unreadNotifications) || 0;
|
||||||
|
this.debtorsPrison = response.data.debtorsPrison || {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
};
|
||||||
const childrenDisplay = `${childCount}${unbaptisedCount > 0 ? `(${unbaptisedCount})` : ''}`;
|
const childrenDisplay = `${childCount}${unbaptisedCount > 0 ? `(${unbaptisedCount})` : ''}`;
|
||||||
let healthStatus = '';
|
let healthStatus = '';
|
||||||
if (health > 90) {
|
if (health > 90) {
|
||||||
@@ -177,6 +197,7 @@ export default {
|
|||||||
this._statusSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
this._statusSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||||
this._familySocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
this._familySocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||||
this._churchSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
this._churchSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
||||||
|
this._debtSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||||
this._childrenSocketHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
|
this._childrenSocketHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
|
||||||
this._productionCertificateSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
this._productionCertificateSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
||||||
this._stockSocketHandler = (data) => this.handleEvent({ event: 'stock_change', ...data });
|
this._stockSocketHandler = (data) => this.handleEvent({ event: 'stock_change', ...data });
|
||||||
@@ -185,6 +206,7 @@ export default {
|
|||||||
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
||||||
this.socket.on('falukantUpdateChurch', this._churchSocketHandler);
|
this.socket.on('falukantUpdateChurch', this._churchSocketHandler);
|
||||||
|
this.socket.on('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
this.socket.on('children_update', this._childrenSocketHandler);
|
this.socket.on('children_update', this._childrenSocketHandler);
|
||||||
this.socket.on('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
this.socket.on('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
||||||
this.socket.on('stock_change', this._stockSocketHandler);
|
this.socket.on('stock_change', this._stockSocketHandler);
|
||||||
@@ -195,6 +217,7 @@ export default {
|
|||||||
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
||||||
if (this._churchSocketHandler) this.socket.off('falukantUpdateChurch', this._churchSocketHandler);
|
if (this._churchSocketHandler) this.socket.off('falukantUpdateChurch', this._churchSocketHandler);
|
||||||
|
if (this._debtSocketHandler) this.socket.off('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
if (this._childrenSocketHandler) this.socket.off('children_update', this._childrenSocketHandler);
|
if (this._childrenSocketHandler) this.socket.off('children_update', this._childrenSocketHandler);
|
||||||
if (this._productionCertificateSocketHandler) this.socket.off('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
if (this._productionCertificateSocketHandler) this.socket.off('falukantUpdateProductionCertificate', this._productionCertificateSocketHandler);
|
||||||
if (this._stockSocketHandler) this.socket.off('stock_change', this._stockSocketHandler);
|
if (this._stockSocketHandler) this.socket.off('stock_change', this._stockSocketHandler);
|
||||||
@@ -207,7 +230,7 @@ export default {
|
|||||||
this._daemonHandler = (event) => {
|
this._daemonHandler = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (['falukantUpdateStatus', 'falukantUpdateFamily', 'falukantUpdateChurch', 'children_update', 'falukantUpdateProductionCertificate', 'stock_change', 'familychanged'].includes(data.event)) {
|
if (['falukantUpdateStatus', 'falukantUpdateFamily', 'falukantUpdateChurch', 'falukantUpdateDebt', 'children_update', 'falukantUpdateProductionCertificate', 'stock_change', 'familychanged'].includes(data.event)) {
|
||||||
this.handleEvent(data);
|
this.handleEvent(data);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@@ -247,6 +270,7 @@ export default {
|
|||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
case 'falukantUpdateFamily':
|
case 'falukantUpdateFamily':
|
||||||
case 'falukantUpdateChurch':
|
case 'falukantUpdateChurch':
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
case 'children_update':
|
case 'children_update':
|
||||||
case 'falukantUpdateProductionCertificate':
|
case 'falukantUpdateProductionCertificate':
|
||||||
case 'stock_change':
|
case 'stock_change':
|
||||||
@@ -294,6 +318,25 @@ export default {
|
|||||||
box-shadow: var(--shadow-soft);
|
box-shadow: var(--shadow-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusbar-warning {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.35);
|
||||||
|
background: rgba(255, 244, 223, 0.92);
|
||||||
|
color: #8a5411;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar-warning.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.42);
|
||||||
|
background: rgba(255, 232, 225, 0.94);
|
||||||
|
color: #8b2f23;
|
||||||
|
}
|
||||||
|
|
||||||
.status-item {
|
.status-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
<dd>{{ falukantData.unreadNotificationsCount }}</dd>
|
<dd>{{ falukantData.unreadNotificationsCount }}</dd>
|
||||||
<dt>{{ $t('falukant.statusbar.children') }}</dt>
|
<dt>{{ $t('falukant.statusbar.children') }}</dt>
|
||||||
<dd>{{ falukantData.childrenCount }}</dd>
|
<dd>{{ falukantData.childrenCount }}</dd>
|
||||||
|
<template v-if="falukantData.debtorsPrison?.active">
|
||||||
|
<dt>{{ $t('falukant.bank.debtorsPrison.titlePrison') }}</dt>
|
||||||
|
<dd class="falukant-debt" :class="{ 'falukant-debt--warning': !falukantData.debtorsPrison?.inDebtorsPrison }">
|
||||||
|
{{ falukantData.debtorsPrison?.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</dd>
|
||||||
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
<span v-else>—</span>
|
<span v-else>—</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,6 +51,7 @@ export default {
|
|||||||
money: pick(raw, 'money', 'money'),
|
money: pick(raw, 'money', 'money'),
|
||||||
unreadNotificationsCount: pick(raw, 'unreadNotificationsCount', 'unread_notifications_count'),
|
unreadNotificationsCount: pick(raw, 'unreadNotificationsCount', 'unread_notifications_count'),
|
||||||
childrenCount: pick(raw, 'childrenCount', 'children_count'),
|
childrenCount: pick(raw, 'childrenCount', 'children_count'),
|
||||||
|
debtorsPrison: pick(raw, 'debtorsPrison', 'debtors_prison'),
|
||||||
// keep all original keys as fallback for any other usage
|
// keep all original keys as fallback for any other usage
|
||||||
...raw
|
...raw
|
||||||
};
|
};
|
||||||
@@ -173,4 +182,13 @@ export default {
|
|||||||
color: #198754;
|
color: #198754;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-widget__falukant dd.falukant-debt {
|
||||||
|
color: #8b2f23;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-widget__falukant dd.falukant-debt--warning {
|
||||||
|
color: #9a5a08;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
"children": "Kinder",
|
"children": "Kinder",
|
||||||
"children_unbaptised": "ungetaufte Kinder"
|
"children_unbaptised": "ungetaufte Kinder"
|
||||||
},
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"actionBlocked": "Im Schuldturm kannst du diese Aktion derzeit nicht ausführen.",
|
||||||
|
"globalWarning": "Dein Kreditverzug schränkt dein Handeln ein. Zwangsmaßnahmen können bald folgen.",
|
||||||
|
"globalLocked": "Du bist im Schuldturm. Fast alle aktiven Falukant-Handlungen sind derzeit gesperrt."
|
||||||
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Nachrichten",
|
"title": "Nachrichten",
|
||||||
"tooltip": "Nachrichten",
|
"tooltip": "Nachrichten",
|
||||||
@@ -220,6 +225,11 @@
|
|||||||
},
|
},
|
||||||
"branch": {
|
"branch": {
|
||||||
"title": "Filiale",
|
"title": "Filiale",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"branchLocked": "Im Schuldturm sind neue wirtschaftliche Schritte blockiert. Geschlossene oder gepfändete Standorte werden hier ebenfalls sichtbar.",
|
||||||
|
"branchRisk": "Dein Kreditverzug gefährdet Niederlassungen, Fahrzeuge und Lagerbestände.",
|
||||||
|
"selectionBlocked": "Neue Ausbauten sind im Schuldturm gesperrt."
|
||||||
|
},
|
||||||
"currentCertificate": "Derzeitiges Zertifikat",
|
"currentCertificate": "Derzeitiges Zertifikat",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"director": "Direktor",
|
"director": "Direktor",
|
||||||
@@ -483,6 +493,10 @@
|
|||||||
},
|
},
|
||||||
"family": {
|
"family": {
|
||||||
"title": "Familie",
|
"title": "Familie",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"familyWarning": "Anhaltender Kreditverzug belastet Ehe, Haushalt und Liebschaften.",
|
||||||
|
"familyImpact": "Der Schuldturm schadet Ehe, Hausfrieden und der Stabilität von Liebschaften."
|
||||||
|
},
|
||||||
"spouse": {
|
"spouse": {
|
||||||
"title": "Beziehung",
|
"title": "Beziehung",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -845,6 +859,10 @@
|
|||||||
},
|
},
|
||||||
"house": {
|
"house": {
|
||||||
"title": "Haus",
|
"title": "Haus",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"houseWarning": "Mit wachsendem Kreditverzug steigt das Risiko für Pfändung und erzwungenen Hausverlust.",
|
||||||
|
"houseRisk": "Dein Haus ist jetzt Teil der möglichen Zwangsverwertung."
|
||||||
|
},
|
||||||
"statusreport": "Zustand des Hauses",
|
"statusreport": "Zustand des Hauses",
|
||||||
"element": "Bereich",
|
"element": "Bereich",
|
||||||
"state": "Zustand",
|
"state": "Zustand",
|
||||||
@@ -1137,6 +1155,23 @@
|
|||||||
"maxCredit": "Maximaler Kredit",
|
"maxCredit": "Maximaler Kredit",
|
||||||
"availableCredit": "Verfügbarer Kredit"
|
"availableCredit": "Verfügbarer Kredit"
|
||||||
},
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"titleWarning": "Kreditverzug",
|
||||||
|
"titlePrison": "Schuldturm",
|
||||||
|
"descriptionWarning": "Deine Kredite sind im Verzug. Wenn du weiter nicht bedienst, drohen Zwangsmaßnahmen.",
|
||||||
|
"descriptionPrison": "Du sitzt im Schuldturm. Neue Kredite sind gesperrt und dein Vermögen wird schrittweise verwertet.",
|
||||||
|
"daysOverdue": "Verzugstage",
|
||||||
|
"creditworthiness": "Kreditwürdigkeit",
|
||||||
|
"nextForcedAction": "Nächste Zwangsmaßnahme",
|
||||||
|
"creditBlocked": "Im Schuldturm kannst du keine neuen Kredite aufnehmen.",
|
||||||
|
"creditError": "Der Kredit konnte nicht aufgenommen werden.",
|
||||||
|
"actions": {
|
||||||
|
"reminder": "Erste Mahnung",
|
||||||
|
"final_warning": "Letzte Mahnung",
|
||||||
|
"debtors_prison": "Einweisung in den Schuldturm",
|
||||||
|
"asset_seizure": "Pfändung von Vermögen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"credits": {
|
"credits": {
|
||||||
"title": "Kredite",
|
"title": "Kredite",
|
||||||
"none": "Derzeit hast Du keinen Kredit aufgenommen.",
|
"none": "Derzeit hast Du keinen Kredit aufgenommen.",
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
"windy": "Windy",
|
"windy": "Windy",
|
||||||
"clear": "Clear"
|
"clear": "Clear"
|
||||||
},
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"actionBlocked": "This action is blocked while you are in debtors' prison.",
|
||||||
|
"globalWarning": "Your credit delinquency is already restricting your actions. Forced measures may follow soon.",
|
||||||
|
"globalLocked": "You are in debtors' prison. Almost all active Falukant actions are currently blocked."
|
||||||
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Messages",
|
"title": "Messages",
|
||||||
"tooltip": "Messages",
|
"tooltip": "Messages",
|
||||||
@@ -184,6 +189,10 @@
|
|||||||
},
|
},
|
||||||
"house": {
|
"house": {
|
||||||
"title": "House",
|
"title": "House",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"houseWarning": "As delinquency grows, the risk of seizure and forced loss of the house increases.",
|
||||||
|
"houseRisk": "Your house is now part of the possible forced liquidation."
|
||||||
|
},
|
||||||
"statusreport": "House condition",
|
"statusreport": "House condition",
|
||||||
"element": "Element",
|
"element": "Element",
|
||||||
"state": "Condition",
|
"state": "Condition",
|
||||||
@@ -265,6 +274,11 @@
|
|||||||
"noProposals": "No director candidates available."
|
"noProposals": "No director candidates available."
|
||||||
},
|
},
|
||||||
"branch": {
|
"branch": {
|
||||||
|
"debtorsPrison": {
|
||||||
|
"branchLocked": "While in debtors' prison, new economic steps are blocked. Closed or seized branches will also become visible here.",
|
||||||
|
"branchRisk": "Your delinquency puts branches, vehicles and stored goods at risk.",
|
||||||
|
"selectionBlocked": "New expansions are blocked while imprisoned for debt."
|
||||||
|
},
|
||||||
"currentCertificate": "Current certificate",
|
"currentCertificate": "Current certificate",
|
||||||
"selection": {
|
"selection": {
|
||||||
"title": "Branch Selection",
|
"title": "Branch Selection",
|
||||||
@@ -505,6 +519,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"family": {
|
"family": {
|
||||||
|
"debtorsPrison": {
|
||||||
|
"familyWarning": "Ongoing debt delinquency puts strain on marriage, household and affairs.",
|
||||||
|
"familyImpact": "Debtors' prison damages marriage, household peace and the stability of affairs."
|
||||||
|
},
|
||||||
"children": {
|
"children": {
|
||||||
"title": "Children",
|
"title": "Children",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -736,6 +754,55 @@
|
|||||||
"error": "The child could not be baptized."
|
"error": "The child could not be baptized."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bank": {
|
||||||
|
"title": "Bank",
|
||||||
|
"account": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Balance",
|
||||||
|
"totalDebt": "Total debt",
|
||||||
|
"maxCredit": "Maximum credit",
|
||||||
|
"availableCredit": "Available credit"
|
||||||
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"titleWarning": "Credit delinquency",
|
||||||
|
"titlePrison": "Debtors' prison",
|
||||||
|
"descriptionWarning": "Your credits are overdue. If you continue to default, forced measures will follow.",
|
||||||
|
"descriptionPrison": "You are in debtors' prison. New credits are blocked and your assets will be liquidated step by step.",
|
||||||
|
"daysOverdue": "Days overdue",
|
||||||
|
"creditworthiness": "Creditworthiness",
|
||||||
|
"nextForcedAction": "Next forced action",
|
||||||
|
"creditBlocked": "You cannot take new credits while imprisoned for debt.",
|
||||||
|
"creditError": "The credit could not be taken.",
|
||||||
|
"actions": {
|
||||||
|
"reminder": "First reminder",
|
||||||
|
"final_warning": "Final warning",
|
||||||
|
"debtors_prison": "Commitment to debtors' prison",
|
||||||
|
"asset_seizure": "Asset seizure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"credits": {
|
||||||
|
"title": "Credits",
|
||||||
|
"none": "You currently do not have any credits.",
|
||||||
|
"amount": "Amount",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"interestRate": "Interest rate",
|
||||||
|
"table": {
|
||||||
|
"name": "Name",
|
||||||
|
"amount": "Amount",
|
||||||
|
"reason": "Reason",
|
||||||
|
"date": "Date"
|
||||||
|
},
|
||||||
|
"payoff": {
|
||||||
|
"title": "Take a new credit",
|
||||||
|
"height": "Credit amount",
|
||||||
|
"remaining": "Remaining possible credit amount",
|
||||||
|
"fee": "Credit interest",
|
||||||
|
"feeHeight": "Installment (10 payments)",
|
||||||
|
"total": "Total",
|
||||||
|
"confirm": "Take credit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"reputation": {
|
"reputation": {
|
||||||
"title": "Reputation",
|
"title": "Reputation",
|
||||||
"overview": {
|
"overview": {
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
"children": "Hijos",
|
"children": "Hijos",
|
||||||
"children_unbaptised": "hijos no bautizados"
|
"children_unbaptised": "hijos no bautizados"
|
||||||
},
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"actionBlocked": "Esta acción está bloqueada mientras estés en la prisión por deudas.",
|
||||||
|
"globalWarning": "Tu mora crediticia ya restringe tus acciones. Pronto pueden llegar medidas forzosas.",
|
||||||
|
"globalLocked": "Estás en la prisión por deudas. Casi todas las acciones activas de Falukant están actualmente bloqueadas."
|
||||||
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"title": "Mensajes",
|
"title": "Mensajes",
|
||||||
"tooltip": "Mensajes",
|
"tooltip": "Mensajes",
|
||||||
@@ -208,6 +213,11 @@
|
|||||||
},
|
},
|
||||||
"branch": {
|
"branch": {
|
||||||
"title": "Sucursal",
|
"title": "Sucursal",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"branchLocked": "En la prisión por deudas se bloquean los nuevos pasos económicos. Las sucursales cerradas o embargadas también se reflejarán aquí.",
|
||||||
|
"branchRisk": "Tu mora pone en peligro sucursales, vehículos y mercancías almacenadas.",
|
||||||
|
"selectionBlocked": "Las nuevas ampliaciones están bloqueadas en la prisión por deudas."
|
||||||
|
},
|
||||||
"currentCertificate": "Certificado actual",
|
"currentCertificate": "Certificado actual",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"director": "Director",
|
"director": "Director",
|
||||||
@@ -467,6 +477,10 @@
|
|||||||
},
|
},
|
||||||
"family": {
|
"family": {
|
||||||
"title": "Familia",
|
"title": "Familia",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"familyWarning": "La mora continuada perjudica el matrimonio, el hogar y las relaciones.",
|
||||||
|
"familyImpact": "La prisión por deudas daña el matrimonio, la paz del hogar y la estabilidad de las relaciones."
|
||||||
|
},
|
||||||
"spouse": {
|
"spouse": {
|
||||||
"title": "Relación",
|
"title": "Relación",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
@@ -811,6 +825,10 @@
|
|||||||
},
|
},
|
||||||
"house": {
|
"house": {
|
||||||
"title": "Casa",
|
"title": "Casa",
|
||||||
|
"debtorsPrison": {
|
||||||
|
"houseWarning": "A medida que aumenta la mora, crece el riesgo de embargo y pérdida forzosa de la casa.",
|
||||||
|
"houseRisk": "Tu casa forma ahora parte de la posible liquidación forzosa."
|
||||||
|
},
|
||||||
"statusreport": "Estado de la casa",
|
"statusreport": "Estado de la casa",
|
||||||
"element": "Elemento",
|
"element": "Elemento",
|
||||||
"state": "Estado",
|
"state": "Estado",
|
||||||
@@ -1070,6 +1088,23 @@
|
|||||||
"maxCredit": "Crédito máximo",
|
"maxCredit": "Crédito máximo",
|
||||||
"availableCredit": "Crédito disponible"
|
"availableCredit": "Crédito disponible"
|
||||||
},
|
},
|
||||||
|
"debtorsPrison": {
|
||||||
|
"titleWarning": "Mora crediticia",
|
||||||
|
"titlePrison": "Prisión por deudas",
|
||||||
|
"descriptionWarning": "Tus créditos están en mora. Si sigues sin pagar, te amenazan medidas forzosas.",
|
||||||
|
"descriptionPrison": "Estás en la prisión por deudas. Los nuevos créditos están bloqueados y tu patrimonio se liquidará gradualmente.",
|
||||||
|
"daysOverdue": "Días de retraso",
|
||||||
|
"creditworthiness": "Solvencia crediticia",
|
||||||
|
"nextForcedAction": "Siguiente medida forzosa",
|
||||||
|
"creditBlocked": "No puedes solicitar nuevos créditos mientras estés en la prisión por deudas.",
|
||||||
|
"creditError": "No se pudo solicitar el crédito.",
|
||||||
|
"actions": {
|
||||||
|
"reminder": "Primer aviso",
|
||||||
|
"final_warning": "Último aviso",
|
||||||
|
"debtors_prison": "Ingreso en prisión por deudas",
|
||||||
|
"asset_seizure": "Embargo de bienes"
|
||||||
|
}
|
||||||
|
},
|
||||||
"credits": {
|
"credits": {
|
||||||
"title": "Créditos",
|
"title": "Créditos",
|
||||||
"none": "Actualmente no tienes ningún crédito.",
|
"none": "Actualmente no tienes ningún crédito.",
|
||||||
|
|||||||
@@ -8,6 +8,18 @@
|
|||||||
|
|
||||||
<!-- OVERVIEW -->
|
<!-- OVERVIEW -->
|
||||||
<div v-if="activeTab === 'account'">
|
<div v-if="activeTab === 'account'">
|
||||||
|
<div v-if="debtorsPrison.active" class="debt-status" :class="{ 'is-prison': debtorsPrison.inDebtorsPrison }">
|
||||||
|
<h3>{{ debtStatusTitle }}</h3>
|
||||||
|
<p>{{ debtStatusDescription }}</p>
|
||||||
|
<div class="debt-status__meta">
|
||||||
|
<span>{{ $t('falukant.bank.debtorsPrison.daysOverdue') }}: <strong>{{ debtorsPrison.daysOverdue }}</strong></span>
|
||||||
|
<span>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}: <strong>{{ debtorsPrison.creditworthiness }}</strong></span>
|
||||||
|
<span v-if="debtorsPrison.nextForcedAction">
|
||||||
|
{{ $t('falukant.bank.debtorsPrison.nextForcedAction') }}:
|
||||||
|
<strong>{{ $t(`falukant.bank.debtorsPrison.actions.${debtorsPrison.nextForcedAction}`) }}</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="account-section">
|
<div class="account-section">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -26,6 +38,10 @@
|
|||||||
<td>{{ $t('falukant.bank.account.availableCredit') }}</td>
|
<td>{{ $t('falukant.bank.account.availableCredit') }}</td>
|
||||||
<td>{{ formatCost(bankOverview.availableCredit) }}</td>
|
<td>{{ formatCost(bankOverview.availableCredit) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</td>
|
||||||
|
<td>{{ bankOverview.creditworthiness }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,9 +93,12 @@
|
|||||||
<p>
|
<p>
|
||||||
<strong>{{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }}</strong>
|
<strong>{{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }}</strong>
|
||||||
</p>
|
</p>
|
||||||
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit">
|
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit || isCreditBlocked">
|
||||||
{{ $t('falukant.bank.credits.payoff.confirm') }}
|
{{ $t('falukant.bank.credits.payoff.confirm') }}
|
||||||
</button>
|
</button>
|
||||||
|
<p v-if="isCreditBlocked" class="payoff-hint payoff-hint--error">
|
||||||
|
{{ $t('falukant.bank.debtorsPrison.creditBlocked') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,6 +112,7 @@ import StatusBar from '@/components/falukant/StatusBar.vue';
|
|||||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
|
import { showError } from '@/utils/feedback.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BankView',
|
name: 'BankView',
|
||||||
@@ -118,34 +138,114 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['socket'])
|
...mapState(['socket', 'daemonSocket', 'user']),
|
||||||
|
debtorsPrison() {
|
||||||
|
return this.bankOverview.debtorsPrison || {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false,
|
||||||
|
daysOverdue: 0,
|
||||||
|
creditworthiness: 100,
|
||||||
|
nextForcedAction: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isCreditBlocked() {
|
||||||
|
return this.debtorsPrison.inDebtorsPrison;
|
||||||
|
},
|
||||||
|
debtStatusTitle() {
|
||||||
|
return this.debtorsPrison.inDebtorsPrison
|
||||||
|
? this.$t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: this.$t('falukant.bank.debtorsPrison.titleWarning');
|
||||||
|
},
|
||||||
|
debtStatusDescription() {
|
||||||
|
return this.debtorsPrison.inDebtorsPrison
|
||||||
|
? this.$t('falukant.bank.debtorsPrison.descriptionPrison')
|
||||||
|
: this.$t('falukant.bank.debtorsPrison.descriptionWarning');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
socket(newVal, oldVal) {
|
||||||
|
if (oldVal) this.teardownSocketEvents();
|
||||||
|
if (newVal) this.setupSocketEvents();
|
||||||
|
},
|
||||||
|
daemonSocket(newVal, oldVal) {
|
||||||
|
if (oldVal) this.teardownSocketEvents();
|
||||||
|
if (newVal) this.setupSocketEvents();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.loadBankOverview();
|
await this.loadBankOverview();
|
||||||
this.setupSocketEvents();
|
this.setupSocketEvents();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.socket) {
|
if (this._pendingRefresh) {
|
||||||
this.socket.off('falukantUpdateStatus', this.loadBankOverview);
|
clearTimeout(this._pendingRefresh);
|
||||||
|
this._pendingRefresh = null;
|
||||||
}
|
}
|
||||||
|
this.teardownSocketEvents();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
matchesCurrentUser(eventData) {
|
||||||
|
if (eventData?.user_id == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const currentIds = [this.user?.id, this.user?.hashedId]
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((value) => String(value));
|
||||||
|
return currentIds.includes(String(eventData.user_id));
|
||||||
|
},
|
||||||
setupSocketEvents() {
|
setupSocketEvents() {
|
||||||
|
this.teardownSocketEvents();
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on('falukantUpdateStatus', (data) => {
|
this._statusSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||||
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
this._familySocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||||
});
|
this._debtSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||||
} else {
|
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
setTimeout(() => this.setupSocketEvents(), 1000);
|
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
||||||
|
this.socket.on('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
|
}
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this._daemonHandler = (event) => this.handleDaemonMessage(event);
|
||||||
|
this.daemonSocket.addEventListener('message', this._daemonHandler);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
teardownSocketEvents() {
|
||||||
|
if (this.socket) {
|
||||||
|
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
||||||
|
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
||||||
|
if (this._debtSocketHandler) this.socket.off('falukantUpdateDebt', this._debtSocketHandler);
|
||||||
|
}
|
||||||
|
if (this.daemonSocket && this._daemonHandler) {
|
||||||
|
this.daemonSocket.removeEventListener('message', this._daemonHandler);
|
||||||
|
this._daemonHandler = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleEvent(eventData) {
|
handleEvent(eventData) {
|
||||||
|
if (!this.matchesCurrentUser(eventData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (eventData.event) {
|
switch (eventData.event) {
|
||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
this.loadBankOverview();
|
this.queueBankRefresh();
|
||||||
|
break;
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
|
this.queueBankRefresh();
|
||||||
|
break;
|
||||||
|
case 'falukantUpdateFamily':
|
||||||
|
if (['monthly', 'lover_installment'].includes(eventData.reason)) {
|
||||||
|
this.queueBankRefresh();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
queueBankRefresh() {
|
||||||
|
if (this._pendingRefresh) {
|
||||||
|
clearTimeout(this._pendingRefresh);
|
||||||
|
}
|
||||||
|
this._pendingRefresh = setTimeout(() => {
|
||||||
|
this._pendingRefresh = null;
|
||||||
|
this.loadBankOverview();
|
||||||
|
}, 120);
|
||||||
|
},
|
||||||
async loadBankOverview() {
|
async loadBankOverview() {
|
||||||
try {
|
try {
|
||||||
const { data } = await apiClient.get('/api/falukant/bank/overview');
|
const { data } = await apiClient.get('/api/falukant/bank/overview');
|
||||||
@@ -155,6 +255,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async confirmPayoff() {
|
async confirmPayoff() {
|
||||||
|
if (this.isCreditBlocked) return;
|
||||||
try {
|
try {
|
||||||
await apiClient.post('/api/falukant/bank/credits', {
|
await apiClient.post('/api/falukant/bank/credits', {
|
||||||
height: this.selectedCredit
|
height: this.selectedCredit
|
||||||
@@ -163,16 +264,17 @@ export default {
|
|||||||
this.selectedCredit = null;
|
this.selectedCredit = null;
|
||||||
this.activeTab = 'credits';
|
this.activeTab = 'credits';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
showError(err.response?.data?.error || this.$t('falukant.bank.debtorsPrison.creditError'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleDaemonMessage(msg) {
|
handleDaemonMessage(msg) {
|
||||||
try {
|
try {
|
||||||
if (['falukantUpdateStatus', 'moneyChange', 'creditChange'].includes(msg.event)) {
|
const data = JSON.parse(msg.data);
|
||||||
this.loadBankOverview();
|
if (['falukantUpdateStatus', 'falukantUpdateDebt'].includes(data.event)) {
|
||||||
|
this.handleEvent(data);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(evt, err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
feeRate() {
|
feeRate() {
|
||||||
@@ -190,4 +292,38 @@ export default {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
h2 { padding-top: 20px; }
|
h2 { padding-top: 20px; }
|
||||||
|
|
||||||
|
.debt-status {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 1rem 1.1rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.35);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 249, 238, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.debt-status.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.45);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.debt-status h3 {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debt-status__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payoff-hint {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payoff-hint--error {
|
||||||
|
color: #8b2f23;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,9 +15,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="debtorsPrison.active"
|
||||||
|
class="branch-debt-warning surface-card"
|
||||||
|
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</strong>
|
||||||
|
<p>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.branch.debtorsPrison.branchLocked')
|
||||||
|
: $t('falukant.branch.debtorsPrison.branchRisk') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<BranchSelection
|
<BranchSelection
|
||||||
:branches="branches"
|
:branches="branches"
|
||||||
:selectedBranch="selectedBranch"
|
:selectedBranch="selectedBranch"
|
||||||
|
:blocked="debtorsPrison.inDebtorsPrison"
|
||||||
|
:blocked-reason="debtorsPrison.inDebtorsPrison ? $t('falukant.branch.debtorsPrison.selectionBlocked') : ''"
|
||||||
@branchSelected="onBranchSelected"
|
@branchSelected="onBranchSelected"
|
||||||
@createBranch="createBranch"
|
@createBranch="createBranch"
|
||||||
@upgradeBranch="upgradeBranch"
|
@upgradeBranch="upgradeBranch"
|
||||||
@@ -404,6 +423,10 @@ export default {
|
|||||||
branchTaxesLoading: false,
|
branchTaxesLoading: false,
|
||||||
branchTaxesError: null,
|
branchTaxesError: null,
|
||||||
currentCertificate: null,
|
currentCertificate: null,
|
||||||
|
debtorsPrison: {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
},
|
||||||
pendingBranchRefresh: null,
|
pendingBranchRefresh: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -462,6 +485,7 @@ export default {
|
|||||||
// Live-Socket-Events (Backend Socket.io)
|
// Live-Socket-Events (Backend Socket.io)
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
|
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
|
||||||
|
this.socket.on('falukantUpdateDebt', (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data }));
|
||||||
this.socket.on('falukantUpdateProductionCertificate', (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data }));
|
this.socket.on('falukantUpdateProductionCertificate', (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data }));
|
||||||
this.socket.on('falukantBranchUpdate', (data) => this.handleEvent({ event: 'falukantBranchUpdate', ...data }));
|
this.socket.on('falukantBranchUpdate', (data) => this.handleEvent({ event: 'falukantBranchUpdate', ...data }));
|
||||||
this.socket.on('transport_arrived', (data) => this.handleEvent({ event: 'transport_arrived', ...data }));
|
this.socket.on('transport_arrived', (data) => this.handleEvent({ event: 'transport_arrived', ...data }));
|
||||||
@@ -482,6 +506,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.off('falukantUpdateStatus');
|
this.socket.off('falukantUpdateStatus');
|
||||||
|
this.socket.off('falukantUpdateDebt');
|
||||||
this.socket.off('falukantUpdateProductionCertificate');
|
this.socket.off('falukantUpdateProductionCertificate');
|
||||||
this.socket.off('falukantBranchUpdate');
|
this.socket.off('falukantBranchUpdate');
|
||||||
this.socket.off('transport_arrived');
|
this.socket.off('transport_arrived');
|
||||||
@@ -558,6 +583,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const result = await apiClient.get('/api/falukant/user');
|
const result = await apiClient.get('/api/falukant/user');
|
||||||
this.currentCertificate = result.data?.certificate ?? null;
|
this.currentCertificate = result.data?.certificate ?? null;
|
||||||
|
this.debtorsPrison = result.data?.debtorsPrison || {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading certificate:', error);
|
console.error('Error loading certificate:', error);
|
||||||
}
|
}
|
||||||
@@ -678,6 +707,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async createBranch() {
|
async createBranch() {
|
||||||
|
if (this.debtorsPrison.inDebtorsPrison) {
|
||||||
|
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this.loadBranches();
|
await this.loadBranches();
|
||||||
// Nach dem Anlegen eines neuen Branches automatisch den
|
// Nach dem Anlegen eines neuen Branches automatisch den
|
||||||
// zuletzt/neu erstellten Branch auswählen.
|
// zuletzt/neu erstellten Branch auswählen.
|
||||||
@@ -694,6 +727,10 @@ export default {
|
|||||||
|
|
||||||
async upgradeBranch() {
|
async upgradeBranch() {
|
||||||
if (!this.selectedBranch) return;
|
if (!this.selectedBranch) return;
|
||||||
|
if (this.debtorsPrison.inDebtorsPrison) {
|
||||||
|
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await apiClient.post('/api/falukant/branches/upgrade', {
|
await apiClient.post('/api/falukant/branches/upgrade', {
|
||||||
branchId: this.selectedBranch.id,
|
branchId: this.selectedBranch.id,
|
||||||
@@ -851,6 +888,7 @@ export default {
|
|||||||
this.$refs.productionSection?.loadStorage();
|
this.$refs.productionSection?.loadStorage();
|
||||||
break;
|
break;
|
||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
case 'falukantUpdateProductionCertificate':
|
case 'falukantUpdateProductionCertificate':
|
||||||
case 'falukantBranchUpdate':
|
case 'falukantBranchUpdate':
|
||||||
this.queueBranchRefresh();
|
this.queueBranchRefresh();
|
||||||
@@ -1184,6 +1222,23 @@ export default {
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.branch-debt-warning {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-debt-warning.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.4);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.branch-debt-warning p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.branch-hero__meta {
|
.branch-hero__meta {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="debtorsPrison.active"
|
||||||
|
class="family-debt-warning surface-card"
|
||||||
|
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</strong>
|
||||||
|
<p>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.family.debtorsPrison.familyImpact')
|
||||||
|
: $t('falukant.family.debtorsPrison.familyWarning') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="spouse-section">
|
<div class="spouse-section">
|
||||||
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
||||||
<div v-if="relationships.length > 0" class="relationship-container">
|
<div v-if="relationships.length > 0" class="relationship-container">
|
||||||
@@ -386,6 +403,10 @@ export default {
|
|||||||
householdTension: null,
|
householdTension: null,
|
||||||
householdTensionScore: null,
|
householdTensionScore: null,
|
||||||
householdTensionReasons: [],
|
householdTensionReasons: [],
|
||||||
|
debtorsPrison: {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
},
|
||||||
selectedChild: null,
|
selectedChild: null,
|
||||||
pendingFamilyRefresh: null
|
pendingFamilyRefresh: null
|
||||||
}
|
}
|
||||||
@@ -426,11 +447,13 @@ export default {
|
|||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this._falukantUpdateStatusHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
this._falukantUpdateStatusHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||||
this._falukantUpdateFamilyHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
this._falukantUpdateFamilyHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||||
|
this._falukantUpdateDebtHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||||
this._childrenUpdateHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
|
this._childrenUpdateHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
|
||||||
this._familyChangedHandler = (data) => this.handleEvent({ event: 'familychanged', ...data });
|
this._familyChangedHandler = (data) => this.handleEvent({ event: 'familychanged', ...data });
|
||||||
|
|
||||||
this.socket.on('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
this.socket.on('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
||||||
this.socket.on('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
this.socket.on('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
||||||
|
this.socket.on('falukantUpdateDebt', this._falukantUpdateDebtHandler);
|
||||||
this.socket.on('children_update', this._childrenUpdateHandler);
|
this.socket.on('children_update', this._childrenUpdateHandler);
|
||||||
this.socket.on('familychanged', this._familyChangedHandler);
|
this.socket.on('familychanged', this._familyChangedHandler);
|
||||||
} else {
|
} else {
|
||||||
@@ -441,6 +464,7 @@ export default {
|
|||||||
if (!this.socket) return;
|
if (!this.socket) return;
|
||||||
if (this._falukantUpdateStatusHandler) this.socket.off('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
if (this._falukantUpdateStatusHandler) this.socket.off('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
||||||
if (this._falukantUpdateFamilyHandler) this.socket.off('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
if (this._falukantUpdateFamilyHandler) this.socket.off('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
||||||
|
if (this._falukantUpdateDebtHandler) this.socket.off('falukantUpdateDebt', this._falukantUpdateDebtHandler);
|
||||||
if (this._childrenUpdateHandler) this.socket.off('children_update', this._childrenUpdateHandler);
|
if (this._childrenUpdateHandler) this.socket.off('children_update', this._childrenUpdateHandler);
|
||||||
if (this._familyChangedHandler) this.socket.off('familychanged', this._familyChangedHandler);
|
if (this._familyChangedHandler) this.socket.off('familychanged', this._familyChangedHandler);
|
||||||
},
|
},
|
||||||
@@ -454,6 +478,7 @@ export default {
|
|||||||
if ([
|
if ([
|
||||||
'falukantUpdateStatus',
|
'falukantUpdateStatus',
|
||||||
'falukantUpdateFamily',
|
'falukantUpdateFamily',
|
||||||
|
'falukantUpdateDebt',
|
||||||
'children_update',
|
'children_update',
|
||||||
'falukantUpdateChurch',
|
'falukantUpdateChurch',
|
||||||
'familychanged',
|
'familychanged',
|
||||||
@@ -502,6 +527,7 @@ export default {
|
|||||||
|
|
||||||
switch (eventData.event) {
|
switch (eventData.event) {
|
||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
case 'familychanged':
|
case 'familychanged':
|
||||||
this.queueFamilyRefresh({ reloadCharacter: true });
|
this.queueFamilyRefresh({ reloadCharacter: true });
|
||||||
break;
|
break;
|
||||||
@@ -533,6 +559,10 @@ export default {
|
|||||||
this.householdTension = response.data.householdTension;
|
this.householdTension = response.data.householdTension;
|
||||||
this.householdTensionScore = response.data.householdTensionScore;
|
this.householdTensionScore = response.data.householdTensionScore;
|
||||||
this.householdTensionReasons = response.data.householdTensionReasons || [];
|
this.householdTensionReasons = response.data.householdTensionReasons || [];
|
||||||
|
this.debtorsPrison = response.data.debtorsPrison || {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading family data:', error);
|
console.error('Error loading family data:', error);
|
||||||
}
|
}
|
||||||
@@ -874,6 +904,23 @@ export default {
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.family-debt-warning {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.family-debt-warning.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.4);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.family-debt-warning p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.marriage-overview {
|
.marriage-overview {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
|||||||
@@ -2,6 +2,22 @@
|
|||||||
<div class="house-view">
|
<div class="house-view">
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<h2>{{ $t('falukant.house.title') }}</h2>
|
<h2>{{ $t('falukant.house.title') }}</h2>
|
||||||
|
<section
|
||||||
|
v-if="debtorsPrison.active"
|
||||||
|
class="house-debt-warning surface-card"
|
||||||
|
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</strong>
|
||||||
|
<p>
|
||||||
|
{{ debtorsPrison.inDebtorsPrison
|
||||||
|
? $t('falukant.house.debtorsPrison.houseRisk')
|
||||||
|
: $t('falukant.house.debtorsPrison.houseWarning') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
<div class="existing-house">
|
<div class="existing-house">
|
||||||
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
|
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
|
||||||
<div class="status-panel surface-card">
|
<div class="status-panel surface-card">
|
||||||
@@ -127,7 +143,7 @@
|
|||||||
<div class="buyable-house-price">
|
<div class="buyable-house-price">
|
||||||
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
||||||
</div>
|
</div>
|
||||||
<button @click="buyHouse(house.id)">
|
<button @click="buyHouse(house.id)" :disabled="debtorsPrison.inDebtorsPrison">
|
||||||
{{ $t('falukant.house.buy') }}
|
{{ $t('falukant.house.buy') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,11 +177,15 @@ export default {
|
|||||||
servantPayLevel: 'normal',
|
servantPayLevel: 'normal',
|
||||||
servantPayOptions: ['low', 'normal', 'high'],
|
servantPayOptions: ['low', 'normal', 'high'],
|
||||||
buyableHouses: [],
|
buyableHouses: [],
|
||||||
currency: '€'
|
currency: '€',
|
||||||
|
debtorsPrison: {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['socket']),
|
...mapState(['socket', 'daemonSocket', 'user']),
|
||||||
allRenovated() {
|
allRenovated() {
|
||||||
return Object.values(this.status).every(v => v >= 100);
|
return Object.values(this.status).every(v => v >= 100);
|
||||||
}
|
}
|
||||||
@@ -176,6 +196,10 @@ export default {
|
|||||||
const userRes = await apiClient.get('/api/falukant/houses');
|
const userRes = await apiClient.get('/api/falukant/houses');
|
||||||
this.userHouse = userRes.data;
|
this.userHouse = userRes.data;
|
||||||
this.houseType = this.userHouse.houseType;
|
this.houseType = this.userHouse.houseType;
|
||||||
|
this.debtorsPrison = this.userHouse.debtorsPrison || {
|
||||||
|
active: false,
|
||||||
|
inDebtorsPrison: false
|
||||||
|
};
|
||||||
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
|
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
|
||||||
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
|
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
|
||||||
this.servantSummary = this.userHouse.servantSummary || this.servantSummary;
|
this.servantSummary = this.userHouse.servantSummary || this.servantSummary;
|
||||||
@@ -327,14 +351,27 @@ export default {
|
|||||||
handleDaemonMessage(evt) {
|
handleDaemonMessage(evt) {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(evt.data);
|
const msg = JSON.parse(evt.data);
|
||||||
if (msg.event === 'houseupdated') this.loadData();
|
if (!this.matchesCurrentUser(msg)) return;
|
||||||
|
if (['houseupdated', 'falukantUpdateStatus', 'falukantUpdateDebt', 'falukantHouseUpdate'].includes(msg.event)) this.loadData();
|
||||||
} catch { }
|
} catch { }
|
||||||
},
|
},
|
||||||
|
matchesCurrentUser(eventData) {
|
||||||
|
if (eventData?.user_id == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const currentIds = [this.user?.id, this.user?.hashedId]
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((value) => String(value));
|
||||||
|
return currentIds.includes(String(eventData.user_id));
|
||||||
|
},
|
||||||
setupSocketEvents() {
|
setupSocketEvents() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on('falukantHouseUpdate', (data) => {
|
this.socket.on('falukantHouseUpdate', (data) => {
|
||||||
this.handleEvent({ event: 'falukantHouseUpdate', ...data });
|
this.handleEvent({ event: 'falukantHouseUpdate', ...data });
|
||||||
});
|
});
|
||||||
|
this.socket.on('falukantUpdateDebt', (data) => {
|
||||||
|
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||||
|
});
|
||||||
this.socket.on('falukantUpdateStatus', (data) => {
|
this.socket.on('falukantUpdateStatus', (data) => {
|
||||||
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||||
});
|
});
|
||||||
@@ -345,6 +382,7 @@ export default {
|
|||||||
handleEvent(eventData) {
|
handleEvent(eventData) {
|
||||||
switch (eventData.event) {
|
switch (eventData.event) {
|
||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
case 'falukantHouseUpdate':
|
case 'falukantHouseUpdate':
|
||||||
this.loadData();
|
this.loadData();
|
||||||
break;
|
break;
|
||||||
@@ -354,10 +392,17 @@ export default {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await this.loadData();
|
await this.loadData();
|
||||||
this.setupSocketEvents();
|
this.setupSocketEvents();
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.off('falukantHouseUpdate', this.loadData);
|
this.socket.off('falukantHouseUpdate', this.loadData);
|
||||||
|
this.socket.off('falukantUpdateDebt', this.loadData);
|
||||||
this.socket.off('falukantUpdateStatus', this.loadData);
|
this.socket.off('falukantUpdateStatus', this.loadData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +432,22 @@ h2 {
|
|||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.house-debt-warning {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.house-debt-warning.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.4);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.house-debt-warning p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.existing-house {
|
.existing-house {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|||||||
@@ -9,7 +9,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div v-if="falukantUser?.character" class="imagecontainer">
|
<section
|
||||||
|
v-if="falukantUser?.debtorsPrison?.active"
|
||||||
|
class="falukant-debt-warning surface-card"
|
||||||
|
:class="{ 'is-prison': falukantUser?.debtorsPrison?.inDebtorsPrison }"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</strong>
|
||||||
|
<p>
|
||||||
|
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
||||||
|
? $t('falukant.bank.debtorsPrison.descriptionPrison')
|
||||||
|
: $t('falukant.bank.debtorsPrison.descriptionWarning') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div v-if="falukantUser?.character && !falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer">
|
||||||
<div :style="getAvatarStyle" class="avatar"></div>
|
<div :style="getAvatarStyle" class="avatar"></div>
|
||||||
<div class="house-with-character">
|
<div class="house-with-character">
|
||||||
<div :style="getHouseStyle" class="house"></div>
|
<div :style="getHouseStyle" class="house"></div>
|
||||||
@@ -23,6 +40,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="falukantUser?.character && falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer imagecontainer--prison">
|
||||||
|
<div class="debtors-prison-visual" aria-hidden="true">
|
||||||
|
<div class="debtors-prison-visual__moon"></div>
|
||||||
|
<div class="debtors-prison-visual__tower"></div>
|
||||||
|
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--left"></div>
|
||||||
|
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--right"></div>
|
||||||
|
<div class="debtors-prison-visual__ground"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section v-if="falukantUser?.character" class="falukant-summary-grid">
|
<section v-if="falukantUser?.character" class="falukant-summary-grid">
|
||||||
<article class="summary-card surface-card">
|
<article class="summary-card surface-card">
|
||||||
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
||||||
@@ -44,6 +71,15 @@
|
|||||||
<strong>{{ stockEntryCount }}</strong>
|
<strong>{{ stockEntryCount }}</strong>
|
||||||
<p>Verdichteter Blick auf Warenbestand über alle Regionen.</p>
|
<p>Verdichteter Blick auf Warenbestand über alle Regionen.</p>
|
||||||
</article>
|
</article>
|
||||||
|
<article v-if="falukantUser?.debtorsPrison?.active" class="summary-card surface-card">
|
||||||
|
<span class="summary-card__label">{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</span>
|
||||||
|
<strong>{{ falukantUser.debtorsPrison.creditworthiness }}</strong>
|
||||||
|
<p>
|
||||||
|
{{ falukantUser.debtorsPrison.nextForcedAction
|
||||||
|
? $t(`falukant.bank.debtorsPrison.actions.${falukantUser.debtorsPrison.nextForcedAction}`)
|
||||||
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-if="falukantUser?.character" class="falukant-routine-grid">
|
<section v-if="falukantUser?.character" class="falukant-routine-grid">
|
||||||
@@ -357,6 +393,7 @@ export default {
|
|||||||
this.socket.off("falukantUpdateStatus");
|
this.socket.off("falukantUpdateStatus");
|
||||||
this.socket.off("falukantUpdateFamily");
|
this.socket.off("falukantUpdateFamily");
|
||||||
this.socket.off("falukantUpdateChurch");
|
this.socket.off("falukantUpdateChurch");
|
||||||
|
this.socket.off("falukantUpdateDebt");
|
||||||
this.socket.off("falukantUpdateProductionCertificate");
|
this.socket.off("falukantUpdateProductionCertificate");
|
||||||
this.socket.off("children_update");
|
this.socket.off("children_update");
|
||||||
this.socket.off("falukantBranchUpdate");
|
this.socket.off("falukantBranchUpdate");
|
||||||
@@ -376,6 +413,9 @@ export default {
|
|||||||
this.socket.on("falukantUpdateChurch", (data) => {
|
this.socket.on("falukantUpdateChurch", (data) => {
|
||||||
this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
||||||
});
|
});
|
||||||
|
this.socket.on("falukantUpdateDebt", (data) => {
|
||||||
|
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||||
|
});
|
||||||
this.socket.on("falukantUpdateProductionCertificate", (data) => {
|
this.socket.on("falukantUpdateProductionCertificate", (data) => {
|
||||||
this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
||||||
});
|
});
|
||||||
@@ -446,6 +486,7 @@ export default {
|
|||||||
case 'falukantUpdateStatus':
|
case 'falukantUpdateStatus':
|
||||||
case 'falukantUpdateFamily':
|
case 'falukantUpdateFamily':
|
||||||
case 'falukantUpdateChurch':
|
case 'falukantUpdateChurch':
|
||||||
|
case 'falukantUpdateDebt':
|
||||||
case 'falukantUpdateProductionCertificate':
|
case 'falukantUpdateProductionCertificate':
|
||||||
case 'children_update':
|
case 'children_update':
|
||||||
case 'falukantBranchUpdate':
|
case 'falukantBranchUpdate':
|
||||||
@@ -579,6 +620,23 @@ export default {
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.falukant-debt-warning {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.falukant-debt-warning.is-prison {
|
||||||
|
border-color: rgba(146, 57, 40, 0.4);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||||
|
}
|
||||||
|
|
||||||
|
.falukant-debt-warning p {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.falukant-summary-grid,
|
.falukant-summary-grid,
|
||||||
.falukant-routine-grid {
|
.falukant-routine-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -694,6 +752,101 @@ export default {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.imagecontainer--prison {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 540px);
|
||||||
|
height: 320px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 22%, rgba(255, 233, 167, 0.95) 0, rgba(255, 233, 167, 0.95) 10%, rgba(255, 233, 167, 0) 11%),
|
||||||
|
linear-gradient(180deg, #273149 0%, #31476b 42%, #6d5953 42%, #6d5953 100%);
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__moon {
|
||||||
|
position: absolute;
|
||||||
|
top: 42px;
|
||||||
|
left: 72px;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 241, 194, 0.9);
|
||||||
|
box-shadow: 0 0 24px rgba(255, 241, 194, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__tower {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 54px;
|
||||||
|
width: 160px;
|
||||||
|
height: 190px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-radius: 18px 18px 10px 10px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #8c8a86 0%, #6f6a64 100%);
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(53, 49, 45, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__tower::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -26px;
|
||||||
|
left: 18px;
|
||||||
|
width: 124px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(90deg, #7d786f 0, #7d786f 18px, #646057 18px, #646057 26px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__tower::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
width: 42px;
|
||||||
|
height: 88px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-radius: 18px 18px 0 0;
|
||||||
|
background: #40362f;
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(17, 13, 11, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__bars {
|
||||||
|
position: absolute;
|
||||||
|
top: 116px;
|
||||||
|
width: 34px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(90deg, rgba(33, 31, 29, 0.85) 0, rgba(33, 31, 29, 0.85) 5px, transparent 5px, transparent 11px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__bars--left {
|
||||||
|
left: calc(50% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__bars--right {
|
||||||
|
right: calc(50% - 54px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debtors-prison-visual__ground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 64px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(72, 57, 51, 0.2), rgba(72, 57, 51, 0.42)),
|
||||||
|
repeating-linear-gradient(90deg, #756357 0, #756357 18px, #6d5a4f 18px, #6d5a4f 30px);
|
||||||
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
|
|||||||
Reference in New Issue
Block a user