Add church management features: Implement endpoints for church overview, available positions, supervised applications, and application processing in FalukantService and FalukantController. Update router to include new routes for these functionalities, enhancing church-related operations.
This commit is contained in:
@@ -140,6 +140,17 @@ class FalukantController {
|
||||
const { characterId: childId, firstName } = req.body;
|
||||
return this.service.baptise(userId, childId, firstName);
|
||||
});
|
||||
this.getChurchOverview = this._wrapWithUser((userId) => this.service.getChurchOverview(userId));
|
||||
this.getAvailableChurchPositions = this._wrapWithUser((userId) => this.service.getAvailableChurchPositions(userId));
|
||||
this.getSupervisedApplications = this._wrapWithUser((userId) => this.service.getSupervisedApplications(userId));
|
||||
this.applyForChurchPosition = this._wrapWithUser((userId, req) => {
|
||||
const { officeTypeId, regionId } = req.body;
|
||||
return this.service.applyForChurchPosition(userId, officeTypeId, regionId);
|
||||
});
|
||||
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
|
||||
const { applicationId, decision } = req.body;
|
||||
return this.service.decideOnChurchApplication(userId, applicationId, decision);
|
||||
});
|
||||
|
||||
this.getEducation = this._wrapWithUser((userId) => this.service.getEducation(userId));
|
||||
this.sendToSchool = this._wrapWithUser((userId, req) => {
|
||||
|
||||
@@ -56,6 +56,11 @@ router.post('/party', falukantController.createParty);
|
||||
router.get('/party', falukantController.getParties);
|
||||
router.get('/family/notbaptised', falukantController.getNotBaptisedChildren);
|
||||
router.post('/church/baptise', falukantController.baptise);
|
||||
router.get('/church/overview', falukantController.getChurchOverview);
|
||||
router.get('/church/positions/available', falukantController.getAvailableChurchPositions);
|
||||
router.get('/church/applications/supervised', falukantController.getSupervisedApplications);
|
||||
router.post('/church/positions/apply', falukantController.applyForChurchPosition);
|
||||
router.post('/church/applications/decide', falukantController.decideOnChurchApplication);
|
||||
router.get('/education', falukantController.getEducation);
|
||||
router.post('/education', falukantController.sendToSchool);
|
||||
router.get('/bank/overview', falukantController.getBankOverview);
|
||||
|
||||
@@ -48,6 +48,9 @@ import Credit from '../models/falukant/data/credit.js';
|
||||
import TitleRequirement from '../models/falukant/type/title_requirement.js';
|
||||
import HealthActivity from '../models/falukant/log/health_activity.js';
|
||||
import Election from '../models/falukant/data/election.js';
|
||||
import ChurchOffice from '../models/falukant/data/church_office.js';
|
||||
import ChurchOfficeType from '../models/falukant/type/church_office_type.js';
|
||||
import ChurchApplication from '../models/falukant/data/church_application.js';
|
||||
import PoliticalOfficeType from '../models/falukant/type/political_office_type.js';
|
||||
import Candidate from '../models/falukant/data/candidate.js';
|
||||
import Vote from '../models/falukant/data/vote.js';
|
||||
@@ -4820,6 +4823,480 @@ class FalukantService extends BaseService {
|
||||
all: mapped
|
||||
};
|
||||
}
|
||||
|
||||
async getChurchOverview(hashedUserId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id', 'regionId']
|
||||
});
|
||||
if (!character) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Alle relevanten Regionen (Region + Eltern) laden
|
||||
const relevantRegionIds = await this.getRegionAndParentIds(character.regionId);
|
||||
|
||||
// Aktuell besetzte Kirchenämter in diesen Regionen laden
|
||||
const offices = await ChurchOffice.findAll({
|
||||
where: {
|
||||
regionId: {
|
||||
[Op.in]: relevantRegionIds
|
||||
}
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'type',
|
||||
attributes: ['name', 'hierarchyLevel']
|
||||
},
|
||||
{
|
||||
model: RegionData,
|
||||
as: 'region',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
attributes: ['id', 'gender'],
|
||||
include: [
|
||||
{
|
||||
model: FalukantPredefineFirstname,
|
||||
as: 'definedFirstName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantPredefineLastname,
|
||||
as: 'definedLastName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: TitleOfNobility,
|
||||
as: 'nobleTitle',
|
||||
attributes: ['labelTr']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'supervisor',
|
||||
attributes: ['id', 'gender'],
|
||||
include: [
|
||||
{
|
||||
model: FalukantPredefineFirstname,
|
||||
as: 'definedFirstName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantPredefineLastname,
|
||||
as: 'definedLastName',
|
||||
attributes: ['name']
|
||||
}
|
||||
],
|
||||
required: false
|
||||
}
|
||||
],
|
||||
order: [
|
||||
[{ model: ChurchOfficeType, as: 'type' }, 'hierarchyLevel', 'DESC'],
|
||||
[{ model: RegionData, as: 'region' }, 'name', 'ASC']
|
||||
]
|
||||
});
|
||||
|
||||
return offices.map(office => {
|
||||
const o = office.get({ plain: true });
|
||||
return {
|
||||
id: o.id,
|
||||
officeType: {
|
||||
name: o.type?.name
|
||||
},
|
||||
region: {
|
||||
name: o.region?.name
|
||||
},
|
||||
character: o.holder
|
||||
? {
|
||||
id: o.holder.id,
|
||||
name: `${o.holder.definedFirstName?.name || ''} ${o.holder.definedLastName?.name || ''}`.trim(),
|
||||
gender: o.holder.gender,
|
||||
title: o.holder.nobleTitle?.labelTr
|
||||
}
|
||||
: null,
|
||||
supervisor: o.supervisor
|
||||
? {
|
||||
id: o.supervisor.id,
|
||||
name: `${o.supervisor.definedFirstName?.name || ''} ${o.supervisor.definedLastName?.name || ''}`.trim()
|
||||
}
|
||||
: null
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getAvailableChurchPositions(hashedUserId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id', 'regionId']
|
||||
});
|
||||
if (!character) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Alle relevanten Regionen (Region + Eltern) laden
|
||||
const relevantRegionIds = await this.getRegionAndParentIds(character.regionId);
|
||||
|
||||
// Alle Kirchenamt-Typen laden
|
||||
const officeTypes = await ChurchOfficeType.findAll({
|
||||
order: [['hierarchyLevel', 'ASC']]
|
||||
});
|
||||
|
||||
const availablePositions = [];
|
||||
|
||||
for (const officeType of officeTypes) {
|
||||
// Finde den RegionType für diesen officeType
|
||||
const regionType = await RegionType.findOne({
|
||||
where: { labelTr: officeType.regionType }
|
||||
});
|
||||
if (!regionType) continue;
|
||||
|
||||
// Finde alle Regionen dieses Typs in den relevanten Regionen
|
||||
const regions = await RegionData.findAll({
|
||||
where: {
|
||||
id: { [Op.in]: relevantRegionIds },
|
||||
regionTypeId: regionType.id
|
||||
},
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
|
||||
for (const region of regions) {
|
||||
// Zähle besetzte Positionen dieses Typs in dieser Region
|
||||
const occupiedCount = await ChurchOffice.count({
|
||||
where: {
|
||||
officeTypeId: officeType.id,
|
||||
regionId: region.id
|
||||
}
|
||||
});
|
||||
|
||||
const availableSeats = officeType.seatsPerRegion - occupiedCount;
|
||||
|
||||
if (availableSeats > 0) {
|
||||
// Finde den Supervisor (höheres Amt in derselben Region oder Eltern-Region)
|
||||
let supervisor = null;
|
||||
const higherOfficeTypeIds = await ChurchOfficeType.findAll({
|
||||
where: {
|
||||
hierarchyLevel: { [Op.gt]: officeType.hierarchyLevel }
|
||||
},
|
||||
attributes: ['id']
|
||||
}).then(types => types.map(t => t.id));
|
||||
|
||||
if (higherOfficeTypeIds.length > 0) {
|
||||
const supervisorOffice = await ChurchOffice.findOne({
|
||||
where: {
|
||||
regionId: region.id,
|
||||
officeTypeId: { [Op.in]: higherOfficeTypeIds }
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'type',
|
||||
attributes: ['hierarchyLevel']
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
attributes: ['id']
|
||||
}
|
||||
],
|
||||
order: [
|
||||
[{ model: ChurchOfficeType, as: 'type' }, 'hierarchyLevel', 'ASC']
|
||||
],
|
||||
limit: 1
|
||||
});
|
||||
|
||||
if (supervisorOffice && supervisorOffice.holder) {
|
||||
supervisor = {
|
||||
id: supervisorOffice.holder.id,
|
||||
name: 'Supervisor' // Wird später geladen falls nötig
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (supervisorOffice && supervisorOffice.holder) {
|
||||
supervisor = {
|
||||
id: supervisorOffice.holder.id,
|
||||
name: 'Supervisor' // Wird später geladen falls nötig
|
||||
};
|
||||
}
|
||||
|
||||
availablePositions.push({
|
||||
id: officeType.id, // Verwende officeTypeId als ID für die Frontend-Identifikation
|
||||
officeType: {
|
||||
name: officeType.name
|
||||
},
|
||||
region: {
|
||||
name: region.name,
|
||||
id: region.id
|
||||
},
|
||||
regionId: region.id,
|
||||
availableSeats: availableSeats,
|
||||
supervisor: supervisor
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return availablePositions;
|
||||
}
|
||||
|
||||
async getSupervisedApplications(hashedUserId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
if (!character) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Finde alle Kirchenämter, die dieser Charakter hält
|
||||
const heldOffices = await ChurchOffice.findAll({
|
||||
where: { characterId: character.id },
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'type',
|
||||
attributes: ['id', 'hierarchyLevel']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (heldOffices.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Finde alle niedrigeren Ämter, die dieser Charakter superviden kann
|
||||
const maxHierarchyLevel = Math.max(...heldOffices.map(o => o.type.hierarchyLevel));
|
||||
const supervisedOfficeTypeIds = await ChurchOfficeType.findAll({
|
||||
where: {
|
||||
hierarchyLevel: { [Op.lt]: maxHierarchyLevel }
|
||||
},
|
||||
attributes: ['id']
|
||||
}).then(types => types.map(t => t.id));
|
||||
|
||||
// Finde alle Bewerbungen für diese Ämter, bei denen dieser Charakter Supervisor ist
|
||||
const applications = await ChurchApplication.findAll({
|
||||
where: {
|
||||
supervisorId: character.id,
|
||||
status: 'pending',
|
||||
officeTypeId: { [Op.in]: supervisedOfficeTypeIds }
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'officeType',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: RegionData,
|
||||
as: 'region',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'applicant',
|
||||
attributes: ['id', 'gender', 'age'],
|
||||
include: [
|
||||
{
|
||||
model: FalukantPredefineFirstname,
|
||||
as: 'definedFirstName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: FalukantPredefineLastname,
|
||||
as: 'definedLastName',
|
||||
attributes: ['name']
|
||||
},
|
||||
{
|
||||
model: TitleOfNobility,
|
||||
as: 'nobleTitle',
|
||||
attributes: ['labelTr']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return applications.map(app => {
|
||||
const a = app.get({ plain: true });
|
||||
return {
|
||||
id: a.id,
|
||||
officeType: {
|
||||
name: a.officeType?.name
|
||||
},
|
||||
region: {
|
||||
name: a.region?.name
|
||||
},
|
||||
applicant: {
|
||||
id: a.applicant.id,
|
||||
name: `${a.applicant.definedFirstName?.name || ''} ${a.applicant.definedLastName?.name || ''}`.trim(),
|
||||
gender: a.applicant.gender,
|
||||
age: a.applicant.age,
|
||||
title: a.applicant.nobleTitle?.labelTr
|
||||
},
|
||||
createdAt: a.createdAt
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async applyForChurchPosition(hashedUserId, officeTypeId, regionId) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id', 'regionId']
|
||||
});
|
||||
if (!character) {
|
||||
throw new Error('Character not found');
|
||||
}
|
||||
|
||||
// Prüfe ob Position verfügbar ist
|
||||
const officeType = await ChurchOfficeType.findByPk(officeTypeId);
|
||||
if (!officeType) {
|
||||
throw new Error('Office type not found');
|
||||
}
|
||||
|
||||
const occupiedCount = await ChurchOffice.count({
|
||||
where: {
|
||||
officeTypeId: officeTypeId,
|
||||
regionId: regionId
|
||||
}
|
||||
});
|
||||
|
||||
if (occupiedCount >= officeType.seatsPerRegion) {
|
||||
throw new Error('No available seats');
|
||||
}
|
||||
|
||||
// Finde Supervisor
|
||||
const higherOfficeTypeIds = await ChurchOfficeType.findAll({
|
||||
where: {
|
||||
hierarchyLevel: { [Op.gt]: officeType.hierarchyLevel }
|
||||
},
|
||||
attributes: ['id']
|
||||
}).then(types => types.map(t => t.id));
|
||||
|
||||
if (higherOfficeTypeIds.length === 0) {
|
||||
throw new Error('No supervisor office type found');
|
||||
}
|
||||
|
||||
const supervisorOffice = await ChurchOffice.findOne({
|
||||
where: {
|
||||
regionId: regionId,
|
||||
officeTypeId: { [Op.in]: higherOfficeTypeIds }
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'type',
|
||||
attributes: ['hierarchyLevel']
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
attributes: ['id']
|
||||
}
|
||||
],
|
||||
order: [
|
||||
[{ model: ChurchOfficeType, as: 'type' }, 'hierarchyLevel', 'ASC']
|
||||
],
|
||||
limit: 1
|
||||
});
|
||||
|
||||
if (!supervisorOffice || !supervisorOffice.holder) {
|
||||
throw new Error('No supervisor found');
|
||||
}
|
||||
|
||||
// Prüfe ob bereits eine Bewerbung existiert
|
||||
const existingApplication = await ChurchApplication.findOne({
|
||||
where: {
|
||||
characterId: character.id,
|
||||
officeTypeId: officeTypeId,
|
||||
regionId: regionId,
|
||||
status: 'pending'
|
||||
}
|
||||
});
|
||||
|
||||
if (existingApplication) {
|
||||
throw new Error('Application already exists');
|
||||
}
|
||||
|
||||
// Erstelle Bewerbung
|
||||
await ChurchApplication.create({
|
||||
officeTypeId: officeTypeId,
|
||||
characterId: character.id,
|
||||
regionId: regionId,
|
||||
supervisorId: supervisorOffice.holder.id,
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async decideOnChurchApplication(hashedUserId, applicationId, decision) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: user.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
if (!character) {
|
||||
throw new Error('Character not found');
|
||||
}
|
||||
|
||||
const application = await ChurchApplication.findOne({
|
||||
where: {
|
||||
id: applicationId,
|
||||
supervisorId: character.id,
|
||||
status: 'pending'
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ChurchOfficeType,
|
||||
as: 'officeType',
|
||||
attributes: ['id', 'seatsPerRegion']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!application) {
|
||||
throw new Error('Application not found or not authorized');
|
||||
}
|
||||
|
||||
if (decision === 'approve') {
|
||||
// Prüfe ob noch Platz verfügbar ist
|
||||
const occupiedCount = await ChurchOffice.count({
|
||||
where: {
|
||||
officeTypeId: application.officeTypeId,
|
||||
regionId: application.regionId
|
||||
}
|
||||
});
|
||||
|
||||
if (occupiedCount >= application.officeType.seatsPerRegion) {
|
||||
throw new Error('No available seats');
|
||||
}
|
||||
|
||||
// Erstelle Kirchenamt
|
||||
await ChurchOffice.create({
|
||||
officeTypeId: application.officeTypeId,
|
||||
characterId: application.characterId,
|
||||
regionId: application.regionId,
|
||||
supervisorId: application.supervisorId
|
||||
});
|
||||
}
|
||||
|
||||
// Aktualisiere Bewerbung
|
||||
application.status = decision === 'approve' ? 'approved' : 'rejected';
|
||||
application.decisionDate = new Date();
|
||||
await application.save();
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
export default new FalukantService();
|
||||
|
||||
Reference in New Issue
Block a user