Rechnungen: Projektrechnungen bei Stripe (Zusatzfelder, A4, Minuszeilen, Projektdatensatz) und Vorschau
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c116c3cb4f
commit
d264f2af5a
3 files changed
+151
-63
No files matched your search
@@ -55,3 +55,111 @@ test('mapInvoice: Projektfelder aus den Metadaten, art mit Rückfall abo oder fr
|
||||
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}"`)
|
||||
})
|
||||
|
||||
test('createProjectInvoice frei: A4 für jede Rechnung, Art in den Metadaten, keine Zusatzfelder, kein Projektdatensatz', async () => {
|
||||
const rechnung = await createProjectInvoice({ customerId: 'k1', items: POSTEN, createdBy: 'kenso@webklar.com' })
|
||||
const [create] = callsTo('POST', '/v1/invoices')
|
||||
assert.equal(create.body['rendering[pdf][page_size]'], 'a4')
|
||||
assert.equal(create.body['metadata[art]'], 'frei')
|
||||
assert.equal(create.body['metadata[webklarCustomerId]'], 'k1')
|
||||
assert.ok(!Object.keys(create.body).some((k) => k.startsWith('custom_fields')))
|
||||
assert.ok(!Object.keys(create.body).some((k) => /^metadata\[(projektId|projektName|auftragId|auftragNummer)\]$/.test(k)))
|
||||
assert.deepEqual([rechnung.art, rechnung.status, rechnung.totalCents, rechnung.projektId], ['frei', 'entwurf', 1200, ''])
|
||||
assert.equal(aw.docs(config.collections.projektRechnungen).size, 0)
|
||||
})
|
||||
|
||||
test('createProjectInvoice festbetrag: Zusatzfeld Projekt, Metadaten und ein Projektdatensatz mit der Rechnungs-ID', async () => {
|
||||
const items = [{ description: 'Erstellung der Website anelia-wolf.com, Pauschalpreis laut Absprache', quantity: 1, unitAmountCents: 50000 }]
|
||||
const rechnung = await createProjectInvoice({ customerId: 'k1', art: 'festbetrag', projektId: 'p1', items, createdBy: 'kenso@webklar.com' })
|
||||
const [create] = callsTo('POST', '/v1/invoices')
|
||||
assert.equal(create.body['custom_fields[0][name]'], 'Projekt')
|
||||
assert.equal(create.body['custom_fields[0][value]'], 'Website anelia-wolf.com')
|
||||
assert.equal(create.body['custom_fields[1][name]'], undefined)
|
||||
assert.equal(create.body['rendering[pdf][page_size]'], 'a4')
|
||||
assert.deepEqual([create.body['metadata[art]'], create.body['metadata[projektId]'], create.body['metadata[projektName]']], ['festbetrag', 'p1', 'Website anelia-wolf.com'])
|
||||
assert.deepEqual([rechnung.art, rechnung.projektId, rechnung.projektName, rechnung.totalCents], ['festbetrag', 'p1', 'Website anelia-wolf.com', 50000])
|
||||
const doc = await getProjektRechnung(rechnung.id)
|
||||
assert.deepEqual(
|
||||
[doc.invoiceId, doc.kundenId, doc.projektId, doc.auftragId, doc.art, doc.betragCents, doc.rechnungsnummer, doc.aktiv, doc.erstelltVon],
|
||||
[rechnung.id, 'k1', 'p1', '', 'festbetrag', 50000, '', true, 'kenso@webklar.com']
|
||||
)
|
||||
})
|
||||
|
||||
test('createProjectInvoice aufwand: eine Zeile je Tag bei Stripe, Blätter im Projektdatensatz, Zeitraum als Zusatzfeld', async () => {
|
||||
const rechnung = await createProjectInvoice({ customerId: 'k1', art: 'aufwand', projektId: 'p1', arbeitsblattIds: ['ws1', 'ws2', 'ws3'], createdBy: 'k' })
|
||||
const items = callsTo('POST', '/v1/invoiceitems')
|
||||
assert.deepEqual(items.map((c) => [c.body.quantity, c.body.unit_amount_decimal, c.body.amount]), [['1', '22500', undefined], ['1', '10000', undefined]])
|
||||
assert.ok(items[0].body.description.startsWith('06.10.2026: '))
|
||||
const [create] = callsTo('POST', '/v1/invoices')
|
||||
assert.deepEqual([0, 1].map((i) => create.body[`custom_fields[${i}][name]`]), ['Projekt', 'Leistungszeitraum'])
|
||||
assert.equal(create.body['custom_fields[1][value]'], '06.10.–07.10.2026')
|
||||
assert.equal(rechnung.totalCents, 32500)
|
||||
assert.deepEqual((await getProjektRechnung(rechnung.id)).arbeitsblattIds, ['ws1', 'ws2', 'ws3'])
|
||||
})
|
||||
|
||||
test('createProjectInvoice schluss: positive Zeilen mit Menge und Einzelpreis, die Minuszeile nur mit amount (ohne quantity)', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const anzahlung = await createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, senden: true, createdBy: 'k' })
|
||||
assert.deepEqual([anzahlung.totalCents, anzahlung.art, anzahlung.auftragNummer, anzahlung.status], [40000, 'anzahlung', 'AB-2026-0002', 'offen'])
|
||||
st.calls.length = 0
|
||||
const schluss = await createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id, createdBy: 'k' })
|
||||
const [create] = callsTo('POST', '/v1/invoices')
|
||||
assert.deepEqual(
|
||||
[0, 1].map((i) => [create.body[`custom_fields[${i}][name]`], create.body[`custom_fields[${i}][value]`]]),
|
||||
[['Projekt', 'Website anelia-wolf.com'], ['Auftrag', 'AB-2026-0002 vom 06.10.2026']]
|
||||
)
|
||||
const items = callsTo('POST', '/v1/invoiceitems')
|
||||
const kunde = items[0].body.customer
|
||||
assert.deepEqual(items.map((c) => c.body), [
|
||||
{ customer: kunde, invoice: schluss.id, description: 'Website', quantity: '1', unit_amount_decimal: '60000', currency: 'eur' },
|
||||
{ customer: kunde, invoice: schluss.id, description: 'Texte', quantity: '2', unit_amount_decimal: '10000', currency: 'eur' },
|
||||
{ customer: kunde, invoice: schluss.id, description: `abzüglich Anzahlung, Rechnung ${anzahlung.number} vom ${formatDe(berlinDate())}`, amount: '-40000', currency: 'eur' },
|
||||
])
|
||||
assert.deepEqual([schluss.totalCents, schluss.art, schluss.status], [40000, 'schluss', 'entwurf'])
|
||||
})
|
||||
|
||||
test('createProjectInvoice: scheitert der Projektdatensatz, wird der Entwurf bei Stripe gelöscht und nichts bleibt zurück', async () => {
|
||||
const eingabe = { customerId: 'k1', art: 'festbetrag', projektId: 'p1', items: POSTEN, createdBy: 'k' }
|
||||
aw.failNext({ collection: config.collections.projektRechnungen, method: 'POST', status: 500 })
|
||||
await assert.rejects(createProjectInvoice(eingabe))
|
||||
assert.equal(st.invoices.size, 0)
|
||||
assert.equal(callsTo('DELETE', /^\/v1\/invoices\/in_/).length, 1)
|
||||
assert.equal(aw.docs(config.collections.projektRechnungen).size, 0)
|
||||
// der Schutz vor doppeltem Anlegen ist wieder frei
|
||||
assert.equal((await createProjectInvoice(eingabe)).status, 'entwurf')
|
||||
})
|
||||
|
||||
test('createProjectInvoice: der Fingerabdruck unterscheidet Art, Projekt, Auftrag und Anzahlung, gleicher Inhalt bleibt 30 Sekunden gesperrt', async () => {
|
||||
const basis = { customerId: 'k1', items: POSTEN }
|
||||
const GESPERRT = 'Diese Rechnung wird gerade schon erstellt.'
|
||||
await createProjectInvoice(basis)
|
||||
await createProjectInvoice({ ...basis, art: 'festbetrag', projektId: 'p1' })
|
||||
await createProjectInvoice({ ...basis, art: 'festbetrag', projektId: 'p3' })
|
||||
await wirft(createProjectInvoice({ ...basis, art: 'festbetrag', projektId: 'p3' }), 409, GESPERRT)
|
||||
const ab = await legeAuftragAn()
|
||||
await createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, anzahlung: { prozent: 50 } })
|
||||
await createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, anzahlung: { prozent: 25 } })
|
||||
await wirft(createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, anzahlung: { prozent: 25 } }), 409, GESPERRT)
|
||||
assert.equal(st.invoices.size, 5)
|
||||
})
|
||||
|
||||
test('rechnungsVorschau: Positionen, Summe, Zeitraum und Zusatzfelder ohne Stripe-Schreibzugriff und ohne Projektdatensatz', async () => {
|
||||
const v = await rechnungsVorschau({ customerId: 'k1', art: 'aufwand', projektId: 'p1' })
|
||||
assert.deepEqual(Object.keys(v).sort(), ['customFields', 'leistungszeitraum', 'positionen', 'summeCents'])
|
||||
assert.equal(v.summeCents, 32500)
|
||||
assert.deepEqual(v.positionen.map((p) => p.schluessel), ['2026-10-06', '2026-10-07'])
|
||||
assert.equal(v.leistungszeitraum, '06.10.–07.10.2026')
|
||||
assert.deepEqual(v.customFields.map((f) => f.name), ['Projekt', 'Leistungszeitraum'])
|
||||
assert.equal(writes().length, 0)
|
||||
assert.equal(aw.docs(config.collections.projektRechnungen).size, 0)
|
||||
})
|
||||
|
||||
test('rechnungsVorschau: dieselben Prüfungen wie das Anlegen; Anzahlung ohne Angaben zeigt den Standardwert', async () => {
|
||||
await createProjectInvoice({ customerId: 'k1', art: 'aufwand', projektId: 'p1', arbeitsblattIds: ['ws1'] })
|
||||
await wirft(rechnungsVorschau({ customerId: 'k1', art: 'aufwand', projektId: 'p1', arbeitsblattIds: ['ws1', 'ws3'] }), 409, 'Arbeitsblatt bereits abgerechnet (Entwurf).')
|
||||
await wirft(rechnungsVorschau({ customerId: 'k1', art: 'aufwand', projektId: 'p2' }), 404, 'Projekt nicht gefunden.')
|
||||
const ab = await legeAuftragAn()
|
||||
const v = await rechnungsVorschau({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id })
|
||||
assert.deepEqual([v.summeCents, v.positionen[0].schluessel], [40000, 'anzahlung'])
|
||||
assert.equal(aw.docs(config.collections.projektRechnungen).size, 1, 'nur der Entwurf von oben')
|
||||
})
|
||||
Reference in new issue
Block a user