Projektkonto: Detail mit ABs, Rechnungen und offenen Arbeitsblättern
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8032aa67c6
commit
7cec85f888
2 files changed
+118
No files matched your search
@@ -584,3 +584,78 @@ test('kunde: Firmenname vor Name, Kundenart nur aus der Liste, unbekannter Kunde
|
||||
assert.deepEqual((await projektkontenFuerKunde('c1')).kunde, { id: 'c1', name: 'Kunde Eins', kundenart: '' })
|
||||
await assert.rejects(projektkontenFuerKunde('gibtsnicht'), { status: 404, message: 'Kunde nicht gefunden.' })
|
||||
})
|
||||
|
||||
// ---------- Projektkonto-Detail (Task 5) ----------
|
||||
|
||||
test('Detail: Konto, ABs, Rechnungen (neueste zuerst, Entwürfe dabei, Inaktive nicht) und offene Arbeitsblätter', async () => {
|
||||
seedTicket('t1', '100001')
|
||||
seedProjekt('p1', { ticketId: 't1' })
|
||||
seedBlatt('w1', 't1', 60) // in Rechnung in_2
|
||||
seedBlatt('w2', 't1', 30)
|
||||
seedBlatt('w3', 't1', 45, { countsToPlan: true }) // Abo-Zeit
|
||||
seedBlatt('w4', 't1', 15, { isComment: true }) // Kommentar
|
||||
seedBlatt('w5', 't1', 15, { startDate: '05.10.2026', endDate: '05.10.2026' })
|
||||
seedAuftrag('a1', { projektId: 'p1', summeCents: 80000 })
|
||||
seedAuftrag('a2', { projektId: 'p1', status: 'entwurf', nummer: '', laufnummer: null, gesendetAm: null })
|
||||
seedPr('in_1', { auftragId: 'a1', art: 'anzahlung', rechnungsnummer: 'WEBKLAR-0001' })
|
||||
seedPr('in_2', { art: 'aufwand', arbeitsblattIds: ['w1'], rechnungsnummer: 'WEBKLAR-0002' })
|
||||
seedPr('in_3', { aktiv: false })
|
||||
seedPr('in_4', { art: 'festbetrag' })
|
||||
st.invoices.set('in_1', stripeInvoice('in_1', { status: 'open', total: 40000, number: 'WEBKLAR-0001', created: '2026-10-01' }))
|
||||
st.invoices.set('in_2', stripeInvoice('in_2', { status: 'paid', total: 10000, paid: 10000, number: 'WEBKLAR-0002', created: '2026-10-03' }))
|
||||
st.invoices.set('in_3', stripeInvoice('in_3', { status: 'void', total: 5000, number: 'WEBKLAR-0003', created: '2026-10-02' }))
|
||||
st.invoices.set('in_4', stripeInvoice('in_4', { status: 'draft', total: 5000, created: '2026-10-04' }))
|
||||
|
||||
const detail = await projektkontoDetail('p1')
|
||||
assert.deepEqual(Object.keys(detail).sort(), ['auftraege', 'konto', 'offeneArbeitsblaetter', 'rechnungen', 'stripeOk'])
|
||||
assert.equal(detail.stripeOk, true)
|
||||
const { konto } = detail
|
||||
assert.equal(konto.projektId, 'p1')
|
||||
assert.deepEqual(konto.vereinbart, { festpreisCents: 80000, aufwand: null })
|
||||
assert.deepEqual([konto.berechnetCents, konto.bezahltCents, konto.offenCents], [50000, 10000, 40000])
|
||||
assert.deepEqual(konto.zeit, { minuten: 105, aboMinuten: 45, offeneMinuten: 45, satzCents: 10000, wertCents: 17500, offenerWertCents: 7500 })
|
||||
assert.deepEqual(detail.rechnungen, [
|
||||
{ id: 'in_4', number: '', status: 'entwurf', createdDate: '2026-10-04', totalCents: 5000, paidCents: 0, art: 'festbetrag', auftragId: '' },
|
||||
{ id: 'in_2', number: 'WEBKLAR-0002', status: 'bezahlt', createdDate: '2026-10-03', totalCents: 10000, paidCents: 10000, art: 'aufwand', auftragId: '' },
|
||||
{ id: 'in_1', number: 'WEBKLAR-0001', status: 'offen', createdDate: '2026-10-01', totalCents: 40000, paidCents: 0, art: 'anzahlung', auftragId: 'a1' },
|
||||
])
|
||||
// sortiert nach Datum (w5 am 05.10., w2 am 06.10.); Abo-Zeit, Kommentar und Berechnetes fehlen
|
||||
assert.deepEqual(detail.offeneArbeitsblaetter.map((eintrag) => eintrag.id), ['w5', 'w2'])
|
||||
assert.deepEqual(detail.auftraege.map((auftrag) => [auftrag.id, auftrag.status]).sort(), [['a1', 'versendet'], ['a2', 'entwurf']])
|
||||
for (const auftrag of detail.auftraege) {
|
||||
for (const feld of ['tokenHash', 'pdfDatei', 'bestaetigtPdfSha256']) assert.ok(!(feld in auftrag), `${feld} darf nicht in der Antwort stehen`)
|
||||
}
|
||||
})
|
||||
|
||||
test('Detail: unbekanntes Projekt und Projekt ohne Kunde -> 404', async () => {
|
||||
seedProjekt('p9', { customerId: '' })
|
||||
await assert.rejects(projektkontoDetail('gibtsnicht'), { status: 404, message: 'Projekt nicht gefunden.' })
|
||||
await assert.rejects(projektkontoDetail('p9'), { status: 404, message: 'Projekt nicht gefunden.' })
|
||||
})
|
||||
|
||||
test('Detail bei Stripe-Ausfall: stripeOk false, keine Rechnungen, Beträge 0, Arbeitsblätter und ABs bleiben', async () => {
|
||||
mock.method(console, 'warn', () => {})
|
||||
seedTicket('t1', '100001')
|
||||
seedProjekt('p1', { ticketId: 't1' })
|
||||
seedBlatt('w1', 't1', 60)
|
||||
seedAuftrag('a1', { projektId: 'p1', summeCents: 80000 })
|
||||
seedPr('in_1', { auftragId: 'a1', art: 'anzahlung' })
|
||||
st.failNext({ method: 'GET', path: '/v1/invoices', status: 400, message: 'kaputt' })
|
||||
const detail = await projektkontoDetail('p1')
|
||||
assert.equal(detail.stripeOk, false)
|
||||
assert.deepEqual(detail.rechnungen, [])
|
||||
assert.deepEqual([detail.konto.berechnetCents, detail.konto.bezahltCents, detail.konto.offenCents], [0, 0, null])
|
||||
assert.deepEqual(detail.offeneArbeitsblaetter.map((eintrag) => eintrag.id), ['w1'])
|
||||
assert.deepEqual(detail.auftraege.map((auftrag) => auftrag.id), ['a1'])
|
||||
})
|
||||
|
||||
test('Detail bei ausgeschaltetem Schalter: keine ABs, Vereinbart leer, ABs werden nicht gelesen', async () => {
|
||||
seedProjekt('p1')
|
||||
seedAuftrag('a1', { projektId: 'p1', summeCents: 80000 })
|
||||
config.auftraege.mode = 'off'
|
||||
aw.resetListCalls()
|
||||
const detail = await projektkontoDetail('p1')
|
||||
assert.deepEqual(detail.auftraege, [])
|
||||
assert.equal(detail.konto.vereinbart.festpreisCents, null)
|
||||
assert.equal(aw.listCalls(config.collections.auftraege).length, 0)
|
||||
})
|
||||
Reference in new issue
Block a user