Finanzen: Warnung vor Ablauf des Bankzugangs (T18) und Abruf-Jobs
checkBankConsent mailt bei 30, 14, 3 Tagen und am Ablauftag (Berliner Kalendertage), merkt sich die erreichten Stufen in warnedThresholds und holt eine nicht versandte Mail beim naechsten Lauf nach. Jobs nur mit Enable Banking: Abruf nach 2 Minuten und alle 6 Stunden, Pruefung nach 3 Minuten und taeglich. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cba14ed0f0
commit
7b35ed67a5
5 files changed
+375
-5
No files matched your search
@@ -1,7 +1,9 @@
|
||||
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'
|
||||
import { config } from '../../config.js'
|
||||
import { withLocks } from '../keyedLock.js'
|
||||
import { sendAdminNotice } from '../mailer.js'
|
||||
import { createSession, EnableBankingError, getBalances, getTransactions, startAuth, toCents } from './enableBanking.js'
|
||||
import { addDays, berlinDate } from './dates.js'
|
||||
import { addDays, berlinDate, daysBetween, formatDe } from './dates.js'
|
||||
import { deleteTransaction, getConnection, listPendingTransactions, saveConnection, upsertTransaction } from './bankStore.js'
|
||||
|
||||
/**
|
||||
@@ -11,7 +13,8 @@ import { deleteTransaction, getConnection, listPendingTransactions, saveConnecti
|
||||
* Bank nicht mehr nennt, werden geloescht. syncBank wirft nie: jeder Fehler landet in der Verbindung (lastError).
|
||||
*
|
||||
* Ausserdem der Weg zum neuen Bankzugang (startBankAuth, completeBankAuth): Freigabe bei Finom starten, nach der
|
||||
* Rueckkehr die Sitzung speichern und sofort einmal abrufen.
|
||||
* Rueckkehr die Sitzung speichern und sofort einmal abrufen, und die Warnung vor dem Ablauf des Zugangs
|
||||
* (checkBankConsent, Mail T18).
|
||||
*/
|
||||
|
||||
// Die Bank liefert hoechstens ~90 Tage: ein Abruf beginnt spaetestens heute - 89 Tage, sonst eine Woche vor der
|
||||
@@ -271,3 +274,67 @@ export async function completeBankAuth({ code, state, error } = {}, { now = new
|
||||
const sync = await syncBank({ now, psu })
|
||||
return { ok: true, validUntil, sync }
|
||||
}
|
||||
|
||||
// ---------- Warnung vor Ablauf des Bankzugangs ----------
|
||||
|
||||
// Warnstufen in Tagen bis zum Ablauf; 0 = am Ablauftag (und danach, falls die Mail zuvor nicht ankam)
|
||||
const WARN_THRESHOLDS = [30, 14, 3, 0]
|
||||
|
||||
const parseWarned = (value) =>
|
||||
String(value || '')
|
||||
.split(',')
|
||||
.map((part) => Number.parseInt(part, 10))
|
||||
.filter((n) => Number.isInteger(n))
|
||||
|
||||
/** Der Satzteil im Betreff: "läuft in 14 Tagen ab", "läuft morgen ab", "läuft heute ab", "ist abgelaufen". */
|
||||
function ablaufText({ expired, tage }) {
|
||||
if (expired) return 'ist abgelaufen'
|
||||
if (tage <= 0) return 'läuft heute ab'
|
||||
if (tage === 1) return 'läuft morgen ab'
|
||||
return `läuft in ${tage} Tagen ab`
|
||||
}
|
||||
|
||||
/**
|
||||
* Warnt das Team per Mail (T18), wenn der Bankzugang bald ablaeuft oder abgelaufen ist. tage = Berliner
|
||||
* Kalendertage von heute bis zum Ablauftag (0 am Ablauftag, solange der Zugang noch gilt; abgelaufen, sobald
|
||||
* validUntil vorbei ist). Warnstufen 30, 14, 3 und 0: erreicht ist eine Stufe, wenn tage <= Stufe; gemailt wird
|
||||
* die kleinste erreichte, falls sie noch nicht in warnedThresholds steht. Gespeichert werden danach alle
|
||||
* erreichten Stufen (sonst kaeme nach "14" am naechsten Tag noch "30"). Die Stufen stehen in der Verbindung
|
||||
* und werden bei einer Neuverbindung geleert (completeBankAuth). Kommt die Mail nicht raus, bleibt die Stufe
|
||||
* offen und der naechste Lauf versucht es erneut. Liefert { sent: Stufe } oder { sent: null }; wirft nur, wenn
|
||||
* Appwrite nicht erreichbar ist (der Job faengt das ab).
|
||||
*/
|
||||
export async function checkBankConsent({ now = new Date() } = {}) {
|
||||
const connection = await getConnection()
|
||||
const validUntilMs = Date.parse(connection?.validUntil ?? '')
|
||||
if (Number.isNaN(validUntilMs)) return { sent: null }
|
||||
|
||||
const ablaufTag = berlinDate(new Date(validUntilMs))
|
||||
const tage = daysBetween(berlinDate(now), ablaufTag)
|
||||
const expired = validUntilMs <= now.getTime()
|
||||
const reached = WARN_THRESHOLDS.filter((threshold) => tage <= threshold)
|
||||
if (!reached.length) return { sent: null }
|
||||
const threshold = Math.min(...reached)
|
||||
if (parseWarned(connection.warnedThresholds).includes(threshold)) return { sent: null }
|
||||
|
||||
const result = await sendAdminNotice({
|
||||
template: 'T18',
|
||||
values: {
|
||||
ablauf_text: ablaufText({ expired, tage }),
|
||||
gueltig_bis: formatDe(ablaufTag),
|
||||
link: `${config.womsBaseUrl}/finance/einstellungen`,
|
||||
},
|
||||
blocks: { abgelaufen: expired },
|
||||
})
|
||||
if (!result.sent) return { sent: null }
|
||||
|
||||
// Speichern unter der Sperre der Abrufe: eine Neuverbindung, die waehrend des Versands ihre Warnstufen leert
|
||||
// (completeBankAuth), wird nicht mit den Stufen der alten Frist ueberschrieben
|
||||
await withLocks(SYNC_LOCK, async () => {
|
||||
const current = await getConnection()
|
||||
if (!current || current.validUntil !== connection.validUntil) return
|
||||
const warned = new Set([...parseWarned(current.warnedThresholds), ...reached])
|
||||
await saveConnection({ warnedThresholds: [...warned].sort((a, b) => b - a).join(',') })
|
||||
})
|
||||
return { sent: threshold }
|
||||
}
|
||||
Reference in new issue
Block a user