import type { VertragArt, VertragStatus } from '@prisma/client' import { kannRueckkaufswertHaben } from '@/lib/vertraege/konstanten' import { RUECKKAUFSWERT_WIEDERVORLAGE_TAGE } from '@/lib/vertraege/rueckkaufswert' import { heuteAlsTag, tageZwischen } from '@/lib/fristen/datum' /** * Die Serienabfrage der Rueckkaufswerte - Auswahllogik. * * Rein und ohne Datenbank: Welche Vertraege kommen in Frage, welche sind * vorausgewaehlt, welche gesperrt? Das sind die Entscheidungen, die * hinterher niemand mehr nachvollziehen kann, wenn sie in einer * Datenbankabfrage stecken. Hier stehen sie einzeln und mit Begruendung. * * Der Anlass: Vor dem Vermoegensverzeichnis und vor jedem Jahresbericht * braucht es von jeder Lebens- und Rentenversicherung den Rueckkaufswert * zum Stichtag. Das sind jedes Mal dieselben fuenf bis zehn Briefe. */ export type SerienKandidat = { id: string bezeichnung: string gesellschaft: string | null art: VertragArt status: VertragStatus rueckkaufswertAngefragtAm: Date | null } export type KandidatBewertung = { vertrag: SerienKandidat /** Kommt der Vertrag ueberhaupt in Frage? */ moeglich: boolean /** Warum nicht? Steht in der Liste, statt den Vertrag stumm auszublenden. */ hindernis: string | null /** Vorausgewaehlt? Nur moegliche Vertraege ohne frische Anfrage. */ vorausgewaehlt: boolean /** Tage seit der letzten Anfrage, falls eine lief. */ tageSeitAnfrage: number | null } /** * Sperrfrist: Wer in den letzten 28 Tagen schon gefragt hat, fragt nicht * gleich noch einmal - die Antwort ist schlicht noch unterwegs. Dieselbe * Zahl wie die Wiedervorlage; ein Test haelt beide zusammen. */ export const SPERRFRIST_TAGE = RUECKKAUFSWERT_WIEDERVORLAGE_TAGE /** * Bewertet einen Vertrag fuer die Serienabfrage. * * Grundsatz: Nicht stumm ausblenden, sondern begruenden. Ein Vertrag, der * fehlt, wirft die Frage auf "und wo ist der?"; ein Vertrag mit dem * Vermerk "gekuendigt" beantwortet sie gleich mit. */ export function bewerten(vertrag: SerienKandidat, heute: Date = heuteAlsTag()): KandidatBewertung { const tageSeitAnfrage = vertrag.rueckkaufswertAngefragtAm ? tageZwischen(vertrag.rueckkaufswertAngefragtAm, heute) : null if (!kannRueckkaufswertHaben(vertrag.art)) { return { vertrag, moeglich: false, hindernis: 'Diese Vertragsart bildet keinen Rückkaufswert.', vorausgewaehlt: false, tageSeitAnfrage, } } // Verwertet oder abgelaufen: Da gibt es nichts mehr anzufragen. GEKUENDIGT // bleibt dagegen moeglich - bis zum Wirksamwerden der Kuendigung besteht // der Vertrag, und genau dann will der Sozialhilfetraeger den Wert sehen. if (vertrag.status === 'VERWERTET' || vertrag.status === 'ABGELAUFEN') { return { vertrag, moeglich: false, hindernis: vertrag.status === 'VERWERTET' ? 'Der Vertrag ist bereits verwertet.' : 'Der Vertrag ist abgelaufen.', vorausgewaehlt: false, tageSeitAnfrage, } } if (!vertrag.gesellschaft?.trim()) { return { vertrag, moeglich: false, hindernis: 'Ohne Gesellschaft fehlt der Empfänger — bitte am Vertrag nachtragen.', vorausgewaehlt: false, tageSeitAnfrage, } } // Frisch angefragt: moeglich, aber nicht vorausgewaehlt. Wer trotzdem // will (etwa weil der Brief verloren ging), waehlt von Hand an. if (tageSeitAnfrage !== null && tageSeitAnfrage < SPERRFRIST_TAGE) { return { vertrag, moeglich: true, hindernis: `Vor ${tageSeitAnfrage} Tag${tageSeitAnfrage === 1 ? '' : 'en'} bereits angefragt — die Antwort ist vermutlich noch unterwegs.`, vorausgewaehlt: false, tageSeitAnfrage, } } return { vertrag, moeglich: true, hindernis: null, vorausgewaehlt: true, tageSeitAnfrage } } export function kandidatenBewerten( vertraege: SerienKandidat[], heute: Date = heuteAlsTag(), ): KandidatBewertung[] { return vertraege .map((vertrag) => bewerten(vertrag, heute)) .sort((a, b) => { // Vorausgewaehlte zuerst, dann die moeglichen, dann der Rest - // jeweils alphabetisch. Was zu tun ist, steht oben. const rang = (k: KandidatBewertung) => (k.vorausgewaehlt ? 0 : k.moeglich ? 1 : 2) if (rang(a) !== rang(b)) return rang(a) - rang(b) return a.vertrag.bezeichnung.localeCompare(b.vertrag.bezeichnung, 'de') }) } /** * Wurde heute schon angefragt? Der Dublettenschutz fuer den zweiten Lauf. * * Bewusst tagesgenau und nicht "innerhalb der Sperrfrist": Wer denselben * Lauf versehentlich zweimal startet, soll keine doppelten Dokumente * bekommen. Wer nach einer Woche bewusst erneut anfragt (Brief verloren), * soll es duerfen. */ export function heuteSchonAngefragt( angefragtAm: Date | null, heute: Date = heuteAlsTag(), ): boolean { if (!angefragtAm) return false return tageZwischen(angefragtAm, heute) === 0 }