Zusatzspeicher: Plan nach Stripe-Abgleich erneut prüfen, Aufgabenliste unter Sperre, K13 ohne Kosten
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
662f8d8144
commit
f66a713531
4 files changed
+153
-25
No files matched your search
@@ -207,10 +207,23 @@ export async function previewStorageChange(customerId, newUnits) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertPlanAllowed(plan, consent) {
|
||||
/**
|
||||
* Richtung und Zustimmung pruefen: unveraendert 400, Plus ohne Zustimmung 400. reconciled: der Plan wurde nach
|
||||
* dem Abgleich mit Stripe neu berechnet (Spiegel und Stripe wichen ab) - dann sagt die Meldung, dass die
|
||||
* Seite neu geladen werden muss; ueber das Kuendigungsformular ist dann nur ein Minus erlaubt (409).
|
||||
*/
|
||||
function assertPlanAllowed(plan, consent, { reconciled = false, viaCancellation = false } = {}) {
|
||||
if (plan.direction === 'none') throw httpError(400, 'Die Anzahl der Pakete ist unverändert.')
|
||||
if (reconciled && viaCancellation && plan.direction !== 'minus') {
|
||||
throw httpError(409, 'Über die Kündigung lässt sich der Zusatzspeicher nur verringern.')
|
||||
}
|
||||
if (plan.direction === 'plus' && !(consent?.termsVersion && consent?.consentAt)) {
|
||||
throw httpError(400, 'Bitte stimmen Sie den Regeln zum Zusatzspeicher zu.')
|
||||
throw httpError(
|
||||
400,
|
||||
reconciled
|
||||
? 'Ihr Speicherstand hat sich geändert. Bitte Seite neu laden und der Bestellung zustimmen.'
|
||||
: 'Bitte stimmen Sie den Regeln zum Zusatzspeicher zu.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +258,7 @@ function assertChangeAllowed(doc, events, direction, viaCancellation) {
|
||||
*/
|
||||
async function recordStorageEvent({ doc, change, consent, viaCancellation }) {
|
||||
try {
|
||||
return await recordContractEvent({
|
||||
const event = await recordContractEvent({
|
||||
customerId: doc.customerId,
|
||||
subscriptionId: doc.$id,
|
||||
type: 'speicher',
|
||||
@@ -270,9 +283,15 @@ async function recordStorageEvent({ doc, change, consent, viaCancellation }) {
|
||||
consentUserAgent: consent.consentUserAgent || null,
|
||||
}
|
||||
: null,
|
||||
status: 'offen',
|
||||
status: change.task ? 'offen' : 'erledigt',
|
||||
dueAt: change.dueAt,
|
||||
})
|
||||
if (change.task) return event
|
||||
// Kein Auftrag fuers Team: als erledigt markiert, damit die Aufgabenliste (Task 13) es nicht zeigt
|
||||
return await updateContractEvent(event.$id, { doneAt: new Date().toISOString(), doneBy: 'keine Aufgabe' }).catch((e) => {
|
||||
console.error(`[speicher] Ereignis ${event.$id} nicht als erledigt markiert:`, e.message)
|
||||
return event
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`[speicher] Ereignis für Abo ${doc.$id} nicht gespeichert:`, err.message)
|
||||
return null
|
||||
@@ -327,10 +346,14 @@ async function sendStorageMails({ doc, change, consent, viaCancellation, proofMi
|
||||
monatspreis_zusatz: fmtEuro(change.monthlyCents),
|
||||
wirksam_ab: fmtDate(change.effectiveAt),
|
||||
},
|
||||
blocks: { plus, minus: !plus },
|
||||
// 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 },
|
||||
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
|
||||
await sendAdminNotice({
|
||||
template: 'T13',
|
||||
values: {
|
||||
@@ -371,12 +394,13 @@ export async function changeStorage(customerId, newUnits, consent, options = {})
|
||||
// Vorpruefung mit dem Spiegel: Grenzen, Richtung, Zustimmung, Status - ohne Stripe-Aufruf
|
||||
const plan = planStorageChange({ currentUnits: unitsOf(doc), paidUnits: paidUnitsOf(doc), newUnits, maxUnits: config.mailStorage.maxUnits })
|
||||
assertPlanAllowed(plan, consent)
|
||||
const events = (await listContractEvents(customerId)).filter((e) => e.subscriptionId === doc.$id)
|
||||
assertChangeAllowed(doc, events, plan.direction, viaCancellation)
|
||||
// Sperre pruefen und setzen ohne await dazwischen
|
||||
// Sperre pruefen und setzen ohne await dazwischen; die offenen Ereignisse erst unter der Sperre lesen, damit
|
||||
// das Ersetzen aelterer Aufgaben nie auf einer veralteten Liste arbeitet
|
||||
if (activeChanges.has(doc.$id)) throw httpError(409, 'Eine Änderung läuft bereits. Bitte warten Sie einen Moment und laden Sie die Seite neu.')
|
||||
activeChanges.add(doc.$id)
|
||||
try {
|
||||
const events = (await listContractEvents(customerId)).filter((e) => e.subscriptionId === doc.$id)
|
||||
assertChangeAllowed(doc, events, plan.direction, viaCancellation)
|
||||
return await performChange({ doc, events, newUnits, consent, viaCancellation })
|
||||
} finally {
|
||||
activeChanges.delete(doc.$id)
|
||||
@@ -393,14 +417,20 @@ async function performChange({ doc, events, newUnits, consent, viaCancellation }
|
||||
const currentUnits = unitsOf(fresh)
|
||||
const paidUnits = paidUnitsOf(fresh)
|
||||
const plan = planStorageChange({ currentUnits, paidUnits, newUnits, maxUnits: config.mailStorage.maxUnits })
|
||||
if (plan.direction === 'none') throw httpError(400, 'Die Anzahl der Pakete ist unverändert.')
|
||||
// Weicht der frische Stand vom Spiegel ab, kann aus einem Minus ein Plus werden: Zustimmung und
|
||||
// Kuendigungsweg erneut pruefen, bevor irgendetwas geschrieben wird (Fix 2)
|
||||
assertPlanAllowed(plan, consent, { reconciled: true, viaCancellation })
|
||||
const plus = plan.direction === 'plus'
|
||||
const now = new Date().toISOString()
|
||||
const kind = plus && newUnits > paidUnits ? 'einrichten' : 'periodenende'
|
||||
// Ein Plus genau zurueck auf die bezahlte Menge: konsoleH steht schon darauf, zum Periodenende ist nichts zu
|
||||
// tun - kein offener Auftrag fuers Team, das Ereignis bleibt als Nachweis (erledigt)
|
||||
const task = !(plus && newUnits === paidUnits)
|
||||
const reductionAt = reductionEffectiveAt(fresh)
|
||||
const change = {
|
||||
direction: plan.direction,
|
||||
kind,
|
||||
task,
|
||||
fromUnits: currentUnits,
|
||||
toUnits: newUnits,
|
||||
// Fuer den Kunden: ein Plus gilt sofort, ein Minus zum Periodenende; fuers Team: die Einrichtung ist
|
||||
@@ -413,7 +443,7 @@ async function performChange({ doc, events, newUnits, consent, viaCancellation }
|
||||
partial: false,
|
||||
}
|
||||
|
||||
// 1. Ereignis zuerst: Nachweis und Aufgabe, Status offen
|
||||
// 1. Ereignis zuerst: Nachweis und Aufgabe, Status offen (ohne Aufgabe: erledigt)
|
||||
const event = await recordStorageEvent({ doc: fresh, change, consent, viaCancellation })
|
||||
|
||||
// 2. Stripe, Schritt fuer Schritt (planStorageChange)
|
||||
|
||||
Reference in new issue
Block a user