From c116c3cb4f087b9ca1b7148686945d38da8beefc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 14:15:44 +0000 Subject: [PATCH] Rechnungen: mapInvoice liefert Projekt, Art und Auftrag Co-Authored-By: Claude Sonnet 5.5 --- server/services/finance/stripeFinance.js | 6 +++ test/auftraegeStripe.test.js | 57 ++++++++++++++++++++++++ test/financeStripeRead.test.js | 5 +++ 3 files changed, 68 insertions(+) create mode 100644 test/auftraegeStripe.test.js diff --git a/server/services/finance/stripeFinance.js b/server/services/finance/stripeFinance.js index 0106e24..85c584e 100644 --- a/server/services/finance/stripeFinance.js +++ b/server/services/finance/stripeFinance.js @@ -3,6 +3,7 @@ import { config } from '../../config.js' import { getStripe, getOrCreateStripeCustomer, stripeCustomerFields, subscriptionIdOfInvoice } from '../stripeBilling.js' import { getDocument } from '../appwriteAdmin.js' import { addDays, berlinDate, daysBetween } from './dates.js' +import { RECHNUNGS_ARTEN } from '../auftraege/konstanten.js' import { getSettings } from './settings.js' /** @@ -87,6 +88,11 @@ export function mapInvoice(raw, { today }) { pdfUrl: raw.invoice_pdf || null, outOfBand: Boolean(metadata.bankTransactionId), bankTransactionId: metadata.bankTransactionId || '', + projektId: metadata.projektId || '', + projektName: metadata.projektName || '', + art: RECHNUNGS_ARTEN.includes(metadata.art) ? metadata.art : subscriptionIdOfInvoice(raw) ? 'abo' : 'frei', + auftragId: metadata.auftragId || '', + auftragNummer: metadata.auftragNummer || '', } } diff --git a/test/auftraegeStripe.test.js b/test/auftraegeStripe.test.js new file mode 100644 index 0000000..b617218 --- /dev/null +++ b/test/auftraegeStripe.test.js @@ -0,0 +1,57 @@ +import { test, after, beforeEach, mock } from 'node:test' +import assert from 'node:assert/strict' +import { startAppwriteMock } from './support/appwriteMock.js' +import { startFakeStripe } from './support/fakeStripe.js' +import { schemaKeys } from './support/schema.js' +import { useTestEnv } from './support/testEnv.js' +import { AUFTRAEGE_DATETIMES } from '../server/services/auftraege/schema.js' + +const aw = await startAppwriteMock({ + schema: { auftraege: schemaKeys('auftraege'), projektRechnungen: schemaKeys('projektRechnungen') }, + datetimes: AUFTRAEGE_DATETIMES, + defaultLimit: 25, +}) +const st = await startFakeStripe() +useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) +const { config } = await import('../server/config.js') +const { seedRechnungsDaten, legeAuftragAn, leereRechnungsDaten } = await import('./auftraegeRechnungenHilfe.js') +const { + mapInvoice, createProjectInvoice, rechnungsVorschau, sendInvoiceNow, voidInvoice, deleteDraft, invalidateFinanceCache, resetInvoiceCreateGuardForTests, +} = await import('../server/services/finance/stripeFinance.js') +const { getProjektRechnung } = await import('../server/services/auftraege/projektRechnungenStore.js') +const { getAuftrag } = await import('../server/services/auftraege/auftragStore.js') +const { sperreAuftrag } = await import('../server/services/auftraege/konstanten.js') +const { withLocks } = await import('../server/services/keyedLock.js') +const { berlinDate, formatDe } = await import('../server/services/finance/dates.js') + +after(() => Promise.all([aw.close(), st.close()])) +seedRechnungsDaten(aw) +config.auftraege.mode = 'on' +beforeEach(async () => { + mock.restoreAll() + await leereRechnungsDaten(aw, st) + invalidateFinanceCache() + resetInvoiceCreateGuardForTests() +}) + +const NBSP = String.fromCharCode(160) // geschütztes Leerzeichen U+00A0, wie euroDe es vor € setzt +const POSTEN = [{ description: 'Domain', quantity: 1, unitAmountCents: 1200 }] +const callsTo = (method, path) => st.calls.filter((c) => c.method === method && (path instanceof RegExp ? path.test(c.path) : c.path === path)) +const writes = () => st.calls.filter((c) => c.method !== 'GET') +const wirft = (promise, status, message) => + assert.rejects(promise, (err) => { + assert.equal(err.status, status, `Status ${err.status}: ${err.message}`) + assert.equal(err.message, message) + return true + }) + +test('mapInvoice: Projektfelder aus den Metadaten, art mit Rückfall abo oder frei', () => { + const roh = (over = {}) => ({ id: 'in_1', object: 'invoice', status: 'open', customer: 'cus_1', created: 1_790_000_000, total: 1000, metadata: {}, lines: { object: 'list', data: [] }, ...over }) + const felder = (r) => [r.projektId, r.projektName, r.art, r.auftragId, r.auftragNummer] + const heute = { today: '2026-10-06' } + assert.deepEqual(felder(mapInvoice(roh(), heute)), ['', '', 'frei', '', '']) + assert.deepEqual(felder(mapInvoice(roh({ subscription: 'sub_1' }), heute)), ['', '', 'abo', '', '']) + const metadata = { projektId: 'p1', projektName: 'Website Wolf', art: 'schluss', auftragId: 'a1', auftragNummer: 'AB-2026-0002' } + assert.deepEqual(felder(mapInvoice(roh({ metadata }), heute)), ['p1', 'Website Wolf', 'schluss', 'a1', 'AB-2026-0002']) + for (const art of ['quatsch', 'abo', '']) assert.equal(mapInvoice(roh({ metadata: { art } }), heute).art, 'frei', `art "${art}"`) +}) diff --git a/test/financeStripeRead.test.js b/test/financeStripeRead.test.js index f0996f5..0bb31c6 100644 --- a/test/financeStripeRead.test.js +++ b/test/financeStripeRead.test.js @@ -114,6 +114,11 @@ test('mapInvoice: Felder, Betraege in Cent und Berliner Kalendertage', () => { pdfUrl: 'https://invoice.stripe.test/i/in_1/pdf', outOfBand: false, bankTransactionId: '', + projektId: '', + projektName: '', + art: 'frei', + auftragId: '', + auftragNummer: '', }) })