81 lines
3.7 KiB
JavaScript
81 lines
3.7 KiB
JavaScript
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
|
||
}
|