Kontrolle: Senden eines Anzahlungs- oder Schluss-Entwurfs prueft die AB unter der Kundensperre erneut
Unter sperreRechnung(kundenId): AB noch versendet oder bestaetigt, bei Schluss keine andere festgeschriebene Schlussrechnung, bei Anzahlung Summe aller festgeschriebenen Rechnungen plus diese nicht ueber der AB-Summe, sonst 409 und nichts wird finalisiert. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57d1ab82c6
commit
e3ae152f12
3 files changed
+99
-4
No files matched your search
@@ -15,7 +15,7 @@ const aw = await startAppwriteMock({
|
||||
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 { seedRechnungsDaten, legeAuftragAn, legeRechnungAn, leereRechnungsDaten } = await import('./auftraegeRechnungenHilfe.js')
|
||||
const { createProjectInvoice, sendInvoiceNow, voidInvoice, deleteDraft, invalidateFinanceCache, resetInvoiceCreateGuardForTests } = await import('../server/services/finance/stripeFinance.js')
|
||||
const { getAuftrag, updateAuftrag } = await import('../server/services/auftraege/auftragStore.js')
|
||||
const { umhaengen } = await import('../server/services/auftraege/projektRechnungenStore.js')
|
||||
@@ -30,8 +30,9 @@ beforeEach(async () => {
|
||||
})
|
||||
|
||||
const letzterEntwurf = () => st.calls.filter((c) => c.method === 'POST' && c.path === '/v1/invoices').at(-1)
|
||||
let zaehler = 0
|
||||
const anzahlung = (ab, prozent, extra = {}) =>
|
||||
createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, projektId: 'p1', anzahlung: { prozent, bezeichnung: `Anzahlung ${prozent}` }, ...extra })
|
||||
createProjectInvoice({ customerId: 'k1', art: 'anzahlung', auftragId: ab.id, projektId: 'p1', anzahlung: { prozent, bezeichnung: `Anzahlung ${++zaehler}` }, ...extra })
|
||||
const schluss = (ab, extra = {}) => createProjectInvoice({ customerId: 'k1', art: 'schluss', auftragId: ab.id, projektId: 'p1', ...extra })
|
||||
|
||||
test('Zahlungsziel: Anzahlung und Schluss ohne eigene Angabe nehmen das Zahlungsziel der AB, eine Angabe gilt vor der AB', async () => {
|
||||
@@ -50,3 +51,56 @@ test('Zahlungsziel: ohne AB-Bezug (frei, festbetrag) bleibt es bei 14 Tagen', as
|
||||
await createProjectInvoice({ customerId: 'k1', items: [{ description: 'Domain', quantity: 1, unitAmountCents: 1200 }] })
|
||||
assert.equal(letzterEntwurf().body.days_until_due, '14')
|
||||
})
|
||||
|
||||
const ENTWURF_ID = /^in_/
|
||||
const finalisiert = (id) => st.calls.some((c) => c.method === 'POST' && c.path === `/v1/invoices/${id}/finalize`)
|
||||
|
||||
test('Senden: zwei Anzahlungs-Entwürfe à 60 % einer 800-€-AB – nur der erste geht, der zweite 409 und nichts finalisiert', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const a = await anzahlung(ab, 60)
|
||||
const b = await anzahlung(ab, 60)
|
||||
assert.match(a.id, ENTWURF_ID)
|
||||
await sendInvoiceNow(a.id)
|
||||
st.calls.length = 0
|
||||
await assert.rejects(sendInvoiceNow(b.id), (err) => err.status === 409 && /übersteig|Auftragssumme/.test(err.message))
|
||||
assert.equal(finalisiert(b.id), false)
|
||||
assert.equal(st.invoices.get(b.id).status, 'draft')
|
||||
})
|
||||
|
||||
test('Senden: parallel gesendet geht von zwei 60-%-Entwürfen genau einer', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const a = await anzahlung(ab, 60)
|
||||
const b = await anzahlung(ab, 60)
|
||||
const ergebnisse = await Promise.allSettled([sendInvoiceNow(a.id), sendInvoiceNow(b.id)])
|
||||
assert.deepEqual(ergebnisse.map((e) => e.status).sort(), ['fulfilled', 'rejected'])
|
||||
assert.equal([a.id, b.id].filter((id) => st.invoices.get(id).status !== 'draft').length, 1)
|
||||
})
|
||||
|
||||
test('Senden: ein Anzahlungs-Entwurf zu einer stornierten, widerrufenen oder ersetzten AB wird nicht gesendet', async () => {
|
||||
for (const status of ['storniert', 'widerrufen', 'ersetzt']) {
|
||||
const ab = await legeAuftragAn({}, { nummer: `AB-2026-${status}` })
|
||||
const entwurf = await anzahlung(ab, 30, { projektId: 'p1', anzahlung: { prozent: 30, bezeichnung: `A ${status}` } })
|
||||
await updateAuftrag(ab.id, { status })
|
||||
await assert.rejects(sendInvoiceNow(entwurf.id), (err) => err.status === 409 && /nicht mehr gültig/.test(err.message), status)
|
||||
assert.equal(finalisiert(entwurf.id), false, status)
|
||||
await deleteDraft(entwurf.id)
|
||||
await updateAuftrag(ab.id, { status: 'storniert' })
|
||||
}
|
||||
})
|
||||
|
||||
test('Senden: ein Schluss-Entwurf wird nicht gesendet, wenn zur AB schon eine andere Schlussrechnung festgeschrieben ist', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const s = await schluss(ab)
|
||||
await legeRechnungAn(st, 'in_fremd1', { status: 'open', total: 80000, art: 'schluss', auftragId: ab.id })
|
||||
await assert.rejects(sendInvoiceNow(s.id), (err) => err.status === 409)
|
||||
assert.equal(finalisiert(s.id), false)
|
||||
})
|
||||
|
||||
test('Senden: ein gültiger Anzahlungs- oder Schluss-Entwurf geht wie bisher', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const a = await anzahlung(ab, 50)
|
||||
assert.equal((await sendInvoiceNow(a.id)).status, 'offen')
|
||||
const s = await schluss(ab)
|
||||
assert.equal((await sendInvoiceNow(s.id)).status, 'offen')
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'abgerechnet')
|
||||
})
|
||||
Reference in new issue
Block a user