feat(falukant): add noble title level to election eligibility checks and improve election filtering logic
This commit is contained in:
@@ -960,7 +960,7 @@ class FalukantService extends BaseService {
|
|||||||
include: [
|
include: [
|
||||||
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||||
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id'] }
|
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr', 'id', 'level'] }
|
||||||
],
|
],
|
||||||
// reputation: für Adel-Voraussetzungen u.a. (checkReputationRequirement nutzt user.character ohne Nachladen)
|
// reputation: für Adel-Voraussetzungen u.a. (checkReputationRequirement nutzt user.character ohne Nachladen)
|
||||||
attributes: ['id', 'birthdate', 'gender', 'moodId', 'health', 'reputation']
|
attributes: ['id', 'birthdate', 'gender', 'moodId', 'health', 'reputation']
|
||||||
@@ -6916,9 +6916,20 @@ class FalukantService extends BaseService {
|
|||||||
|
|
||||||
async getElections(hashedUserId) {
|
async getElections(hashedUserId) {
|
||||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
if (!user || user.character.nobleTitle.labelTr === 'noncivil') {
|
const character = user?.character;
|
||||||
|
if (!character || Number(character.nobleTitle?.level || 0) < 2) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wahlberechtigung: Das Einstiegsamt (ID 1, assessor) wird von allen
|
||||||
|
// Bürgern oder höheren Ständen gewählt. Jedes weitere Amt wird nur
|
||||||
|
// von den aktuellen Inhabern des unmittelbar vorherigen Amttyps
|
||||||
|
// gewählt (z. B. Amt 2 von Inhabern von Amt 1).
|
||||||
|
const heldOffices = await PoliticalOffice.findAll({
|
||||||
|
where: { characterId: character.id },
|
||||||
|
attributes: ['officeTypeId']
|
||||||
|
});
|
||||||
|
const heldOfficeTypeIds = new Set(heldOffices.map((office) => Number(office.officeTypeId)));
|
||||||
const rows = await sequelize.query(
|
const rows = await sequelize.query(
|
||||||
FalukantService.RECURSIVE_REGION_SEARCH,
|
FalukantService.RECURSIVE_REGION_SEARCH,
|
||||||
{
|
{
|
||||||
@@ -6983,37 +6994,42 @@ class FalukantService extends BaseService {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
return rawElections.map(election => {
|
return rawElections
|
||||||
const e = election.get({ plain: true });
|
.filter((election) => {
|
||||||
|
const officeTypeId = Number(election.officeTypeId);
|
||||||
|
return officeTypeId === 1 || heldOfficeTypeIds.has(officeTypeId - 1);
|
||||||
|
})
|
||||||
|
.map(election => {
|
||||||
|
const e = election.get({ plain: true });
|
||||||
|
|
||||||
|
const voted = Array.isArray(e.votes) && e.votes.length > 0;
|
||||||
|
const reducedCandidates = (e.candidates || []).map(cand => {
|
||||||
|
const ch = cand.character || {};
|
||||||
|
const firstname = ch.definedFirstName?.name || '';
|
||||||
|
const lastname = ch.definedLastName?.name || '';
|
||||||
|
return {
|
||||||
|
id: cand.id,
|
||||||
|
title: ch.nobleTitle?.labelTr || null,
|
||||||
|
name: `${firstname} ${lastname}`.trim(),
|
||||||
|
age: calcAge(ch.birthdate),
|
||||||
|
gender: ch.gender
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const voted = Array.isArray(e.votes) && e.votes.length > 0;
|
|
||||||
const reducedCandidates = (e.candidates || []).map(cand => {
|
|
||||||
const ch = cand.character || {};
|
|
||||||
const firstname = ch.definedFirstName?.name || '';
|
|
||||||
const lastname = ch.definedLastName?.name || '';
|
|
||||||
return {
|
return {
|
||||||
id: cand.id,
|
id: e.id,
|
||||||
title: ch.nobleTitle?.labelTr || null,
|
officeType: { name: e.officeType.name },
|
||||||
name: `${firstname} ${lastname}`.trim(),
|
region: {
|
||||||
age: calcAge(ch.birthdate),
|
name: e.region.name,
|
||||||
gender: ch.gender
|
regionType: { labelTr: e.region.regionType.labelTr }
|
||||||
|
},
|
||||||
|
date: e.date,
|
||||||
|
postsToFill: e.postsToFill,
|
||||||
|
candidates: reducedCandidates,
|
||||||
|
voted: voted,
|
||||||
|
votedFor: voted ? e.votes.map(vote => { return vote.candidateId }) : null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
id: e.id,
|
|
||||||
officeType: { name: e.officeType.name },
|
|
||||||
region: {
|
|
||||||
name: e.region.name,
|
|
||||||
regionType: { labelTr: e.region.regionType.labelTr }
|
|
||||||
},
|
|
||||||
date: e.date,
|
|
||||||
postsToFill: e.postsToFill,
|
|
||||||
candidates: reducedCandidates,
|
|
||||||
voted: voted,
|
|
||||||
votedFor: voted ? e.votes.map(vote => { return vote.candidateId }) : null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async vote(hashedUserId, votes) {
|
async vote(hashedUserId, votes) {
|
||||||
|
|||||||
@@ -4650,13 +4650,25 @@ export default {
|
|||||||
.exercise-unlock-conditions li {
|
.exercise-unlock-conditions li {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 4px 0;
|
gap: var(--space-3);
|
||||||
|
padding: 7px 0;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
.exercise-unlock-conditions li:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
.exercise-unlock-conditions li span {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
.exercise-unlock-conditions li.ok {
|
.exercise-unlock-conditions li.ok {
|
||||||
color: var(--color-positive, #2a9d2b);
|
color: var(--color-success);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.exercise-unlock-conditions li.ok span {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
.language-assistant-panel {
|
.language-assistant-panel {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -5809,9 +5821,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.exercise-lock-note {
|
.exercise-lock-note {
|
||||||
background: #fff3cd;
|
padding-left: 16px;
|
||||||
border: 1px solid #ffc107;
|
background: var(--color-primary-soft);
|
||||||
color: #856404;
|
border: 1px solid var(--color-border-strong);
|
||||||
|
border-left: 4px solid var(--color-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-priority-note strong,
|
.review-priority-note strong,
|
||||||
@@ -5848,7 +5862,8 @@ export default {
|
|||||||
.vocab-trainer-stats {
|
.vocab-trainer-stats {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #f5f5f5;
|
background: var(--color-surface-accent);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5866,15 +5881,17 @@ export default {
|
|||||||
|
|
||||||
.success-rate {
|
.success-rate {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #28a745;
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-badge {
|
.mode-badge {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
background: #e9ecef;
|
background: var(--color-surface-strong);
|
||||||
color: #6c757d;
|
border: 1px solid var(--color-border);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-badge.mode-clickable {
|
.mode-badge.mode-clickable {
|
||||||
@@ -5882,7 +5899,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mode-badge.mode-clickable:hover {
|
.mode-badge.mode-clickable:hover {
|
||||||
filter: brightness(0.98);
|
background: var(--color-primary-soft);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-badge.mode-clickable:focus-visible,
|
||||||
|
.btn-stop-trainer:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-badge.mode-active {
|
.mode-badge.mode-active {
|
||||||
@@ -5892,14 +5917,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mode-badge.mode-completed {
|
.mode-badge.mode-completed {
|
||||||
background: #28a745;
|
background: var(--color-success);
|
||||||
color: white;
|
border-color: var(--color-success);
|
||||||
|
color: var(--color-text-on-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-stop-trainer {
|
.btn-stop-trainer {
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
background: rgba(177, 59, 53, 0.92);
|
background: var(--color-danger);
|
||||||
color: white;
|
color: var(--color-text-on-accent);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -5907,7 +5933,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-stop-trainer:hover {
|
.btn-stop-trainer:hover {
|
||||||
background: var(--color-danger-hover);
|
background: var(--color-danger-hover, #96312c);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vocab-question {
|
.vocab-question {
|
||||||
|
|||||||
Reference in New Issue
Block a user