diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index f0c331c..90aa6ec 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -98,6 +98,8 @@ class FalukantController { if (!result) throw { status: 404, message: 'No family data found' }; return result; }); + this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId)); + this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId)); this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId)); this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId)); this.cancelWooing = this._wrapWithUser(async (userId) => { diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index af7cf87..1623f4a 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -47,6 +47,8 @@ router.get('/dashboard-widget', falukantController.getDashboardWidget); router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); router.post('/family/cancel-wooing', falukantController.cancelWooing); router.post('/family/set-heir', falukantController.setHeir); +router.get('/heirs/potential', falukantController.getPotentialHeirs); +router.post('/heirs/select', falukantController.selectHeir); router.get('/family/gifts', falukantController.getGifts); router.get('/family/children', falukantController.getChildren); router.post('/family/gift', falukantController.sendGift); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index bbe04b6..77d4576 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2910,6 +2910,97 @@ class FalukantService extends BaseService { return { success: true, childCharacterId }; } + async getPotentialHeirs(hashedUserId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + if (!user) throw new Error('User not found'); + if (user.character?.id) return []; + + const noncivilTitle = await TitleOfNobility.findOne({ + where: { labelTr: 'noncivil' }, + attributes: ['id'] + }); + if (!noncivilTitle?.id) return []; + + const tenDaysAgo = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000); + const mainRegionId = user.mainBranchRegionId || null; + + const includes = [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] } + ]; + + const buildWhere = ({ withRegion = true, withYoungAge = true } = {}) => { + const where = { + userId: null, + titleOfNobility: noncivilTitle.id, + health: { [Op.gt]: 0 } + }; + if (withRegion && mainRegionId) where.regionId = mainRegionId; + if (withYoungAge) where.birthdate = { [Op.gte]: tenDaysAgo }; + return where; + }; + + const loadCandidates = async (where) => FalukantCharacter.findAll({ + where, + include: includes, + attributes: ['id', 'birthdate', 'gender'], + order: sequelize.random(), + limit: 10 + }); + + let candidates = await loadCandidates(buildWhere({ withRegion: true, withYoungAge: true })); + if (candidates.length === 0) { + candidates = await loadCandidates(buildWhere({ withRegion: true, withYoungAge: false })); + } + if (candidates.length === 0) { + candidates = await loadCandidates(buildWhere({ withRegion: false, withYoungAge: false })); + } + + return candidates.map(candidate => { + const plain = candidate.get({ plain: true }); + return { + ...plain, + age: plain.birthdate ? calcAge(plain.birthdate) : null + }; + }); + } + + async selectHeir(hashedUserId, heirId) { + const parsedHeirId = Number(heirId); + if (!Number.isInteger(parsedHeirId) || parsedHeirId < 1) { + throw { status: 400, message: 'Invalid heirId' }; + } + + const user = await this.getFalukantUserByHashedId(hashedUserId); + if (!user) throw new Error('User not found'); + if (user.character?.id) { + throw { status: 409, message: 'User already has an active character' }; + } + + const noncivilTitle = await TitleOfNobility.findOne({ + where: { labelTr: 'noncivil' }, + attributes: ['id'] + }); + if (!noncivilTitle?.id) throw new Error('Title "noncivil" not found'); + + const mainRegionId = user.mainBranchRegionId || null; + const where = { + id: parsedHeirId, + userId: null, + titleOfNobility: noncivilTitle.id, + health: { [Op.gt]: 0 } + }; + if (mainRegionId) where.regionId = mainRegionId; + + const candidate = await FalukantCharacter.findOne({ where, attributes: ['id'] }); + if (!candidate) { + throw { status: 404, message: 'Selected heir is not available' }; + } + + await candidate.update({ userId: user.id }); + return { success: true, heirId: candidate.id }; + } + async getPossiblePartners(requestingCharacterId) { const proposals = await MarriageProposal.findAll({ where: { diff --git a/frontend/src/views/falukant/OverviewView.vue b/frontend/src/views/falukant/OverviewView.vue index ea28aac..647de26 100644 --- a/frontend/src/views/falukant/OverviewView.vue +++ b/frontend/src/views/falukant/OverviewView.vue @@ -16,7 +16,7 @@
{{ $t(`falukant.titles.${heir.gender}.noncivil`) }} - {{ heir.definedFirstName.name }} {{ heir.definedLastName.name }} + {{ heir.definedFirstName?.name || '---' }} {{ heir.definedLastName?.name || '' }}
{{ $t('falukant.overview.metadata.age') }}: {{ heir.age }}
@@ -381,13 +381,6 @@ export default { return new Date(timestamp).toLocaleString(); }, async fetchPotentialHeirs() { - // Prüfe sowohl mainBranchRegion.id als auch mainBranchRegionId - const regionId = this.falukantUser?.mainBranchRegion?.id || this.falukantUser?.mainBranchRegionId; - if (!regionId) { - console.error('No main branch region found', this.falukantUser); - this.potentialHeirs = []; - return; - } this.loadingHeirs = true; try { const response = await apiClient.get('/api/falukant/heirs/potential');