118 lines
9.1 KiB
JavaScript
118 lines
9.1 KiB
JavaScript
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.')
|
||
})
|