Kontrolle: Storno einer Anzahlung bei bestehendem Schluss-Entwurf der AB abgewiesen
409 'Erst den Entwurf der Schlussrechnung löschen.'; das Storno einer Anzahlung laeuft unter der Kundensperre. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3ae152f12
commit
1bb172ad91
3 files changed
+38
-7
No files matched your search
@@ -409,3 +409,10 @@ export async function pruefeVorSenden({ invoiceId, art, auftragIdMetadaten = '',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Gibt es zur AB (aus dem Projektdatensatz der Rechnung, ersatzweise aus den Stripe-Metadaten) einen Entwurf der Schlussrechnung? */
|
||||
export async function hatSchlussEntwurf({ invoiceId, auftragIdMetadaten = '' }) {
|
||||
const auftragId = (await getProjektRechnung(invoiceId))?.auftragId || auftragIdMetadaten
|
||||
if (!auftragId) return false
|
||||
return (await ladeFruehere(auftragId)).some((f) => f.art === 'schluss' && f.status === 'draft')
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { getStripe, getOrCreateStripeCustomer, stripeCustomerFields, subscriptio
|
||||
import { addDays, berlinDate, daysBetween } from './dates.js'
|
||||
import { RECHNUNGS_ARTEN, AB_RECHNUNGS_ARTEN, sperreRechnung } from '../auftraege/konstanten.js'
|
||||
import { withLocks } from '../keyedLock.js'
|
||||
import { baueRechnung, ladeFestenKunden, buche, gibProjektRechnungFrei, setzeAbAbgerechnet, pruefeVorSenden } from '../auftraege/rechnungsbau.js'
|
||||
import { baueRechnung, ladeFestenKunden, buche, gibProjektRechnungFrei, setzeAbAbgerechnet, pruefeVorSenden, hatSchlussEntwurf } from '../auftraege/rechnungsbau.js'
|
||||
import { createProjektRechnung, updateProjektRechnung } from '../auftraege/projektRechnungenStore.js'
|
||||
import { getSettings } from './settings.js'
|
||||
|
||||
@@ -507,13 +507,21 @@ export async function voidInvoice(invoiceId) {
|
||||
const invoice = await readInvoice(invoiceId)
|
||||
if (invoice.source !== 'projekt') throw httpError(409, ABO_MESSAGE)
|
||||
if (invoice.status !== 'offen' && invoice.status !== 'ueberfaellig') throw httpError(409, 'Nur offene Rechnungen können storniert werden.')
|
||||
try {
|
||||
await getStripe().invoices.voidInvoice(invoice.id)
|
||||
await bucheStorniert(invoice.id, invoice.art)
|
||||
return await readInvoice(invoice.id)
|
||||
} finally {
|
||||
invalidateFinanceCache()
|
||||
const stornieren = async () => {
|
||||
// Der Schluss-Entwurf enthaelt die Minuszeile dieser Anzahlung: wuerde sie storniert, ginge er zu niedrig hinaus
|
||||
if (invoice.art === 'anzahlung' && (await hatSchlussEntwurf({ invoiceId: invoice.id, auftragIdMetadaten: invoice.auftragId }))) {
|
||||
throw httpError(409, 'Erst den Entwurf der Schlussrechnung löschen.')
|
||||
}
|
||||
try {
|
||||
await getStripe().invoices.voidInvoice(invoice.id)
|
||||
await bucheStorniert(invoice.id, invoice.art)
|
||||
return await readInvoice(invoice.id)
|
||||
} finally {
|
||||
invalidateFinanceCache()
|
||||
}
|
||||
}
|
||||
// Eine Anzahlung unter der Sperre des Kunden: das Anlegen und Senden einer Schlussrechnung laeuft nie zwischen Pruefung und Storno
|
||||
return invoice.art === 'anzahlung' && invoice.webklarCustomerId ? withLocks(sperreRechnung(invoice.webklarCustomerId), stornieren) : stornieren()
|
||||
}
|
||||
|
||||
/** Projekt-Entwurf bei Stripe loeschen, sonst status 409 (nur Entwuerfe lassen sich loeschen). */
|
||||
|
||||
@@ -104,3 +104,19 @@ test('Senden: ein gültiger Anzahlungs- oder Schluss-Entwurf geht wie bisher', a
|
||||
assert.equal((await sendInvoiceNow(s.id)).status, 'offen')
|
||||
assert.equal((await getAuftrag(ab.id)).status, 'abgerechnet')
|
||||
})
|
||||
|
||||
test('Storno: eine Anzahlung wird nicht storniert, solange zur AB ein Schluss-Entwurf besteht (409); nach dem Löschen des Entwurfs geht es', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const a = await anzahlung(ab, 50, { senden: true })
|
||||
const s = await schluss(ab)
|
||||
await assert.rejects(voidInvoice(a.id), (err) => err.status === 409 && err.message === 'Erst den Entwurf der Schlussrechnung löschen.')
|
||||
assert.equal(st.invoices.get(a.id).status, 'open')
|
||||
await deleteDraft(s.id)
|
||||
assert.equal((await voidInvoice(a.id)).status, 'storniert')
|
||||
})
|
||||
|
||||
test('Storno: eine Anzahlung ohne Schluss-Entwurf lässt sich stornieren', async () => {
|
||||
const ab = await legeAuftragAn()
|
||||
const a = await anzahlung(ab, 50, { senden: true })
|
||||
assert.equal((await voidInvoice(a.id)).status, 'storniert')
|
||||
})
|
||||
Reference in new issue
Block a user