Rechnungen: Rechnungsnummer buchen, AB abgerechnet, Storno und Löschen geben frei
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab3e6938da
commit
88a7305e41
3 files changed
+125
-6
No files matched your search
@@ -203,3 +203,86 @@ test('Schluss: zwei gleichzeitige Schlussrechnungen zur selben AB -> eine entste
|
||||
assert.equal(ergebnisse[1].reason.message, `Erst den Entwurf über 800,00${NBSP}€ senden oder löschen.`)
|
||||
assert.equal(st.invoices.size, 1)
|
||||
})
|
||||
|
||||
test('Finalisieren trägt die Rechnungsnummer in den Projektdatensatz ein (senden und sendInvoiceNow); freie Rechnungen haben keinen Datensatz', async () => {
|
||||
const gesendet = await createProjectInvoice({ customerId: 'k1', art: 'festbetrag', projektId: 'p1', items: POSTEN, senden: true })
|
||||
assert.match(gesendet.number, /^WK-\d{4}$/)
|
||||
assert.equal((await getProjektRechnung(gesendet.id)).rechnungsnummer, gesendet.number)
|
||||
const entwurf = await createProjectInvoice({ customerId: 'k1', art: 'festbetrag', projektId: 'p1', items: POSTEN, memo: 'später' })
|
||||
assert.equal((await getProjektRechnung(entwurf.id)).rechnungsnummer, '')
|
||||
const nachher = await sendInvoiceNow(entwurf.id)
|
||||
assert.equal((await getProjektRechnung(entwurf.id)).rechnungsnummer, nachher.number)
|
||||
const frei = await createProjectInvoice({ customerId: 'k1', items: POSTEN, memo: 'frei', senden: true })
|
||||
assert.equal(await getProjektRechnung(frei.id), null)
|
||||
})
|
||||
|
||||
test('Schlussrechnung: nach dem Finalisieren steht die AB auf abgerechnet; Anzahlung und Entwurf ändern den Status nicht', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
await createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, senden: true })
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'versendet')
|
||||
const entwurf = await createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id })
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'versendet')
|
||||
const gesendet = await sendInvoiceNow(entwurf.id)
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'abgerechnet')
|
||||
assert.equal((await getProjektRechnung(entwurf.id)).rechnungsnummer, gesendet.number)
|
||||
await wirft(createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id, memo: 'noch eine' }), 400, 'Die Auftragsbestätigung muss versendet oder bestätigt sein.')
|
||||
})
|
||||
|
||||
test('Storno der Schlussrechnung: Datensatz inaktiv, AB zurück auf bestaetigt (mit bestaetigtAm) bzw. versendet; Storno einer Anzahlung ändert die AB nie', async () => {
|
||||
for (const [patch, erwartet] of [[{}, 'versendet'], [{ status: 'bestaetigt', bestaetigtAm: '2026-10-07T09:00:00.000Z' }, 'bestaetigt']]) {
|
||||
await leereRechnungsDaten(aw, st)
|
||||
resetInvoiceCreateGuardForTests()
|
||||
const ab = await legeAuftragAn({}, patch)
|
||||
const anzahlung = await createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, senden: true })
|
||||
await voidInvoice(anzahlung.id)
|
||||
assert.equal((await getProjektRechnung(anzahlung.id)).aktiv, false)
|
||||
assert.equal((await getAuftrag(ab.id)).status, patch.status ?? 'versendet')
|
||||
const schluss = await createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id, senden: true })
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'abgerechnet')
|
||||
await voidInvoice(schluss.id)
|
||||
assert.equal((await getProjektRechnung(schluss.id)).aktiv, false)
|
||||
assert.equal((await getAuftrag(ab.id)).status, erwartet)
|
||||
// die stornierten Rechnungen zählen nicht: eine neue Schlussrechnung geht über die volle Summe
|
||||
assert.equal((await createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id, memo: 'neu' })).totalCents, 80000)
|
||||
}
|
||||
})
|
||||
|
||||
test('Entwurf löschen und Storno geben die Arbeitsblätter frei (Datensatz inaktiv)', async () => {
|
||||
const args = { customerId: 'k1', art: 'aufwand', projektId: 'p1', arbeitsblattIds: ['ws1'], createdBy: 'k' }
|
||||
const entwurf = await createProjectInvoice(args)
|
||||
await wirft(createProjectInvoice({ ...args, memo: 'nochmal' }), 409, 'Arbeitsblatt bereits abgerechnet (Entwurf).')
|
||||
await deleteDraft(entwurf.id)
|
||||
assert.equal((await getProjektRechnung(entwurf.id)).aktiv, false)
|
||||
const offen = await createProjectInvoice({ ...args, memo: 'nochmal', senden: true })
|
||||
assert.equal(offen.status, 'offen')
|
||||
await wirft(createProjectInvoice({ ...args, memo: 'drittes Mal' }), 409, `Arbeitsblatt bereits abgerechnet (Rechnung ${offen.number}).`)
|
||||
await voidInvoice(offen.id)
|
||||
assert.equal((await getProjektRechnung(offen.id)).aktiv, false)
|
||||
assert.equal((await createProjectInvoice({ ...args, memo: 'viertes Mal' })).status, 'entwurf')
|
||||
})
|
||||
|
||||
test('Scheitert das Buchen nach dem Finalisieren, wird die Rechnung trotzdem gesendet (nur Protokoll); erneutes Senden trägt die Nummer nach', async () => {
|
||||
const log = mock.method(console, 'error', () => {})
|
||||
aw.failNext({ collection: config.collections.projektRechnungen, method: 'PATCH', status: 500 })
|
||||
const rechnung = await createProjectInvoice({ customerId: 'k1', art: 'festbetrag', projektId: 'p1', items: POSTEN, senden: true })
|
||||
assert.equal(rechnung.status, 'offen')
|
||||
assert.equal(callsTo('POST', /\/send$/).length, 1)
|
||||
assert.ok(log.mock.calls.some((c) => String(c.arguments[0]).includes('Rechnungsnummer buchen')))
|
||||
assert.equal((await getProjektRechnung(rechnung.id)).rechnungsnummer, '')
|
||||
const erneut = await sendInvoiceNow(rechnung.id)
|
||||
assert.equal(erneut.status, 'offen')
|
||||
assert.equal((await getProjektRechnung(rechnung.id)).rechnungsnummer, rechnung.number)
|
||||
})
|
||||
|
||||
test('Die AB ändert L2 nur unter der AB-Sperre: eine laufende Statusänderung eines anderen Strangs wird nicht überholt', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const entwurf = await createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id })
|
||||
let freigeben
|
||||
const gehalten = withLocks(sperreAuftrag(ab.id), () => new Promise((resolve) => { freigeben = resolve }))
|
||||
const senden = sendInvoiceNow(entwurf.id)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'versendet', 'L2 wartet auf die Sperre der AB')
|
||||
freigeben()
|
||||
await Promise.all([gehalten, senden])
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'abgerechnet')
|
||||
})
|
||||
Reference in new issue
Block a user