Ein Wunschtag ohne Tauschpartner bedeutet jetzt: die Person übernimmt dort einen offenen Platz; ihr alter Platz wird beim Ausführen sofort fair nachbesetzt, ohne sie selbst. Rollen werden bei allen Tauschformen geprüft. Bearbeiter können Person und Wunschtag einer Anfrage nachtragen, etwa aus der Notiz. Der Anfragedialog zeigt, was am Wunschtag los ist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
5.9 KiB
JavaScript
108 lines
5.9 KiB
JavaScript
// Tauschanfragen: Betrachter melden, dass sie an einem Tag nicht können; Bearbeiter führen den Tausch aus.
|
||
import { isValidDate } from './scheduler.js';
|
||
|
||
export const REQUEST_STATUS = ['offen', 'erledigt', 'abgelehnt'];
|
||
|
||
/** Rolle einer Person an einem Tag: 'main', 'co' oder null. */
|
||
export function roleOn(assignment, personId) {
|
||
if (!assignment || assignment.off || !personId) return null;
|
||
if (assignment.main === personId) return 'main';
|
||
if (assignment.co === personId) return 'co';
|
||
return null;
|
||
}
|
||
|
||
/** Darf die Person diese Rolle übernehmen? */
|
||
export function canTake(person, role) {
|
||
if (!person || person.active === false) return false;
|
||
return role === 'main' ? person.canMain !== false : person.canCo !== false;
|
||
}
|
||
|
||
/** Offener Platz an einem Tag, den die Person übernehmen könnte: 'main', 'co' oder null. */
|
||
export function openSlotFor(assignment, person) {
|
||
if (!assignment || assignment.off || !person) return null;
|
||
if (assignment.main === person.id || assignment.co === person.id) return null;
|
||
if (!assignment.main && canTake(person, 'main')) return 'main';
|
||
if (!assignment.co && canTake(person, 'co')) return 'co';
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Eingabe einer Anfrage prüfen. persons: [{id, active}], assignments: { date: {main, co, off} }.
|
||
* Liefert { error } oder { request } (ohne id/status/createdAt).
|
||
*/
|
||
export function validateRequest(body, persons, assignments) {
|
||
const b = body || {};
|
||
const active = (id) => persons.find((p) => p.id === id && p.active !== false);
|
||
const personId = String(b.personId || '');
|
||
if (!active(personId)) return { error: 'Bitte wähle deinen Namen.' };
|
||
const date = String(b.date || '');
|
||
if (!isValidDate(date)) return { error: 'Ungültiger Tag.' };
|
||
const role = roleOn(assignments[date], personId);
|
||
if (!role) return { error: 'An diesem Tag bist du laut Plan nicht eingeteilt.' };
|
||
const partnerId = b.partnerId ? String(b.partnerId) : null;
|
||
if (partnerId && !active(partnerId)) return { error: 'Tauschpartner nicht gefunden.' };
|
||
if (partnerId === personId) return { error: 'Mit dir selbst kannst du nicht tauschen.' };
|
||
const partnerDate = b.partnerDate ? String(b.partnerDate) : null;
|
||
if (partnerDate && !isValidDate(partnerDate)) return { error: 'Ungültiger Wunschtag.' };
|
||
if (partnerDate === date) return { error: 'Der Wunschtag ist derselbe Tag.' };
|
||
if (partnerDate && !partnerId) {
|
||
// Ohne Tauschpartner heißt der Wunsch: einen noch offenen Platz am Wunschtag übernehmen
|
||
const me = active(personId);
|
||
const a = assignments[partnerDate];
|
||
if (!a || a.off) return { error: 'Am Wunschtag ist kein Meeting geplant.' };
|
||
if (a.main === personId || a.co === personId) return { error: 'Am Wunschtag bist du schon eingeteilt.' };
|
||
if (!openSlotFor(a, me)) return { error: 'Am Wunschtag ist kein Platz frei, den du übernehmen könntest. Nenne eine Person zum Tauschen.' };
|
||
}
|
||
const note = String(b.note || '').trim().slice(0, 300);
|
||
return { request: { personId, date, role, partnerId, partnerDate, note } };
|
||
}
|
||
|
||
/**
|
||
* Tausch ausführen. Liefert { changes: [{date, role, from, to}], vacated } und verändert assignments direkt.
|
||
* Fälle:
|
||
* - Partner + Wunschtag: über Kreuz – Partner übernimmt den Tag des Anfragenden, dieser den Slot des Partners am Wunschtag.
|
||
* - Nur Partner: Partner übernimmt den Slot des Anfragenden.
|
||
* - Nur Wunschtag: die Person übernimmt dort einen offenen Platz; ihr alter Platz wird frei (vacated) und
|
||
* sollte vom Aufrufer nachbesetzt werden.
|
||
* - Weder noch: nicht automatisch ausführbar.
|
||
* persons: [{ id, canMain, canCo, active }] für die Rollenprüfung.
|
||
*/
|
||
export function applySwap(assignments, req, persons = []) {
|
||
const find = (id) => persons.find((p) => p.id === id) || null;
|
||
const a = assignments[req.date];
|
||
const role = roleOn(a, req.personId);
|
||
if (!role) throw new Error('Die Person ist an diesem Tag nicht mehr eingeteilt. Bitte den Tag von Hand anpassen.');
|
||
const me = find(req.personId);
|
||
const changes = [];
|
||
|
||
if (!req.partnerId) {
|
||
if (!req.partnerDate) throw new Error('Ohne Wunschpartner oder Wunschtag bitte den Tag öffnen und selbst jemanden auswählen.');
|
||
const b = assignments[req.partnerDate];
|
||
const slot = openSlotFor(b, me || { id: req.personId, canMain: true, canCo: true });
|
||
if (!slot) throw new Error('Am Wunschtag ist kein passender Platz mehr frei. Bitte den Tag von Hand anpassen.');
|
||
b[slot] = req.personId;
|
||
changes.push({ date: req.partnerDate, role: slot, from: null, to: req.personId });
|
||
a[role] = null;
|
||
changes.push({ date: req.date, role, from: req.personId, to: null });
|
||
return { changes, vacated: { date: req.date, role } };
|
||
}
|
||
|
||
const partner = find(req.partnerId);
|
||
const otherRole = role === 'main' ? 'co' : 'main';
|
||
if (a[otherRole] === req.partnerId) throw new Error('Der Wunschpartner ist an diesem Tag schon die andere Moderation. Bitte den Tag von Hand anpassen.');
|
||
if (partner && !canTake(partner, role)) throw new Error(`Der Wunschpartner darf keine ${role === 'main' ? 'Haupt' : 'Neben'}moderation übernehmen. Bitte den Tag von Hand anpassen.`);
|
||
if (req.partnerDate) {
|
||
const b = assignments[req.partnerDate];
|
||
const partnerRole = roleOn(b, req.partnerId);
|
||
if (!partnerRole) throw new Error('Der Wunschpartner ist am Wunschtag nicht eingeteilt. Bitte den Tag von Hand anpassen.');
|
||
const otherB = partnerRole === 'main' ? 'co' : 'main';
|
||
if (b[otherB] === req.personId) throw new Error('Die anfragende Person ist am Wunschtag schon die andere Moderation. Bitte den Tag von Hand anpassen.');
|
||
if (me && !canTake(me, partnerRole)) throw new Error(`Die anfragende Person darf keine ${partnerRole === 'main' ? 'Haupt' : 'Neben'}moderation übernehmen. Bitte den Tag von Hand anpassen.`);
|
||
b[partnerRole] = req.personId;
|
||
changes.push({ date: req.partnerDate, role: partnerRole, from: req.partnerId, to: req.personId });
|
||
}
|
||
a[role] = req.partnerId;
|
||
changes.push({ date: req.date, role, from: req.personId, to: req.partnerId });
|
||
return { changes, vacated: null };
|
||
}
|