Anlegen, verschieben und lösen schreiben in den Kalender des Anbieters. Jeder Zugriff auf einen bestehenden Termin geht mit If-Match und dem zuletzt gelesenen Etag heraus; antwortet der Anbieter mit 412, gewinnt der Kalender: der Planer schreibt nicht, verwirft seinen Fingerabdruck und überlässt dem nächsten Abgleich, den fremden Stand zu holen. Der Rückweg läuft nach jedem Abgleich und vergleicht die eigenen Zeitblöcke mit dem aufgefrischten Spiegel. Wurde im Kalender verschoben, zieht der Planer nach. Wurde dort gelöscht, verliert der Zeitblock seine Verankerung und die Aufgabe fällt zurück auf offen, wenn nichts anderes mehr für sie geplant ist. Zwei Entscheidungen zum Fehlerfall: - Scheitert das Schreiben beim Anlegen, bleibt der Zeitblock trotzdem bestehen, mit vermerktem Fehler. Ihn zu verwerfen hieße, die Planung wegen eines Netzproblems zu vergessen; das Verschieben trägt ihn nach. - Scheitert das Entfernen, verschwindet der Zeitblock dennoch. Bliebe er stehen, weil der Kalender gerade nicht erreichbar ist, wäre die Planung blockiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import 'server-only'
|
|
import { abgleichEntscheiden, zeitraumHash } from '@/lib/zeitblock'
|
|
import { db } from '@/server/db'
|
|
|
|
/**
|
|
* Der Rückweg: Änderungen an Zeitblöcken aus dem Kalender übernehmen.
|
|
*
|
|
* Läuft nach jedem Abgleich eines Kontos. Für jeden Zeitblock wird verglichen,
|
|
* was im Kalender steht — der örtliche Spiegel (Tabelle `termin`) ist dafür
|
|
* bereits auf dem neuesten Stand.
|
|
*
|
|
* **Der Kalender gewinnt.** Wer dort etwas verschiebt, meint es so; der Planer
|
|
* zieht nach, statt zurückzuschreiben. Sonst zöge die App dem Benutzer den
|
|
* Termin unter den Händen weg.
|
|
*/
|
|
|
|
export type RueckwegBericht = {
|
|
uebernommen: number
|
|
geloest: number
|
|
}
|
|
|
|
export async function rueckwegAnwenden(
|
|
kalenderkontoId: string,
|
|
): Promise<RueckwegBericht> {
|
|
const zeitbloecke = await db.zeitblock.findMany({
|
|
where: { kalenderkontoId, extId: { not: null } },
|
|
select: {
|
|
id: true,
|
|
extId: true,
|
|
beginn: true,
|
|
ende: true,
|
|
hash: true,
|
|
aufgabeId: true,
|
|
},
|
|
})
|
|
if (zeitbloecke.length === 0) return { uebernommen: 0, geloest: 0 }
|
|
|
|
const extIds = zeitbloecke.map((z) => z.extId).filter((id): id is string => id !== null)
|
|
|
|
const termine = await db.termin.findMany({
|
|
where: { kalenderkontoId, extId: { in: extIds } },
|
|
select: { extId: true, beginn: true, ende: true, etag: true },
|
|
})
|
|
const nachExtId = new Map(termine.map((t) => [t.extId, t]))
|
|
|
|
let uebernommen = 0
|
|
let geloest = 0
|
|
|
|
for (const zeitblock of zeitbloecke) {
|
|
const imKalender = nachExtId.get(zeitblock.extId as string) ?? null
|
|
const entscheidung = abgleichEntscheiden(
|
|
{ hash: zeitblock.hash, beginn: zeitblock.beginn, ende: zeitblock.ende },
|
|
imKalender ? { beginn: imKalender.beginn, ende: imKalender.ende } : null,
|
|
)
|
|
|
|
if (entscheidung.tun === 'nichts') continue
|
|
|
|
if (entscheidung.tun === 'loesen') {
|
|
// Im Kalender gelöscht: der Zeitblock verliert seine Verankerung. Die
|
|
// Aufgabe bleibt bestehen und fällt zurück auf offen, wenn nichts
|
|
// anderes mehr für sie geplant ist.
|
|
await db.zeitblock.delete({ where: { id: zeitblock.id } })
|
|
geloest++
|
|
|
|
const nochGeplant = await db.zeitblock.count({
|
|
where: { aufgabeId: zeitblock.aufgabeId },
|
|
})
|
|
if (nochGeplant === 0) {
|
|
await db.aufgabe.updateMany({
|
|
where: { id: zeitblock.aufgabeId, status: 'GEPLANT' },
|
|
data: { status: 'OFFEN' },
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
|
|
await db.zeitblock.update({
|
|
where: { id: zeitblock.id },
|
|
data: {
|
|
beginn: entscheidung.beginn,
|
|
ende: entscheidung.ende,
|
|
// Den Fingerabdruck auf den übernommenen Stand setzen, damit die
|
|
// nächste Runde ihn wiedererkennt und nichts mehr tut.
|
|
hash: zeitraumHash(entscheidung.beginn, entscheidung.ende),
|
|
etag: imKalender?.etag ?? null,
|
|
letzterFehler: null,
|
|
},
|
|
})
|
|
uebernommen++
|
|
}
|
|
|
|
return { uebernommen, geloest }
|
|
}
|