import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' // Etappe 3: Texte und Rechnungen aus dem frueheren Admin-Panel des Portals, wortgleich ins Ticketsystem uebernommen const fmt = await loadModule('src/lib/format.js') const credit = await loadModule('src/lib/creditFormat.js') const offer = await loadModule('src/lib/offerFormat.js') const storage = await loadModule('src/lib/storageFormat.js') const NB = ' ' test('format: Euro aus Cent, Eingabe in Cent, deutsche Daten', () => { assert.equal(fmt.formatCents(123456), `1.234,56${NB}€`) assert.equal(fmt.formatCents(0), `0,00${NB}€`) assert.equal(fmt.parseEuroToCents('12,5'), 1250) assert.equal(fmt.parseEuroToCents('12.50'), 1250) assert.equal(fmt.parseEuroToCents(''), null) assert.equal(fmt.parseEuroToCents('abc'), null) assert.equal(fmt.fmtDate('2026-09-30T22:30:00.000Z'), '01.10.2026') assert.equal(fmt.fmtDate(null), '–') assert.equal(fmt.fmtDateTime('2026-09-30T10:05:00.000Z'), '30.09.26, 12:05') assert.equal(fmt.fmtYmd('2026-10-27'), '27.10.2026') }) test('Gutschrift: Status, Verlauf, Rechenzeile', () => { assert.deepEqual(credit.CREDIT_STATUS.offen, { tone: 'warn', label: 'Zu prüfen' }) assert.deepEqual(credit.CREDIT_STATUS.rueckfrage, { tone: 'muted', label: 'Rückfrage beim Kunden' }) assert.equal(credit.historyLabel('verrechnet'), 'In Stripe gutgeschrieben') assert.equal(credit.historyLabel('nachweise-geloescht'), 'Nachweise gelöscht') assert.equal(credit.historyLabel('neu'), 'neu') assert.deepEqual(credit.BILLING_MONTHS, [1, 3, 6, 12, 24, 36]) assert.equal( credit.calcLine({ periodStart: '2026-01-01', periodEnd: '2026-12-31', periodDays: 365, overlapStart: '2026-10-01', overlapDays: 92, suggestedCents: 3025 }), `Rechnungszeitraum 01.01.2026 – 31.12.2026 (365 Tage). Doppelt bezahlt ab 01.10.2026: 92 Tage → Vorschlag 30,25${NB}€` ) }) test('Gutschrift: Pruefung, Rueckfragen und Meldungen wie im Admin-Panel', () => { assert.equal(credit.decisionError('question', 'kurz'), 'Bitte die Rückfrage an den Kunden ins Nachrichtenfeld schreiben.') assert.equal(credit.decisionError('reject', ''), 'Bitte die Begründung für den Kunden ins Nachrichtenfeld schreiben.') assert.equal(credit.decisionError('question', 'Bitte Rechnung'), null) assert.equal(credit.decisionError('approve', ''), null) assert.equal(credit.decisionConfirm('approve', 3025, 'Berta'), `Gutschrift über 30,25${NB}€ genehmigen, in Stripe gutschreiben und Berta per E-Mail informieren?`) assert.equal(credit.decisionConfirm('question', 0, ''), 'Rückfrage per E-Mail an den Kunden senden?') assert.equal(credit.decisionConfirm('reject', 0, 'Berta'), 'Antrag ablehnen und Berta die Begründung per E-Mail senden?') assert.equal(credit.decisionMessage('approve', { status: 'genehmigt', applied: true, mailSent: true }), 'Genehmigt. Das Guthaben ist in Stripe gebucht. Die E-Mail an den Kunden ist raus.') assert.equal( credit.decisionMessage('approve', { status: 'genehmigt', applied: false, reason: 'Stripe: kaputt', mailSent: false, mailError: 'SMTP weg' }), 'Genehmigt. Noch nicht in Stripe gebucht: Stripe: kaputt E-Mail an den Kunden fehlgeschlagen (SMTP weg) – bitte selbst informieren.' ) assert.equal(credit.decisionMessage('question', { status: 'rueckfrage', mailSent: false }), 'Rückfrage gesendet. E-Mail an den Kunden fehlgeschlagen (unbekannt) – bitte selbst informieren.') assert.equal(credit.applyMessage({ applied: true }), 'Das Guthaben ist in Stripe gebucht.') assert.equal(credit.applyMessage({ applied: false, reason: 'Der Kunde hat kein laufendes Abo.' }), 'Nicht gebucht: Der Kunde hat kein laufendes Abo.') }) test('Angebot: Vorschautext, Monate, Rabattende', () => { assert.equal(offer.offerPreviewText(2500, 2, 5000, false), `Erste 2 Monate je 25,00${NB}€ statt 50,00${NB}€, danach 50,00${NB}€ pro Monat`) assert.equal(offer.offerPreviewText(0, 1, 2000, true), `Nächster Monat kostenlos, danach 20,00${NB}€ pro Monat`) assert.equal(offer.addMonthsYmd('2026-01-31', 1), '2026-02-28') assert.equal(offer.addMonthsYmd('2026-11-15', 2), '2027-01-15') const est = offer.offerDiscountEstimate(2, '2026-10-31', false, '2026-10-02') assert.deepEqual(est, { earliest: '2026-12-02', latest: '2026-12-31' }) assert.deepEqual(offer.offerDiscountEstimate(2, '2026-10-31', true, '2026-10-02'), { earliest: '2027-01-02', latest: '2027-01-31' }) assert.equal(offer.discountLine({ status: 'offen', validUntil: '2026-10-02T00:00:00.000Z', months: 2 }, '2026-10-02'), 'Rabatt endet voraussichtlich am 02.12.2026') assert.equal(offer.discountLine({ status: 'aktiv', discount: { known: false } }), 'Rabattende unbekannt (Abo nicht gefunden)') assert.equal(offer.discountLine({ status: 'aktiv', discount: { known: true, endsAt: '2099-01-01T12:00:00.000Z' } }), 'Rabatt endet am 01.01.2099') assert.deepEqual(offer.OFFER_STATUS.zurueckgezogen, { tone: 'muted', label: 'zurückgezogen' }) }) const ctx = (over = {}) => ({ customer: { id: 'c1', name: 'Berta', portalAccessEnabled: true, testAccount: false }, subscription: null, openOffers: { hosting: null, wartung: null }, products: { hosting: { regularPriceCents: 2000 }, wartung: { regularPriceCents: 5000 } }, defaultValidUntil: '2026-11-01', ...over, }) const form = (over = {}) => ({ customerId: 'c1', productKey: 'wartung', monthlyPriceCents: 2500, months: 2, validUntil: '2026-10-31', ...over }) test('Angebotsformular: Sperren, laufendes Abo, Knopftext', () => { const ok = offer.evaluateOfferForm(ctx(), form(), '2026-10-02') assert.deepEqual(ok.errors, []) assert.equal(ok.running, false) assert.equal(ok.saveLabel, 'Angebot anlegen') assert.equal(ok.previewLines[0], `Erste 2 Monate je 25,00${NB}€ statt 50,00${NB}€, danach 50,00${NB}€ pro Monat · Ersparnis 50,00${NB}€`) assert.equal(ok.previewLines.at(-1), 'Noch kein laufendes Abo: Der Coupon wird angelegt und beim Abschluss im Kundenportal angewendet.') const blocked = offer.evaluateOfferForm(ctx({ openOffers: { hosting: null, wartung: { ref: 'A1', text: 'x' } } }), form({ monthlyPriceCents: 5000 }), '2026-10-02') assert.deepEqual(blocked.errors, [ 'Es gibt schon ein offenes Wartungs-Abo-Angebot (A1: x) – bitte zuerst zurückziehen.', `Angebotspreis bitte zwischen 0,00 € und 49,99${NB}€ angeben.`, ]) const running = offer.evaluateOfferForm(ctx({ subscription: { active: true, productKey: 'wartung', currentPeriodEnd: '2026-11-01T00:00:00.000Z', upcomingCharges: ['2026-11-01T00:00:00.000Z', '2026-12-01T00:00:00.000Z', '2027-01-01T00:00:00.000Z'] } }), form({ validUntil: '' }), '2026-10-02') assert.equal(running.running, true) assert.equal(running.saveLabel, 'Angebot anlegen und aufs laufende Abo anwenden') assert.ok(running.previewLines.includes('Rabatt endet am 01.01.2027 (nach 2 Abbuchungen).')) assert.deepEqual(offer.evaluateOfferForm(ctx(), form({ validUntil: '' }), '2026-10-02').errors, ['Bitte „Gültig bis“ angeben.']) assert.equal( offer.offerConfirm(ctx(), form(), false), `Angebot für Berta anlegen?\n\nErste 2 Monate je 25,00${NB}€ statt 50,00${NB}€, danach 50,00${NB}€ pro Monat\nGültig bis 31.10.2026\n\nDer Coupon wird sofort in Stripe angelegt.` ) }) test('E-Mail-Speicher: Pakete, Zusammenfassung, Aufgaben', () => { assert.equal(storage.storageLabel(0), 'kein Paket (5 GB)') assert.equal(storage.storageLabel(1), '1 Paket (10 GB)') assert.equal(storage.storageLabel(3), '3 Pakete (20 GB)') assert.deepEqual(storage.summaryText({ allocatedGb: 20, packageGb: 50, warnGb: 40, packageName: 'Webhosting M' }), { warn: false, text: '20 von 50 GB vergeben (Paket Webhosting M) · Warnung ab 40 GB' }) assert.deepEqual(storage.summaryText({ allocatedGb: 41, packageGb: 50, warnGb: 40, packageName: 'Webhosting M' }), { warn: true, text: '41 von 50 GB vergeben (Paket Webhosting M) – Warnschwelle 40 GB erreicht: größeres Hetzner-Paket buchen.' }) assert.equal(storage.taskText({ kind: 'einrichten', targetGb: 15 }), 'Postfachgrenze in konsoleH jetzt auf 15 GB setzen') assert.equal(storage.taskText({ kind: 'periodenende', targetGb: 10, dueAt: '2026-11-01T00:00:00.000Z' }), 'Postfachgrenze in konsoleH am 01.11.2026 auf 10 GB senken') assert.equal(storage.scopeText({ targetGb: 15, contactDomain: 'kunde.de' }), 'Gesamtspeicher des Kunden: 15 GB, auf seine Postfächer verteilt nach seiner Vorgabe – § 3 Abs. 4 · Kontakt: kunde.de') assert.equal(storage.changeText({ fromUnits: 0, toUnits: 2, viaCancellation: true }), 'kein Paket (5 GB) → 2 Pakete (15 GB) · über die Kündigungsseite') assert.equal(storage.partialText({ partial: true, toUnits: 1, error: 'x' }), 'Teilweise: Stripe steht noch auf der bezahlten Menge, nicht auf 1 Paket(en) – Stückzahl in Stripe von Hand nachziehen. Fehler: x') assert.equal(storage.orderedKind({ fromUnits: 0, toUnits: 2 }), 'Bestellung (Plus)') assert.equal(storage.orderedKind({ fromUnits: 3, toUnits: 1 }), 'Verringerung (Minus)') assert.equal(storage.failedText({ fromUnits: 0, toUnits: 1, error: '' }), 'kein Paket (5 GB) → 1 Paket (10 GB) – Stripe hat die Änderung abgelehnt, nichts berechnet, keine Aufgabe.') })