Kundenansicht neu E-Fix W1 (Portal): Team-Endpunkt stundenkonto liefert zusätzlich active

GET /api/team/customers/:id/stundenkonto kannte nur Produkt und Konto; WOMS (stundenkontoView.js) kann bei beendetem Abo nicht "verfällt, wird
nicht berechnet" (V8, E17) statt "wird mit den nächsten Gutschriften verrechnet" zeigen, weil das Feld active fehlte. Jetzt trägt jede Antwort
active (Abo läuft: Status active, trialing oder past_due, auch mit vorgemerkter Kündigung; ohne Abo false). WOMS liest antwort.active schon,
nur ein ausdrückliches false ändert den Satz. Test in teamStundenkonto.test.js.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 21:49:50 +00:00
1 parent 6ff63a7a11
commit c92ddd9363
2 files changed
+18 -9

No files matched your search

+8 -4
View File
@@ -56,16 +56,20 @@ router.get('/:id/abo', async (req, res) => {
// Stundenkonto fuers Ticketsystem (Abo-Modell der Funktionen): Produkt wie /:id/abo, dazu Guthaben, Vorschuss // Stundenkonto fuers Ticketsystem (Abo-Modell der Funktionen): Produkt wie /:id/abo, dazu Guthaben, Vorschuss
// (Einrichtung ohne Deckung) und Mehraufwand. Nur das Wartungs-Abo hat ein Konto, auch nach dem Ende // (Einrichtung ohne Deckung) und Mehraufwand. Nur das Wartungs-Abo hat ein Konto, auch nach dem Ende
// (dann Guthaben 0, der Vorschuss bleibt stehen); Hosting und kein Abo ergeben ledger null // (dann Guthaben 0, der Vorschuss bleibt stehen); Hosting und kein Abo ergeben ledger null.
// active (E-Fix W1): das Abo laeuft (Status active, trialing oder past_due, auch mit vorgemerkter Kuendigung); ohne Abo false. WOMS zeigt
// bei active false zum Vorschuss "verfaellt, wird nicht berechnet" (V8, E17) statt "wird mit den naechsten Gutschriften verrechnet".
router.get('/:id/stundenkonto', async (req, res) => { router.get('/:id/stundenkonto', async (req, res) => {
try { try {
const sub = await getCurrentSubscription(req.params.id) const sub = await getCurrentSubscription(req.params.id)
if (!sub) return res.json({ produkt: null, ledger: null }) if (!sub) return res.json({ produkt: null, active: false, ledger: null })
const active = ACTIVE_STATUSES.has(sub.status)
const produkt = PRODUCT_KEYS.includes(sub.productKey) ? sub.productKey : 'wartung' const produkt = PRODUCT_KEYS.includes(sub.productKey) ? sub.productKey : 'wartung'
if (produkt !== 'wartung') return res.json({ produkt, ledger: null }) if (produkt !== 'wartung') return res.json({ produkt, active, ledger: null })
const l = await getLedger(req.params.id, { active: ACTIVE_STATUSES.has(sub.status) }) const l = await getLedger(req.params.id, { active })
return res.json({ return res.json({
produkt, produkt,
active,
ledger: { ledger: {
balanceMinutes: l.balanceMinutes, balanceMinutes: l.balanceMinutes,
vorschussMinutes: l.vorschussMinutes, vorschussMinutes: l.vorschussMinutes,
+10 -5
View File
@@ -4,7 +4,8 @@ import { startAppwriteMock } from './support/appwriteMock.js'
import { startFakeStripe } from './support/fakeStripe.js' import { startFakeStripe } from './support/fakeStripe.js'
import { useTestEnv } from './support/testEnv.js' import { useTestEnv } from './support/testEnv.js'
// Team-Endpunkt Stundenkonto fuers Ticketsystem: Produkt, Guthaben, Vorschuss, Mehraufwand // Team-Endpunkt Stundenkonto fuers Ticketsystem: Produkt, Guthaben, Vorschuss, Mehraufwand und active (Abo laeuft; E-Fix W1: WOMS zeigt bei beendetem Abo
// "verfaellt, wird nicht berechnet" statt "wird mit den naechsten Gutschriften verrechnet")
const aw = await startAppwriteMock() const aw = await startAppwriteMock()
const st = await startFakeStripe() const st = await startFakeStripe()
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
@@ -24,13 +25,17 @@ test('Wartung: Guthaben, Vorschuss und Mehraufwand aus dem Stundenkonto', async
aw.seed('workorders', { $id: 'wo_t', customerId: 'c3', woid: '100401', topic: 'Einrichtung', einrichtungAbrechnung: 'abo' }) aw.seed('workorders', { $id: 'wo_t', customerId: 'c3', woid: '100401', topic: 'Einrichtung', einrichtungAbrechnung: 'abo' })
aw.seed('worksheets', { $id: 'ws_t', workorderId: 'wo_t', wsid: 'ws_t', countsToPlan: true, totalTime: 90, startDate: '10.09.2026' }) aw.seed('worksheets', { $id: 'ws_t', workorderId: 'wo_t', wsid: 'ws_t', countsToPlan: true, totalTime: 90, startDate: '10.09.2026' })
const res = await rawGet('/api/team/customers/c3/stundenkonto', auth) const res = await rawGet('/api/team/customers/c3/stundenkonto', auth)
assert.deepEqual([res.status, res.body], [200, { produkt: 'wartung', ledger: { balanceMinutes: 0, vorschussMinutes: 30, overageMinutes: 0, nextExpiry: null } }]) assert.deepEqual([res.status, res.body], [200, { produkt: 'wartung', active: true, ledger: { balanceMinutes: 0, vorschussMinutes: 30, overageMinutes: 0, nextExpiry: null } }])
}) })
test('Hosting: kein Konto; ohne Abo null; beendetes Wartungs-Abo behält den Vorschuss, Guthaben 0; ohne JWT 401', async () => { test('Hosting: kein Konto; ohne Abo null; beendetes Wartungs-Abo behält den Vorschuss, Guthaben 0; ohne JWT 401', async () => {
assert.deepEqual((await rawGet('/api/team/customers/c1/stundenkonto', auth)).body, { produkt: 'hosting', ledger: null }) // c1: Hosting aus seedFixtures assert.deepEqual((await rawGet('/api/team/customers/c1/stundenkonto', auth)).body, { produkt: 'hosting', active: true, ledger: null }) // c1: Hosting aus seedFixtures
assert.deepEqual((await rawGet('/api/team/customers/c4/stundenkonto', auth)).body, { produkt: null, ledger: null }) assert.deepEqual((await rawGet('/api/team/customers/c4/stundenkonto', auth)).body, { produkt: null, active: false, ledger: null })
await updateDocument('subscriptions', 'sub_t', { status: 'canceled' }) // aw.docs() liefert Kopien: ueber die API aendern await updateDocument('subscriptions', 'sub_t', { status: 'canceled' }) // aw.docs() liefert Kopien: ueber die API aendern
const ende = (await rawGet('/api/team/customers/c3/stundenkonto', auth)).body const ende = (await rawGet('/api/team/customers/c3/stundenkonto', auth)).body
assert.deepEqual([ende.produkt, ende.ledger.vorschussMinutes, ende.ledger.balanceMinutes], ['wartung', 30, 0]) assert.deepEqual([ende.produkt, ende.active, ende.ledger.vorschussMinutes, ende.ledger.balanceMinutes], ['wartung', false, 30, 0])
await updateDocument('subscriptions', 'sub_t', { status: 'active', cancelAtPeriodEnd: true }) // gekündigt, aber bis zum Periodenende noch aktiv: der Vorschuss geht noch in die Gutschriften
assert.equal((await rawGet('/api/team/customers/c3/stundenkonto', auth)).body.active, true)
await updateDocument('subscriptions', 'sub_t', { status: 'past_due', cancelAtPeriodEnd: false }) // offene Zahlung: das Abo läuft noch (ACTIVE_STATUSES)
assert.equal((await rawGet('/api/team/customers/c3/stundenkonto', auth)).body.active, true)
assert.equal((await rawGet('/api/team/customers/c3/stundenkonto')).status, 401) assert.equal((await rawGet('/api/team/customers/c3/stundenkonto')).status, 401)
}) })