Übernahmegutschrift: Verrechnung nach Wechsel, Mail K09, Leitfaden, Admin zeigt das Abo, Tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-28 07:49:51 +00:00
1 parent 0c2e50fc20
commit e456c3de69
9 files changed
+80 -23

No files matched your search

+9 -3
View File
@@ -9,6 +9,11 @@ const { config } = await import('../server/config.js')
// Fest eingetragen, weil die Tests auch ohne git laufen muessen (node:20-alpine).
const V3_SHA256 = '1c9e4b002dcd5d7896bbda24c573e56544b7123e3a88d154e5db77bd2855bc47'
// = sha256 des Blocks ab `const TAKEOVER_CONDITIONS` bis vor das schliessende ] in public/app.js, je conditionsVersion
// (Nachweis der Zustimmung). Aendert sich der Text der Bedingungen, muss config.takeover.conditionsVersion hochgezogen
// und der neue Hash hier eingetragen werden; die Fehlermeldung des Tests nennt ihn.
const TAKEOVER_CONDITIONS_SHA256 = { '2026-09-27': '2b7a745cb599c0ab28f20615a745bd86da6faa8d120391ca56beac35a0473e62' }
const file = (rel) => new URL(`../${rel}`, import.meta.url)
const read = (rel) => readFileSync(file(rel), 'utf8')
const sha256 = (rel) => createHash('sha256').update(readFileSync(file(rel))).digest('hex')
@@ -335,16 +340,17 @@ test('Übernahmegutschrift und Leitfaden: produktneutral', () => {
assert.notEqual(config.takeover.conditionsVersion, '2026-09-25', 'conditionsVersion hochgezogen')
const tc = read('server/services/takeoverCredits.js')
assert.ok(!/Wartungs-Abo (läuft|ist beendet)|kein (laufendes )?Wartungs-Abo/.test(tc), 'Prüf-Labels und Fehlermeldungen sagen „Abo“')
assert.ok(!tc.includes('config.stripe.priceAmountCents'), 'Plausibilität gegen den Produktpreis, nicht gegen den Wartungspreis')
const lf = read('docs/leitfaden-empfehlungsgeber.html')
assert.ok(lf.includes('Hosting-Abo') && lf.includes('beliebig viele Postfächer'), 'Leitfaden nennt beide Abos')
// Festlegungen zur Umsetzung: Postfächer im Plural (Wartungs-Abo: beliebig viele), Entwurfsdatum als Version,
// Karte im Portal und Checkliste ohne Produktnamen, Preisvergleich je Produkt
// Karte im Portal und Checkliste ohne Produktnamen (Preisvergleich je Produkt: test/takeoverCredits.test.js)
assert.ok(cond.includes('E-Mail-Postfächer'))
assert.match(config.takeover.conditionsVersion, /^\d{4}-\d{2}-\d{2}$/)
const condHash = createHash('sha256').update(cond).digest('hex')
assert.equal(condHash, TAKEOVER_CONDITIONS_SHA256[config.takeover.conditionsVersion],
`Bedingungen der Übernahmegutschrift geändert: conditionsVersion hochziehen und Hash eintragen (${condHash})`)
assert.ok(app.includes('sobald Ihr Abo läuft') && !app.includes('sobald Ihr Wartungs-Abo läuft'))
assert.ok(!tc.includes('durch das Wartungs-Abo ersetzt'))
assert.ok(tc.includes("getProduct(subscription?.productKey || 'wartung')"))
// Leitfaden: beide Preise mit § 19 UStG, Zusatzspeicher, Wechsel, Gutschrift für beide Abos, Bestellweg „Verträge“
const lfText = strip(lf)
for (const s of ['zusammen 5 GB', 'Zusatzspeicher', 'Wechsel', 'Übernahmegutschrift', 'beiden Abos', '„Verträge“']) assert.ok(lfText.includes(s), s)
+8
View File
@@ -129,3 +129,11 @@ test('htmlToText: Links, Buttons und Leerzeilen', () => {
test('Vorlagen-Ordner enthält nur Vorlagen', () => {
assert.deepEqual(readdirSync(DIR).filter((f) => !/^[KT]\d{2} .*\.html$/.test(f)), [])
})
test('K09: Gutschrift wird mit dem Abo verrechnet, auch nach einem Wechsel', () => {
const tpl = loadTemplate('K09')
const mail = renderMail('K09', sampleValues(tpl), allBlocks(tpl, true))
assert.match(mail.text, /mit den nächsten Monatsgebühren Ihres Abos\. Auf Ihren Rechnungen erscheint sie als angerechnetes Guthaben\./)
assert.match(mail.text, /Eine Auszahlung ist nicht möglich\. Wechseln Sie in ein anderes Abo, verrechnen wir die Gutschrift weiter; endet Ihr Abo ohne einen solchen Wechsel, entfällt ein noch nicht verrechneter Rest\./)
assert.doesNotMatch(mail.html, /Wartungs-Abo|Vertragsende/)
})
+37
View File
@@ -0,0 +1,37 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
// runAutoChecks arbeitet ohne Domain komplett offline (kein DNS-Abgleich, kein Appwrite, kein Stripe):
// Die Pruefung vergleicht den Monatsbetrag beim bisherigen Anbieter mit der Monatsgebuehr des Abos des Kunden.
const { runAutoChecks } = await import('../server/services/takeoverCredits.js')
// 240 € fuer 12 Monate = 20,00 € pro Monat: mehr als das Hosting-Abo (15,00 €), weniger als das Wartungs-Abo (50,00 €)
const doc = { $id: 'req1', $createdAt: '2026-10-01T10:00:00.000Z', domain: '', invoiceAmountCents: 24000, billingMonths: 12, files: '[]' }
const sub = (productKey) => ({
status: 'active',
startedAt: '2026-10-01T00:00:00.000Z',
currentPeriodEnd: '2026-11-01T00:00:00.000Z',
...(productKey === undefined ? {} : { productKey }),
})
const amountCheck = async (subscription) =>
(await runAutoChecks(doc, { allRequests: [], subscription, calc: null })).find((c) => c.key === 'amount')
test('Plausibilität: 20 € pro Monat ist mehr als das Hosting-Abo (15,00 €)', async () => {
const hosting = await amountCheck(sub('hosting'))
assert.ok(hosting, 'Hinweis „mehr als die Monatsgebühr“ fehlt beim Hosting-Abo')
assert.equal(hosting.status, 'info')
assert.ok(hosting.label.includes('15,00'), hosting.label)
assert.ok(hosting.label.includes('20,00'), hosting.label)
})
test('Plausibilität: 20 € pro Monat ist nicht mehr als das Wartungs-Abo (50,00 €)', async () => {
assert.equal(await amountCheck(sub('wartung')), undefined)
})
test('Abos ohne oder mit unbekanntem productKey gelten als Wartungs-Abo, ohne Abo ebenso', async () => {
assert.equal(await amountCheck(sub(undefined)), undefined, 'Altbestand ohne productKey')
assert.equal(await amountCheck(sub('gibtsnicht')), undefined, 'unbekannter productKey darf die Prüfung nicht abbrechen')
const ohneAbo = await runAutoChecks(doc, { allRequests: [], subscription: null, calc: null })
assert.equal(ohneAbo.find((c) => c.key === 'amount'), undefined)
assert.equal(ohneAbo.find((c) => c.key === 'abo')?.status, 'warn')
})