/// // Wiederkehrende Termine — vom wöchentlichen Stammtisch bis zur monatlichen // Feierabendrunde. // // Eine Serie beschreibt die Regel und erzeugt daraus echte Einzeltermine; // sie ist keine Wiederholungsregel, aus der Termine nur gerechnet werden. // Der Unterschied zählt, sobald jemand fragt „wer kommt am 12.?" oder // „diese Woche fällt aus": Beides braucht einen Datensatz je Termin, den man // absagen, verschieben und bespielen kann. // // Die Serie trägt dafür die Vorlage (Ort, Beschreibung, Schalter), aus der // neue Termine entstehen. Ändert sich die Vorlage, gilt das für künftige // Termine — bereits erzeugte bleiben, wie sie sind. migrate((app) => { const teams = app.findCollectionByNameOrId('teams') const users = app.findCollectionByNameOrId('users') const events = app.findCollectionByNameOrId('events') let series try { series = app.findCollectionByNameOrId('event_series') } catch { const admin = 'team.owner.id ?= @request.auth.id' + ' || team.admins.id ?= @request.auth.id' + ' || created_by.id ?= @request.auth.id' series = new Collection({ type: 'base', name: 'event_series', listRule: 'team.users.id ?= @request.auth.id', viewRule: 'team.users.id ?= @request.auth.id', createRule: 'team.users.id ?= @request.auth.id', updateRule: admin, deleteRule: admin, fields: [ { name: 'team', type: 'relation', required: true, maxSelect: 1, collectionId: teams.id, cascadeDelete: true, }, { name: 'name', type: 'text', required: true, max: 200 }, { name: 'location', type: 'text', max: 200 }, { name: 'description', type: 'text', max: 2000 }, { name: 'rule', type: 'select', maxSelect: 1, values: ['woechentlich', 'zweiwoechentlich', 'monatlich'], }, { name: 'starts', type: 'date' }, { name: 'until', type: 'date' }, { name: 'timing', type: 'bool' }, { name: 'participation', type: 'select', maxSelect: 1, values: ['offen', 'bestaetigung', 'geschlossen'], }, { name: 'created_by', type: 'relation', maxSelect: 1, collectionId: users.id, cascadeDelete: false, }, { name: 'created', type: 'autodate', onCreate: true, onUpdate: false }, { name: 'updated', type: 'autodate', onCreate: true, onUpdate: true }, ], }) app.save(series) } // cascadeDelete bleibt aus: Wer eine Serie beendet, will selten alle // gelaufenen Termine samt Zeiten und Teilnehmern mitlöschen. if (!events.fields.getByName('series')) { events.fields.add(new Field({ id: 'relation_event_series', name: 'series', type: 'relation', collectionId: series.id, cascadeDelete: false, maxSelect: 1, minSelect: 0, required: false, })) app.save(events) } }, (app) => { const events = app.findCollectionByNameOrId('events') events.fields.removeByName('series') app.save(events) app.delete(app.findCollectionByNameOrId('event_series')) })