feat(admin): add potential fathers retrieval for character management
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s

- Implemented a new method in AdminService to fetch potential fathers for a given character based on existing relationships.
- Updated AdminController to expose this functionality via a new API endpoint.
- Enhanced adminRouter to include the route for retrieving potential fathers.
- Modified frontend components to allow selection of potential fathers during pregnancy and birth management.
- Updated internationalization files to include new translation keys related to father selection.
This commit is contained in:
Torsten Schulz (local)
2026-03-31 08:50:56 +02:00
parent ee11a989a0
commit 9a78bc7c4b
30 changed files with 3907 additions and 45 deletions

View File

@@ -29,6 +29,9 @@ import EroticVideo from '../models/community/erotic_video.js';
import EroticContentReport from '../models/community/erotic_content_report.js';
import TitleOfNobility from "../models/falukant/type/title_of_nobility.js";
import ChildRelation from "../models/falukant/data/child_relation.js";
import Relationship from "../models/falukant/data/relationship.js";
import RelationshipType from "../models/falukant/type/relationship.js";
import RelationshipState from "../models/falukant/data/relationship_state.js";
import { sequelize } from '../utils/sequelize.js';
import npcCreationJobService from './npcCreationJobService.js';
import { v4 as uuidv4 } from 'uuid';
@@ -668,7 +671,7 @@ class AdminService {
required: true,
attributes: ['money', 'certificate', 'id'],
include: [{
model: FalukantCharacter,
model: FalukantCharacter.unscoped(),
as: 'character',
include: [{
model: FalukantPredefineFirstname,
@@ -945,10 +948,85 @@ class AdminService {
await character.save();
}
/**
* Ehepartner/Verlobte/Liebhaber eines Charakters (für Admin-Auswahl Vater).
*/
async adminGetPotentialFathersForCharacter(userId, motherCharacterId) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const mid = Number(motherCharacterId);
if (!Number.isFinite(mid)) {
throw new Error('invalidCharacter');
}
const mother = await FalukantCharacter.findByPk(mid, { attributes: ['id'] });
if (!mother) {
throw new Error('notfound');
}
const rels = await Relationship.findAll({
where: {
[Op.or]: [
{ character1Id: mid },
{ character2Id: mid }
]
},
include: [
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'] },
{ model: RelationshipState, as: 'state', required: false },
{
model: FalukantCharacter,
as: 'character1',
attributes: ['id'],
required: true,
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
},
{
model: FalukantCharacter,
as: 'character2',
attributes: ['id'],
required: true,
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
}
]
});
const allowedTypes = new Set(['married', 'engaged', 'lover']);
const typeOrder = { married: 0, engaged: 1, lover: 2 };
const seen = new Set();
const options = [];
for (const rel of rels) {
const typeTr = rel.relationshipType?.tr;
if (!typeTr || !allowedTypes.has(typeTr)) {
continue;
}
if (typeTr === 'lover' && rel.state && rel.state.active === false) {
continue;
}
const partnerId = rel.character1Id === mid ? rel.character2Id : rel.character1Id;
if (partnerId === mid || seen.has(partnerId)) {
continue;
}
seen.add(partnerId);
const partner = rel.character1Id === mid ? rel.character2 : rel.character1;
const displayName = partner?.definedFirstName?.name?.trim() || `Charakter #${partnerId}`;
options.push({
characterId: partnerId,
displayName,
relationshipType: typeTr
});
}
options.sort((a, b) => (typeOrder[a.relationshipType] ?? 9) - (typeOrder[b.relationshipType] ?? 9));
return { options };
}
/**
* Admin: Charakter als schwanger markieren (erwarteter Geburtstermin).
* @param {number} fatherCharacterId - optional; Vater-Charakter-ID
* @param {number} dueInDays - Tage bis zur „Geburt“ (Default 21)
* @param {number} dueInDays - Tage bis zum Termin (0 = heute; Default 21)
*/
async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
@@ -960,7 +1038,16 @@ class AdminService {
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
if (!father) throw new Error('fatherNotFound');
}
const days = Math.max(1, Math.min(365, Number(dueInDays) || 21));
let rawDays = dueInDays;
if (rawDays === undefined || rawDays === null || rawDays === '') {
rawDays = 21;
} else {
rawDays = Number(rawDays);
if (!Number.isFinite(rawDays)) {
rawDays = 21;
}
}
const days = Math.max(0, Math.min(365, rawDays));
const due = new Date();
due.setDate(due.getDate() + days);
await mother.update({