Kundenansicht neu E-Fix F5: Fassung 6 ohne PDF behauptet keinen Anhang, das Team bekommt den Hinweis

Die PDFs der Fassung 6 entstehen erst nach der Freigabe. Bis dahin gingen K01, K12 und K13 ohne Anhang raus, sagten aber "im Anhang" bzw.
"beigefügt", und das Team erfuhr es nicht (nur K38/T24 hatten dafür den Block anhang_fehlt).

- Neuer Helfer vertragsAnhang.js: lädt das PDF der zugestimmten Fassung und meldet fehlt nur bei Fassung 6 ohne lesbare Datei.
- K01, K12, K13: Block anhang (Anhangsaussagen) und ohne_anhang (Satz, dass die Unterlagen gesondert kommen).
- T02, T12, T13: Block anhang_fehlt "Unterlagen von Hand nachsenden" (T12 auch beim späteren Wirksamwerden, T13 auch ohne Aufgabe).
- Alte Fassungen unverändert: Hash-Pins auf die Wiedergabe der Vorlagen vor der Änderung, Anhang und Texte wie bisher.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 21:38:54 +00:00
1 parent 9c04f806eb
commit 72b5ac5265
12 files changed
+362 -66

No files matched your search

+16 -19
View File
@@ -1,9 +1,9 @@
import { readFile } from 'node:fs/promises'
import { config } from '../config.js'
import { getDocument, updateDocument } from './appwriteAdmin.js'
import { listContractEvents, recordContractEvent, updateContractEvent } from './contractEvents.js'
import { sendAdminNotice, sendCustomerMail } from './mailer.js'
import { contractAttachmentFor, describeItems, priceIdsOfInvoice, regularStorageLine } from './products.js'
import { describeItems, priceIdsOfInvoice, regularStorageLine } from './products.js'
import { ladeVertragsAnhang } from './vertragsAnhang.js'
import {
ACTIVE_STATUSES,
ensurePrice,
@@ -315,16 +315,6 @@ async function supersedeStorageTasks(events, change) {
for (const e of superseded) await updateContractEvent(e.$id, { status: 'erledigt', doneAt: now, doneBy: 'ersetzt' })
}
/** Vertragsunterlagen (PDF) in der zugestimmten Fassung als Mail-Anhang; ohne Datei geht die Mail ohne Anhang raus. */
async function contractPdfAttachment(termsVersion) {
const { filename, path } = contractAttachmentFor(termsVersion)
const content = await readFile(path).catch((err) => {
console.error(`[speicher] Vertragsunterlagen ${filename} nicht lesbar:`, err.message)
return null
})
return content ? [{ filename, content, contentType: 'application/pdf' }] : []
}
/**
* K13 an den Kunden (Plus: mit PDF der zugestimmten Fassung, F22) und T13 ans Team (Aufgabe nach Art, dazu
* die Bloecke teilweise und nachweis_fehlt). Ueber das Kuendigungsformular bestaetigt K03, K13 entfaellt dort
@@ -335,8 +325,13 @@ async function sendStorageMails({ doc, change, consent, viaCancellation, proofMi
const customer = await getDocument(config.collections.customers, doc.customerId).catch(() => null)
const email = customer?.email || doc.email || ''
const dueLabel = fmtDate(change.dueAt)
let anhangFehlt = false
if (!viaCancellation && email) {
const attachments = plus ? await contractPdfAttachment(consent.termsVersion) : []
// Plus: Vertragsunterlagen der zugestimmten Fassung. Fehlt das PDF der Fassung 6, geht K13 ohne Anhang und ohne die Behauptung "beigefügt"
// raus (Block ohne_anhang), T13 sagt es dem Team (anhang_fehlt, F5); ältere Fassungen wie bisher
const unterlagen = plus ? await ladeVertragsAnhang(consent.termsVersion, 'speicher') : { attachments: [], fehlt: false }
anhangFehlt = unterlagen.fehlt
const attachments = unterlagen.attachments
await sendCustomerMail(email, {
template: 'K13',
values: {
@@ -348,16 +343,17 @@ async function sendStorageMails({ doc, change, consent, viaCancellation, proofMi
},
// plus: Einrichtung mit anteiliger Berechnung; plus_bezahlt: bis zur schon bezahlten Menge (keine Kosten);
// bestellung: Unterlagen und Widerruf bei beiden; minus: Verringerung zum Periodenende
blocks: { plus: plus && change.kind === 'einrichten', plus_bezahlt: plus && change.kind !== 'einrichten', bestellung: plus, minus: !plus },
blocks: { plus: plus && change.kind === 'einrichten', plus_bezahlt: plus && change.kind !== 'einrichten', bestellung: plus, minus: !plus, anhang: !anhangFehlt, ohne_anhang: anhangFehlt },
attachments,
}).catch((e) => console.error(`[speicher] K13 an ${email} fehlgeschlagen:`, e.message))
}
// Ohne Auftrag fuers Team (Plus genau zurueck auf die bezahlte Menge) kein T13 - es gibt nichts zu tun
if (!change.task && !proofMissing) return
// Ohne Auftrag fuers Team (Plus genau zurueck auf die bezahlte Menge) kein T13 - es gibt nichts zu tun; ausser die Unterlagen muessen nachgesendet werden
if (!change.task && !proofMissing && !anhangFehlt) return
const nurUnterlagen = !change.task && !proofMissing
await sendAdminNotice({
template: 'T13',
values: {
aktion: change.kind === 'einrichten' ? 'einrichten' : `reduzieren zum ${dueLabel}`,
aktion: nurUnterlagen ? 'Unterlagen nachsenden' : change.kind === 'einrichten' ? 'einrichten' : `reduzieren zum ${dueLabel}`,
email,
name: customer?.name || '',
stripe_abo_id: doc.$id,
@@ -368,10 +364,11 @@ async function sendStorageMails({ doc, change, consent, viaCancellation, proofMi
faellig: dueLabel,
},
blocks: {
einrichten: change.kind === 'einrichten',
periodenende: change.kind === 'periodenende',
einrichten: !nurUnterlagen && change.kind === 'einrichten',
periodenende: !nurUnterlagen && change.kind === 'periodenende',
teilweise: change.partial,
nachweis_fehlt: proofMissing,
anhang_fehlt: anhangFehlt,
},
})
}
+15 -19
View File
@@ -1,9 +1,9 @@
import { readFile } from 'node:fs/promises'
import { config } from '../config.js'
import { getDocument } from './appwriteAdmin.js'
import { eventIdFor, getContractEvent, listContractEvents, recordContractEvent, updateContractEvent } from './contractEvents.js'
import { sendAdminNotice, sendCustomerMail } from './mailer.js'
import { contractAttachmentFor, describeItems } from './products.js'
import { describeItems } from './products.js'
import { anhangFehlt, ladeVertragsAnhang } from './vertragsAnhang.js'
import { getFeatureDoc } from './siteFeatures.js'
import {
addMonths,
@@ -276,23 +276,17 @@ async function previewFor(ctx, prorationDate) {
}
}
/** Vertragsunterlagen (PDF) in der zugestimmten Fassung; ohne Datei geht K12 ohne Anhang raus (Fehler im Log). */
async function contractPdfAttachment(termsVersion) {
const { filename, path } = contractAttachmentFor(termsVersion)
const content = await readFile(path).catch((err) => {
console.error(`[wechsel] Vertragsunterlagen ${filename} nicht lesbar:`, err.message)
return null
})
return content ? [{ filename, content, contentType: 'application/pdf' }] : []
}
/** K12 an den Kunden: Vertragsbestaetigung mit PDF. Ein Mailfehler bricht den Wechsel nicht ab. */
/**
* K12 an den Kunden: Vertragsbestaetigung mit PDF der zugestimmten Fassung. Ein Mailfehler bricht den Wechsel nicht ab.
* Fehlt das PDF der Fassung 6, geht K12 ohne Anhang und ohne die Behauptung "im Anhang" raus (Block ohne_anhang, F5); die Rueckgabe
* { anhangFehlt } sagt es dem Aufrufer fuer T12 (Block anhang_fehlt). Ohne Datei der alten Fassungen bleibt es beim bisherigen Verhalten.
*/
async function sendSwitchConfirmation(contact, consent, facts, blocks) {
if (!contact.email) {
console.error('[wechsel] K12 nicht verschickt: keine E-Mail-Adresse zum Kunden')
return
return { anhangFehlt: false }
}
const attachments = await contractPdfAttachment(consent.termsVersion)
const { attachments, fehlt } = await ladeVertragsAnhang(consent.termsVersion, 'wechsel')
await sendCustomerMail(contact.email, {
template: 'K12',
values: {
@@ -304,9 +298,10 @@ async function sendSwitchConfirmation(contact, consent, facts, blocks) {
bedingungen_version: consent.termsVersion,
zustimmung_zeitpunkt: fmtDateTime(consent.consentAt),
},
blocks,
blocks: { ...blocks, anhang: !fehlt, ohne_anhang: fehlt },
attachments,
}).catch((err) => console.error(`[wechsel] K12 an ${contact.email} fehlgeschlagen:`, err.message))
return { anhangFehlt: fehlt }
}
/** T12 ans Team (sendAdminNotice protokolliert Fehler selbst und wirft nie). */
@@ -468,10 +463,10 @@ async function performSwitch(customerId, current, consent, prorationDate, expect
const facts = { switchedAt, amountDueCents, monthlyCents }
const lastschrift = sepa && effective && !paid
// K12 sofort; zahlung_offen nur bei zurueckgehaltener Aenderung (Karte), lastschrift bei SEPA (F37); sofortverlangen nur mit Zeitstempel (kein Betrieb in Fassung 6)
await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift, zusatzspeicher: ctx.storageUnits > 0, sofortverlangen: Boolean(consent.earlyStartConsentAt) })
const { anhangFehlt: ohneAnhang } = await sendSwitchConfirmation(contact, consent, facts, { zahlung_offen: pending, lastschrift, zusatzspeicher: ctx.storageUnits > 0, sofortverlangen: Boolean(consent.earlyStartConsentAt) })
// T12 erst, wenn der Wechsel wirksam ist - ohne Nachweis sofort, damit das Team ihn von Hand verfolgt
if (effective || !event) {
await sendSwitchNotice(contact, doc.$id, facts, { wirksam: effective, lastschrift, nachweis_fehlt: !event })
await sendSwitchNotice(contact, doc.$id, facts, { wirksam: effective, lastschrift, nachweis_fehlt: !event, anhang_fehlt: ohneAnhang })
}
return { subscription, paymentUrl, event }
}
@@ -544,7 +539,8 @@ async function completeSwitch(eventId, contact, doneBy = 'stripe') {
doneBy,
details: { ...(wasEffective ? {} : { effectiveAt: now }), paidAt: now },
})
if (!wasEffective) await sendSwitchNotice(contact, event.subscriptionId, factsOf(event), { wirksam: true })
// Das T12 "wirksam" kommt hier erst nach K12: fehlen die PDFs der Fassung 6 noch, sagt es auch dieses T12 dem Team (F5)
if (!wasEffective) await sendSwitchNotice(contact, event.subscriptionId, factsOf(event), { wirksam: true, anhang_fehlt: await anhangFehlt(event.termsVersion) })
return { completed: true, eventId }
})
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Vertragsunterlagen (PDF) als Mail-Anhang der Bestaetigungen K01 (Abo), K12 (Wechsel) und K13 (Zusatzspeicher-Plus), mit der Auskunft, ob sie fehlen.
*
* Fassung 6 (config.abo.termsVersionNeu) liefert die PDFs erst nach der Freigabe (build-legal-pdfs.sh abo6); bis dahin fehlt die Datei. Dann darf die
* Kundenmail nicht behaupten, die Unterlagen haengten an (Block ohne_anhang statt anhang), und das Team bekommt in T02, T12 bzw. T13 den Block
* anhang_fehlt und sendet sie von Hand nach (Muster K38/T24). Alle aelteren Fassungen bleiben, wie sie waren: ohne Datei geht die Mail ohne Anhang
* raus, Vorlage und Text unveraendert (fehlt bleibt false).
*/
import { access, readFile } from 'node:fs/promises'
import { contractAttachmentFor } from './products.js'
import { istNeueFassung } from './vertragsNachweis.js'
/**
* @param {string} termsVersion zugestimmte Fassung (die Fassung der Zustimmung, nicht die des Abos)
* @param {string} tag Kennung fuer das Log, z. B. 'abo'
* @returns {Promise<{ attachments: Array<{ filename: string, content: Buffer, contentType: string }>, fehlt: boolean }>}
* fehlt: nur bei Fassung 6 und nicht lesbarer Datei
*/
export async function ladeVertragsAnhang(termsVersion, tag) {
const { filename, path } = contractAttachmentFor(termsVersion)
const content = await readFile(path).catch((err) => {
console.error(`[${tag}] Vertragsunterlagen ${filename} nicht lesbar:`, err.message)
return null
})
return { attachments: content ? [{ filename, content, contentType: 'application/pdf' }] : [], fehlt: !content && istNeueFassung(termsVersion) }
}
/** Fehlen die PDFs der Fassung 6 (ohne Log, ohne Lesen der Datei)? Für ein später gesendetes T12; ältere Fassungen: nie. */
export async function anhangFehlt(termsVersion) {
if (!istNeueFassung(termsVersion)) return false
const { path } = contractAttachmentFor(termsVersion)
return access(path).then(() => false, () => true)
}