AB-Kern: Neue Fassung und Ersetzen der alten AB beim Senden
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0da5b30119
commit
cf87a7e1e0
2 files changed
+156
-2
No files matched your search
@@ -119,3 +119,108 @@ test('erinnereAuftrag wartet auf die Sperre der AB und prüft den Stand danach (
|
||||
assert.equal(calls.mail.length, 1) // nur die Mail des ersten Sendens
|
||||
assert.equal((await getAuftrag(a.id)).tokenHash, a.tokenHash)
|
||||
})
|
||||
|
||||
test('neueFassung: Kopie als Entwurf mit ersetztId, ohne Nummer, Schnappschuss, PDF und Token; die alte AB bleibt unberührt', async () => {
|
||||
const alt = await gesendet(baueDeps().deps)
|
||||
const neu = await neueFassung(alt.id, { von: 'justin@webklar.com' })
|
||||
assert.deepEqual([neu.status, neu.ersetztId, neu.nummer, neu.kunde, neu.erstelltVon], ['entwurf', alt.id, '', null, 'justin@webklar.com'])
|
||||
assert.ok(!neu.pdfDatei && !neu.tokenHash && !neu.gesendetAm)
|
||||
for (const feld of ['kundenId', 'projektId', 'titel', 'zeitquelle', 'art', 'positionen', 'stundensatzCents', 'schaetzungMinuten', 'summeCents', 'zahlungsplan', 'zahlungszielTage', 'auftragVom', 'auftragWeg', 'startAm', 'fertigBis']) {
|
||||
assert.deepEqual(neu[feld], alt[feld], feld)
|
||||
}
|
||||
assert.deepEqual(await getAuftrag(alt.id), alt)
|
||||
})
|
||||
|
||||
test('neueFassung: nur für versendete, bestätigte und abgerechnete (409); je AB nur ein offener Entwurf', async () => {
|
||||
const alt = await gesendet(baueDeps().deps)
|
||||
for (const status of ['entwurf', 'storniert', 'ersetzt', 'widerrufen']) {
|
||||
await updateAuftrag(alt.id, { status })
|
||||
await abgelehnt(neueFassung(alt.id, { von: VON }), 409, MELDUNG.keineNeueFassung, status)
|
||||
}
|
||||
for (const status of ['bestaetigt', 'abgerechnet', 'versendet']) {
|
||||
await updateAuftrag(alt.id, { status })
|
||||
const neu = await neueFassung(alt.id, { von: VON })
|
||||
await abgelehnt(neueFassung(alt.id, { von: VON }), 409, MELDUNG.schonEntwurf, status)
|
||||
await loescheEntwurf(neu.id)
|
||||
}
|
||||
await abgelehnt(neueFassung('a_gibtsnicht', { von: VON }), 404, MELDUNG.auftragFehlt)
|
||||
})
|
||||
|
||||
test('Senden der neuen Fassung: die alte AB wird ersetzt, ihre Rechnungen wandern mit, der alte Link bleibt auffindbar', async () => {
|
||||
const { deps } = baueDeps()
|
||||
const alt = await gesendet(deps)
|
||||
await createProjektRechnung('in_Anzahlung1', {
|
||||
kundenId: KUNDE_ID, projektId: PROJEKT_ID, auftragId: alt.id, art: 'anzahlung', betragCents: 40000, arbeitsblattIds: [],
|
||||
rechnungsnummer: 'WEBKLAR-0007', aktiv: true, erstelltVon: VON, erstelltAm: new Date().toISOString(),
|
||||
})
|
||||
const entwurf = await neueFassung(alt.id, { von: VON })
|
||||
await aendereEntwurf(entwurf.id, { positionen: [{ beschreibung: 'Webdesign Startseite', menge: 1, einzelpreisCents: 70000 }] })
|
||||
const { auftrag: neu } = await sendeAuftrag(entwurf.id, { von: VON, deps, jetzt: new Date('2026-10-07T10:00:00Z') })
|
||||
assert.deepEqual([neu.nummer, neu.status, neu.ersetztId, neu.summeCents], ['AB-2026-0002', 'versendet', alt.id, 70000])
|
||||
const altNachher = await getAuftrag(alt.id)
|
||||
assert.deepEqual([altNachher.status, altNachher.ersetztDurchId, altNachher.tokenHash], ['ersetzt', neu.id, alt.tokenHash])
|
||||
assert.equal((await findAuftragByTokenHash(alt.tokenHash)).id, alt.id)
|
||||
assert.equal((await getProjektRechnung('in_Anzahlung1')).auftragId, neu.id)
|
||||
})
|
||||
|
||||
test('Senden der neuen Fassung: ist die alte AB inzwischen storniert, gibt es 409 vor der Nummer', async () => {
|
||||
const { deps, calls } = baueDeps()
|
||||
const alt = await gesendet(deps)
|
||||
const neu = await neueFassung(alt.id, { von: VON })
|
||||
await updateAuftrag(alt.id, { status: 'storniert' })
|
||||
await abgelehnt(sendeAuftrag(neu.id, { von: VON, deps, jetzt: JETZT }), 409, MELDUNG.ersetzteUngueltig)
|
||||
assert.equal(calls.pdf.length, 1) // nur die PDF der ersten AB
|
||||
const danach = await getAuftrag(neu.id)
|
||||
assert.deepEqual([danach.status, danach.nummer], ['entwurf', ''])
|
||||
assert.equal((await getAuftrag(alt.id)).status, 'storniert')
|
||||
})
|
||||
|
||||
test('Senden der neuen Fassung: scheitert das Speichern erst nach dem Ersetzen der alten AB, führt die Wiederholung alles zu Ende', async () => {
|
||||
mock.method(console, 'error', () => {})
|
||||
const { deps } = baueDeps()
|
||||
const alt = await gesendet(deps)
|
||||
await createProjektRechnung('in_Anzahlung2', {
|
||||
kundenId: KUNDE_ID, projektId: PROJEKT_ID, auftragId: alt.id, art: 'anzahlung', betragCents: 40000, arbeitsblattIds: [],
|
||||
rechnungsnummer: 'WEBKLAR-0008', aktiv: true, erstelltVon: VON, erstelltAm: new Date().toISOString(),
|
||||
})
|
||||
const neu = await neueFassung(alt.id, { von: VON })
|
||||
// erster PATCH auf auftraege: alte AB ersetzt (geht durch); zweiter PATCH: Speichern der neuen AB (scheitert)
|
||||
aw.failNext({ collection: config.collections.auftraege, method: 'PATCH', status: 500, after: 1 })
|
||||
await assert.rejects(sendeAuftrag(neu.id, { von: VON, deps, jetzt: JETZT }), (err) => err.status === 500)
|
||||
assert.equal((await getAuftrag(neu.id)).status, 'entwurf')
|
||||
assert.deepEqual(dateienIn(uploadDir), [alt.pdfDatei]) // die Datei der neuen AB wurde wieder entfernt
|
||||
const { auftrag } = await sendeAuftrag(neu.id, { von: VON, deps, jetzt: JETZT })
|
||||
assert.equal(auftrag.nummer, 'AB-2026-0002')
|
||||
assert.deepEqual([(await getAuftrag(alt.id)).status, (await getAuftrag(alt.id)).ersetztDurchId], ['ersetzt', neu.id])
|
||||
assert.equal((await getProjektRechnung('in_Anzahlung2')).auftragId, neu.id)
|
||||
})
|
||||
|
||||
test('Senden der neuen Fassung wartet auf die Sperre der alten AB und prüft sie danach noch einmal (L5 widerruft gerade)', async () => {
|
||||
const { deps, calls } = baueDeps()
|
||||
const alt = await gesendet(deps)
|
||||
const neu = await neueFassung(alt.id, { von: VON })
|
||||
const lauf = await unterFremderSperre(alt.id, {
|
||||
aktion: () => sendeAuftrag(neu.id, { von: VON, deps, jetzt: JETZT }),
|
||||
bereit: () => calls.pdf.length === 2, // das PDF der neuen AB ist fertig, als Nächstes kommt das Ersetzen
|
||||
waehrend: async () => {
|
||||
assert.equal((await getAuftrag(alt.id)).status, 'versendet') // noch nicht ersetzt
|
||||
await updateAuftrag(alt.id, { status: 'widerrufen', widerrufenAm: new Date().toISOString() })
|
||||
},
|
||||
})
|
||||
assert.deepEqual([lauf.fehler?.status, lauf.fehler?.message], [409, MELDUNG.ersetzteUngueltig])
|
||||
const danach = await getAuftrag(neu.id)
|
||||
assert.deepEqual([danach.status, danach.nummer], ['entwurf', ''])
|
||||
assert.deepEqual(dateienIn(uploadDir), [alt.pdfDatei]) // die Datei der neuen AB wurde wieder entfernt
|
||||
assert.equal((await getAuftrag(alt.id)).status, 'widerrufen')
|
||||
})
|
||||
|
||||
test('Senden: zeigt die ersetztId eines Entwurfs auf ihn selbst (beschädigte Daten), gibt es 409 statt eines Hängers an der eigenen Sperre', { timeout: 5000 }, async () => {
|
||||
const { deps, calls } = baueDeps()
|
||||
const entwurf = await legeEntwurfAn(festpreisEingabe(), { von: VON })
|
||||
await updateAuftrag(entwurf.id, { ersetztId: entwurf.id })
|
||||
await abgelehnt(sendeAuftrag(entwurf.id, { von: VON, deps, jetzt: JETZT }), 409, MELDUNG.ersetzteUngueltig)
|
||||
assert.deepEqual([calls.pdf.length, calls.mail.length], [0, 0])
|
||||
const danach = await getAuftrag(entwurf.id)
|
||||
assert.deepEqual([danach.status, danach.nummer], ['entwurf', ''])
|
||||
assert.deepEqual(dateienIn(uploadDir), [])
|
||||
})
|
||||
Reference in new issue
Block a user