// 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 }; }