feat(admin): implement pregnancy and birth management features
Some checks failed
Deploy to production / deploy (push) Failing after 2m6s

- Added new admin functionalities to force pregnancy, clear pregnancy, and trigger birth for characters.
- Introduced corresponding routes and controller methods in adminRouter and adminController.
- Enhanced the FalukantCharacter model to include pregnancy-related fields.
- Created database migration for adding pregnancy columns to the character table.
- Updated frontend views and internationalization files to support new pregnancy and birth management features.
- Improved user feedback and error handling for these new actions.
This commit is contained in:
Torsten Schulz (local)
2026-03-30 13:44:43 +02:00
parent b2591da428
commit c52d4b60f9
18 changed files with 628 additions and 160 deletions

View File

@@ -28,6 +28,7 @@ import Image from '../models/community/image.js';
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 { sequelize } from '../utils/sequelize.js';
import npcCreationJobService from './npcCreationJobService.js';
import { v4 as uuidv4 } from 'uuid';
@@ -669,7 +670,6 @@ class AdminService {
include: [{
model: FalukantCharacter,
as: 'character',
attributes: ['birthdate', 'health', 'title_of_nobility'],
include: [{
model: FalukantPredefineFirstname,
as: 'definedFirstName',
@@ -945,6 +945,145 @@ class AdminService {
await character.save();
}
/**
* Admin: Charakter als schwanger markieren (erwarteter Geburtstermin).
* @param {number} fatherCharacterId - optional; Vater-Charakter-ID
* @param {number} dueInDays - Tage bis zur „Geburt“ (Default 21)
*/
async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const mother = await FalukantCharacter.findByPk(characterId);
if (!mother) throw new Error('notfound');
if (fatherCharacterId != null) {
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
if (!father) throw new Error('fatherNotFound');
}
const days = Math.max(1, Math.min(365, Number(dueInDays) || 21));
const due = new Date();
due.setDate(due.getDate() + days);
await mother.update({
pregnancyDueAt: due,
pregnancyFatherCharacterId: fatherCharacterId != null ? Number(fatherCharacterId) : null,
});
const fu = mother.userId ? await FalukantUser.findByPk(mother.userId) : null;
if (fu) {
const u = await User.findByPk(fu.userId);
if (u?.hashedId) await notifyUser(u.hashedId, 'familychanged', {});
}
return {
success: true,
pregnancyDueAt: due.toISOString(),
pregnancyFatherCharacterId: fatherCharacterId != null ? Number(fatherCharacterId) : null,
};
}
async adminClearFalukantPregnancy(userId, characterId) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
const mother = await FalukantCharacter.findByPk(characterId);
if (!mother) throw new Error('notfound');
await mother.update({
pregnancyDueAt: null,
pregnancyFatherCharacterId: null,
});
const fu = mother.userId ? await FalukantUser.findByPk(mother.userId) : null;
if (fu) {
const u = await User.findByPk(fu.userId);
if (u?.hashedId) await notifyUser(u.hashedId, 'familychanged', {});
}
return { success: true };
}
/**
* Admin: Geburt auslösen Kind-Charakter + child_relation; setzt Schwangerschaft zurück.
*/
async adminForceFalukantBirth(userId, motherCharacterId, {
fatherCharacterId,
birthContext = 'marriage',
legitimacy = 'legitimate',
gender = null,
} = {}) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
throw new Error('noaccess');
}
if (fatherCharacterId == null || fatherCharacterId === '') {
throw new Error('fatherRequired');
}
const mother = await FalukantCharacter.findByPk(motherCharacterId);
if (!mother) throw new Error('notfound');
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
if (!father) throw new Error('fatherNotFound');
if (Number(fatherCharacterId) === Number(motherCharacterId)) {
throw new Error('invalidParents');
}
const ctx = ['marriage', 'lover'].includes(birthContext) ? birthContext : 'marriage';
const leg = ['legitimate', 'acknowledged_bastard', 'hidden_bastard'].includes(legitimacy)
? legitimacy
: 'legitimate';
const childGender = gender === 'male' || gender === 'female'
? gender
: (Math.random() < 0.5 ? 'male' : 'female');
const nobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
if (!nobility) throw new Error('titleNotFound');
const fnObj = await FalukantPredefineFirstname.findOne({
where: { gender: childGender },
order: Sequelize.fn('RANDOM'),
});
if (!fnObj) throw new Error('firstNameNotFound');
let childCharacterId;
await sequelize.transaction(async (t) => {
const baby = await FalukantCharacter.create({
userId: null,
regionId: mother.regionId,
firstName: fnObj.id,
lastName: mother.lastName,
gender: childGender,
birthdate: new Date(),
titleOfNobility: nobility.id,
health: 100,
moodId: 1,
}, { transaction: t });
childCharacterId = baby.id;
await ChildRelation.create({
fatherCharacterId: father.id,
motherCharacterId: mother.id,
childCharacterId: baby.id,
nameSet: false,
isHeir: false,
legitimacy: leg,
birthContext: ctx,
publicKnown: ctx === 'marriage',
}, { transaction: t });
await mother.update({
pregnancyDueAt: null,
pregnancyFatherCharacterId: null,
}, { transaction: t });
});
const notifyParent = async (char) => {
if (!char?.userId) return;
const fu = await FalukantUser.findByPk(char.userId);
if (!fu) return;
const u = await User.findByPk(fu.userId);
if (u?.hashedId) {
await notifyUser(u.hashedId, 'familychanged', {});
await notifyUser(u.hashedId, 'falukantUpdateStatus', {});
}
};
await notifyParent(mother);
await notifyParent(father);
return { success: true, childCharacterId, gender: childGender };
}
// --- User Administration ---
async searchUsers(requestingHashedUserId, query) {
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {