Files
Webklar-Kundenbereich/server/services/unterkunft/abschaltHinweis.js
T

81 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { config } from '../../config.js'
import { getDocument, listAllDocuments, Query, updateDocument } from '../appwriteAdmin.js'
import { sendCustomerMail } from '../mailer.js'
import { customerOf } from '../siteFeatures.js'
import { datumKurz, heute } from '../termine/zeit.js'
import { hatUkDaten } from './aufbewahrung.js'
/**
* Hinweis-Mail K35 vor der Loeschung: Sobald der Tagesjob einen abgeschalteten Buchungskalender mit Daten erstmals sieht, geht EINMAL
* K35 an die E-Mail des Kundenkontos (Loeschdatum = disabledAt + loeschTageNachAbschalten Tage, Rechnungen bis dahin im Portal abrufbar).
* Marker in portalFeatures.adminNote: "K35 am YYYY-MM-DD" (gilt nur, wenn nicht vor disabledAt; ein spaeteres Abschalten setzt disabledAt neu
* und loest den Hinweis erneut aus). Scheitert der Versand, bleibt der Marker aus und der naechste Lauf wiederholt ihn. Ein Kunde ohne Adresse
* (z. B. Kontoloeschung) bekommt den Marker ohne Mail. Ist das Loeschdatum schon erreicht, geht keine Mail mehr raus (die Loeschung laeuft im
* selben Lauf). Logs ohne Personendaten.
*/
const TAG_MS = 24 * 60 * 60 * 1000
const MARKER_RE = /K35 am (\d{4}-\d{2}-\d{2})/
const zeitMs = (v) => {
const t = Date.parse(v)
return Number.isNaN(t) ? NaN : t
}
function markiere(notiz, tag) {
const marker = `K35 am ${tag}`
const alt = String(notiz ?? '').replace(/\s*(?:[-–|]\s*)?K35 am \d{4}-\d{2}-\d{2}/g, '').trim()
if (!alt) return marker
return `${alt.slice(0, Math.max(0, 300 - marker.length - 3))} – ${marker}`
}
/** @returns {Promise<{ geprueft: number, gesendet: number, ohneAdresse: number, fehler: number }>} */
export async function sendeHinweiseVorLoeschung(now = new Date()) {
const nowMs = now instanceof Date ? now.getTime() : Number(now)
const tage = config.unterkunft.loeschTageNachAbschalten ?? 90
const FEATURES = config.collections.portalFeatures
const erg = { geprueft: 0, gesendet: 0, ohneAdresse: 0, fehler: 0 }
const funktionen = await listAllDocuments(FEATURES, [Query.equal('featureKey', 'unterkunft'), Query.equal('state', 'disabled')])
for (const f of funktionen) {
const abgeschaltet = zeitMs(f.disabledAt)
if (!Number.isFinite(abgeschaltet)) continue
const m = MARKER_RE.exec(String(f.adminNote ?? ''))
if (m && m[1] >= String(f.disabledAt).slice(0, 10)) continue
const loeschAm = abgeschaltet + tage * TAG_MS
if (loeschAm <= nowMs) continue
erg.geprueft += 1
try {
if (!(await hatUkDaten(f.$id))) continue
const kunde = f.customerId ? await customerOf(f.customerId).catch(() => null) : null
const an = typeof kunde?.email === 'string' ? kunde.email.trim() : ''
if (an) {
let projekt = ''
try {
const p = await getDocument(config.collections.websiteProjects, f.projectId)
projekt = p?.projectName || p?.subdomain || ''
} catch {
// ohne Projektname: die Vorlage nennt dann keinen
}
await sendCustomerMail(an, {
template: 'K35',
values: {
kunde_name: typeof kunde.name === 'string' ? kunde.name.trim() : '',
projekt,
loeschdatum: datumKurz(new Date(loeschAm)),
portal_url: `${config.publicBaseUrl}/dashboard.html#buchungen?projekt=${encodeURIComponent(f.projectId ?? '')}`,
},
blocks: {},
bccTeam: true,
})
erg.gesendet += 1
} else {
erg.ohneAdresse += 1
}
await updateDocument(FEATURES, f.$id, { adminNote: markiere(f.adminNote, heute(new Date(nowMs))) })
} catch (err) {
erg.fehler += 1
console.warn(`[unterkunft-aufbewahrung] K35 für Funktion ${f.$id} nicht gesendet: ${[err?.name, err?.status].filter(Boolean).join(' ')}`)
}
}
return erg
}