Abo-Angebote: Admin sieht, bis wann der Rabatt läuft

"Gültig bis" ist nur die Frist zum Annehmen. Jetzt steht auch das
Rabattende da:
- Formular "Neues Angebot", Kunde ohne Abo: Schätzung "Rabatt läuft
  N Monate ab Abschluss und endet voraussichtlich zwischen <heute + N>
  und <gültig bis + N>", mit Hinweis auf den späteren Start nach der
  Widerrufsfrist (gut 2 Wochen)
- Formular, laufendes Abo: genaues Ende aus den nächsten Abbuchungen
  (Anker = Leistungsbeginn, Monatsende wie bei Stripe), auch in der
  Erfolgsmeldung
- Angebotsliste: offen -> Schätzung, aktiv -> "Rabatt endet am ..."
  aus dem Abo-Dokument (bzw. "endete", "endet mit dem Abo am ...",
  "unbekannt (Abo nicht gefunden)")

Rechnung in server/services/offerDiscount.js: Der Coupon deckt die
N Abbuchungen ab dem Anwenden. Beim Checkout ist das der Abo-Start, bei
laufendem Abo und Testkonto (Coupon per Webhook) der Zeitpunkt des
Anhängens; Testkonten laufen deshalb einen Monat länger.

Tests: npm test (37 Fälle, davon 8 neu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-26 23:01:45 +00:00
1 parent c600118374
commit 054b04d5b6
5 files changed
+227 -12

No files matched your search

+9 -4
View File
@@ -22,12 +22,17 @@ function sendError(res, err, fallback) {
offersAdminRouter.get('/', async (_req, res) => {
try {
const [docs, customers] = await Promise.all([
const [docs, customers, subs] = await Promise.all([
listOffers(),
listDocuments(config.collections.customers, [Query.limit(1000)]),
// Abo-Dokumente (ID = Stripe-Subscription) fuer das Rabattende eingeloester Angebote
listDocuments(config.collections.subscriptions, [Query.limit(1000)]),
])
const byId = new Map(customers.map((c) => [c.$id, c]))
return res.json({ offers: docs.map((d) => adminOfferView(d, byId.get(d.customerId))) })
const subById = new Map(subs.map((s) => [s.$id, s]))
return res.json({
offers: docs.map((d) => adminOfferView(d, byId.get(d.customerId), subById.get(d.stripeSubscriptionId))),
})
} catch (err) {
return sendError(res, err, 'Angebote konnten nicht geladen werden')
}
@@ -43,9 +48,9 @@ offersAdminRouter.get('/context/:customerId', async (req, res) => {
offersAdminRouter.post('/', async (req, res) => {
try {
const { doc, customer, mail } = await createOffer(req.body || {}, adminOf(req))
const { doc, customer, mail, sub } = await createOffer(req.body || {}, adminOf(req))
return res.status(201).json({
offer: adminOfferView(doc, customer),
offer: adminOfferView(doc, customer, sub),
mailSent: mail.sent,
mailError: mail.sent ? '' : mail.reason || '',
})