import { test, after } from 'node:test' import assert from 'node:assert/strict' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' const aw = await startAppwriteMock() const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { computeLedger, listPlanUsage, getLedger } = await import('../server/services/hourLedger.js') const K = (id, minutes, grantedAt) => ({ $id: id, minutes, grantedAt, expiresAt: '2099-01-01T00:00:00.000Z' }) const U = (id, tag, minutes, einrichtung = false) => ({ id, date: `2026-${tag}T12:00:00.000Z`, minutes, einrichtung }) const NOW = new Date('2026-12-01T00:00:00.000Z') const je = (l) => Object.fromEntries(l.usage.map((e) => [e.id, [e.coveredMinutes, e.overageMinutes, e.vorschussMinutes]])) // [gedeckt, Mehraufwand, Vorschuss] test('Einrichtung ohne volles Guthaben: Vorschuss, nie Mehraufwand; normale Arbeit bleibt Mehraufwand', () => { const k = [K('k1', 60, '2026-09-01T00:00:00.000Z')] const e = computeLedger(k, [U('e1', '09-10', 150, true)], { now: NOW }) assert.deepEqual(je(e), { e1: [60, 0, 90] }); assert.deepEqual([e.vorschussMinutes, e.overageMinutes, e.balanceMinutes, e.usedMinutes], [90, 0, 0, 60]) const n = computeLedger(k, [U('n1', '09-10', 90)], { now: NOW }) assert.deepEqual(je(n), { n1: [60, 30, 0] }); assert.deepEqual([n.vorschussMinutes, n.overageMinutes], [0, 30]) }) test('spätere Gutschrift gleicht zuerst den Vorschuss aus, vor jeder anderen Arbeit', () => { const use = [U('n1', '09-05', 60), U('n2', '09-06', 60), U('e1', '09-10', 60, true)] const [k1, k2, k3] = [K('k1', 60, '2026-09-01T00:00:00.000Z'), K('k2', 60, '2026-10-01T00:00:00.000Z'), K('k3', 60, '2026-11-01T00:00:00.000Z')] assert.deepEqual(je(computeLedger([k1], use, { now: NOW })), { n1: [60, 0, 0], n2: [0, 60, 0], e1: [0, 0, 60] }) // beides sichtbar assert.deepEqual(je(computeLedger([k1, k2], use, { now: NOW })), { n1: [60, 0, 0], n2: [0, 60, 0], e1: [60, 0, 0] }) // k2 deckt e1 vor n2 const voll = computeLedger([k1, k2, k3], use, { now: NOW }) assert.deepEqual(je(voll), { n1: [60, 0, 0], n2: [60, 0, 0], e1: [60, 0, 0] }); assert.deepEqual([voll.overageMinutes, voll.vorschussMinutes, voll.balanceMinutes], [0, 0, 0]) }) test('ohne Einrichtung bleibt die Zuordnung wie bisher (Eintrag für Eintrag, rückwirkend)', () => { const l = computeLedger([K('a', 30, '2026-01-01T00:00:00.000Z'), K('c', 60, '2026-01-15T00:00:00.000Z')], [U('n1', '01-10', 60), U('n2', '01-20', 60)], { now: NOW }) assert.deepEqual(je(l), { n1: [60, 0, 0], n2: [30, 30, 0] }); assert.deepEqual([l.overageMinutes, l.vorschussMinutes], [30, 0]) }) test('Vertragsende (active false): Vorschuss bleibt stehen, Guthaben 0', () => { const l = computeLedger([K('k1', 60, '2026-09-01T00:00:00.000Z')], [U('e1', '09-10', 150, true)], { now: NOW, active: false }) assert.deepEqual([l.vorschussMinutes, l.balanceMinutes, l.nextExpiry], [90, 0, null]) }) test('listPlanUsage und getLedger: Einrichtung nur bei einrichtungAbrechnung abo; Git-Blatt zählt mit; projekt und fehlendes Feld nicht', async () => { aw.seed('workorders', { $id: 'wo_e', customerId: 'c9', woid: '100301', topic: 'Einrichtung', einrichtungAbrechnung: 'abo' }) aw.seed('workorders', { $id: 'wo_p', customerId: 'c9', woid: '100302', topic: 'Projekt', einrichtungAbrechnung: 'projekt' }) aw.seed('workorders', { $id: 'wo_n', customerId: 'c9', woid: '100303', topic: 'Alt' }) const ws = (id, wo, tag, over = {}) => aw.seed('worksheets', { $id: id, workorderId: wo, wsid: id, countsToPlan: true, totalTime: 60, startDate: `${tag}.10.2026`, employeeName: 'Justin', details: 'x', ...over }) ws('s_e', 'wo_e', '05'); ws('s_git', 'wo_e', '06', { serviceType: 'GIT', isComment: true, totalTime: 30, employeeShort: 'GIT' }) ws('s_p', 'wo_p', '07'); ws('s_n', 'wo_n', '08'); ws('s_aus', 'wo_e', '09', { countsToPlan: false }) assert.deepEqual(Object.fromEntries((await listPlanUsage('c9')).map((x) => [x.id, x.einrichtung])), { s_e: true, s_git: true, s_p: false, s_n: false }) aw.seed('hourCredits', { ...K('h1', 60, '2026-09-01T00:00:00.000Z'), customerId: 'c9' }) const l = await getLedger('c9') // 60 min gedeckt (s_e), s_git 30 min Vorschuss, s_p und s_n je 60 min Mehraufwand assert.deepEqual([l.usedMinutes, l.vorschussMinutes, l.overageMinutes], [60, 30, 120]) })