Finanzen: Rueckfrage vor dem Senden, Anschrift des Kunden pruefen, Fehlermeldungen im Bild
Senden einer Rechnung (Dialog und Entwurf in der Tabelle) fragt vorher: "Rechnung fuer
<Kunde> ueber <Betrag> jetzt festschreiben und an <E-Mail> senden? Danach laesst sie sich
nur noch stornieren."
Fehlt dem Kunden Strasse, PLZ oder Ort, zeigen Rechnungsdialog und Kundenansicht die
Warnung "Adresse des Kunden unvollstaendig ..." mit Weg zur Uebersicht, und Senden ist
ueber 250 € gesperrt (§ 14 Abs. 4 Nr. 1 UStG, Kleinbetragsrechnung); Entwuerfe bleiben
moeglich. Reine Helfer addressComplete und invoiceSendGate samt Tests.
Fehlermeldungen in Rechnungs- und Zuordnen-Dialog werden per scrollIntoView
({ block: 'nearest' }) in den sichtbaren Bereich geholt.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1e72faf97c
commit
72f07410fe
9 files changed
+240
-15
No files matched your search
@@ -20,7 +20,6 @@ const {
|
||||
forecastRows,
|
||||
forecastNotes,
|
||||
taxView,
|
||||
TAX_NOTE,
|
||||
aboView,
|
||||
productLabel,
|
||||
aboStatus,
|
||||
@@ -34,6 +33,7 @@ const {
|
||||
invoiceActions,
|
||||
invoiceRows,
|
||||
invoiceConfirmText,
|
||||
invoiceSendConfirmText,
|
||||
invoiceDoneMessage,
|
||||
isFinalizedNotSent,
|
||||
finalizedFilter,
|
||||
@@ -48,6 +48,11 @@ const {
|
||||
customerSuggestions,
|
||||
resolveCustomerText,
|
||||
customerInvoicesGate,
|
||||
SMALL_INVOICE_MAX_CENTS,
|
||||
ADDRESS_INCOMPLETE_TEXT,
|
||||
addressComplete,
|
||||
invoiceSendGate,
|
||||
TAX_NOTE,
|
||||
sortTransactions,
|
||||
strongestSuggestion,
|
||||
suggestionText,
|
||||
@@ -790,10 +795,41 @@ test('invoiceConfirmText: Löschen, Stornieren, Erinnerung; sonst leer', () => {
|
||||
assert.equal(invoiceConfirmText('delete', inv), 'Den Entwurf für Kunde Zwei über 50,00 € wirklich löschen? Das lässt sich nicht rückgängig machen.')
|
||||
assert.equal(invoiceConfirmText('void', inv), 'Die Rechnung WK-0006 an Kunde Zwei über 50,00 € wirklich stornieren? Das lässt sich nicht rückgängig machen.')
|
||||
assert.equal(invoiceConfirmText('resend', inv), 'Die Rechnung WK-0006 jetzt noch einmal an k@example.invalid senden (Erinnerung)?')
|
||||
// „send“ gibt es nur für Entwürfe; bei jeder anderen Rechnung keine Rückfrage
|
||||
assert.equal(invoiceConfirmText('send', inv), '')
|
||||
assert.equal(invoiceConfirmText('pdf', inv), '')
|
||||
})
|
||||
|
||||
test('invoiceConfirmText: Senden eines Entwurfs fragt vorher, mit Kunde, Betrag und E-Mail', () => {
|
||||
const draft = INV({ status: 'entwurf', number: '', totalCents: 120050 })
|
||||
assert.equal(
|
||||
invoiceConfirmText('send', draft),
|
||||
'Rechnung für Kunde Zwei über 1.200,50 € jetzt festschreiben und an k@example.invalid senden? Danach lässt sie sich nur noch stornieren.'
|
||||
)
|
||||
// ohne Namen steht die E-Mail als Kunde, ohne beides „den Kunden“
|
||||
assert.equal(
|
||||
invoiceConfirmText('send', INV({ status: 'entwurf', customerName: '', customerEmail: 'a@example.invalid' })),
|
||||
'Rechnung für a@example.invalid über 50,00 € jetzt festschreiben und an a@example.invalid senden? Danach lässt sie sich nur noch stornieren.'
|
||||
)
|
||||
assert.equal(
|
||||
invoiceConfirmText('send', INV({ status: 'entwurf', customerName: '', customerEmail: '' })),
|
||||
'Rechnung für den Kunden über 50,00 € jetzt festschreiben und an den Kunden senden? Danach lässt sie sich nur noch stornieren.'
|
||||
)
|
||||
for (const status of ['offen', 'ueberfaellig', 'bezahlt']) assert.equal(invoiceConfirmText('send', INV({ status })), '')
|
||||
})
|
||||
|
||||
test('invoiceSendConfirmText: Text für den Dialog „Senden“ (Kunde, Summe, E-Mail des gewählten Kunden)', () => {
|
||||
assert.equal(
|
||||
invoiceSendConfirmText({ name: 'Müller Sanitär', email: 'm@example.invalid', totalCents: 15050 }),
|
||||
'Rechnung für Müller Sanitär über 150,50 € jetzt festschreiben und an m@example.invalid senden? Danach lässt sie sich nur noch stornieren.'
|
||||
)
|
||||
assert.equal(
|
||||
invoiceSendConfirmText({ name: ' ', email: ' m@example.invalid ', totalCents: 100 }),
|
||||
'Rechnung für m@example.invalid über 1,00 € jetzt festschreiben und an m@example.invalid senden? Danach lässt sie sich nur noch stornieren.'
|
||||
)
|
||||
assert.match(invoiceSendConfirmText(), /^Rechnung für den Kunden über /)
|
||||
})
|
||||
|
||||
test('invoiceDoneMessage', () => {
|
||||
const r = { number: 'WK-0007', customerEmail: 'k@example.invalid' }
|
||||
assert.equal(invoiceDoneMessage('send', r), 'Rechnung WK-0007 wurde an k@example.invalid gesendet.')
|
||||
@@ -1220,3 +1256,37 @@ test('customerInvoicesGate: fester Kunde mit E-Mail darf, alle anderen bekommen
|
||||
assert.equal(none.title, 'Kein Kunde ausgewählt')
|
||||
assert.equal(none.hint, '')
|
||||
})
|
||||
|
||||
test('addressComplete: Straße, PLZ und Ort müssen alle gefüllt sein', () => {
|
||||
const full = { street: 'Hauptstr. 1', postalCode: '10115', city: 'Berlin' }
|
||||
assert.equal(addressComplete(full), true)
|
||||
assert.equal(addressComplete({ ...full, street: '' }), false)
|
||||
assert.equal(addressComplete({ ...full, postalCode: ' ' }), false)
|
||||
assert.equal(addressComplete({ ...full, city: undefined }), false)
|
||||
assert.equal(addressComplete({ ...full, city: null }), false)
|
||||
assert.equal(addressComplete({}), false)
|
||||
assert.equal(addressComplete(null), false)
|
||||
// Land allein macht keine Anschrift
|
||||
assert.equal(addressComplete({ country: 'Deutschland' }), false)
|
||||
})
|
||||
|
||||
test('invoiceSendGate: Senden nur gesperrt bei fehlender Anschrift UND Summe über 250 €', () => {
|
||||
assert.equal(SMALL_INVOICE_MAX_CENTS, 25000)
|
||||
assert.equal(ADDRESS_INCOMPLETE_TEXT, 'Adresse des Kunden unvollständig (Straße, PLZ, Ort). Für den Versand bitte zuerst ergänzen.')
|
||||
const open = { street: '', postalCode: '10115', city: 'Berlin' }
|
||||
const full = { street: 'Hauptstr. 1', postalCode: '10115', city: 'Berlin' }
|
||||
|
||||
// unvollständig + über 250 €: Warnung, Senden gesperrt
|
||||
assert.deepEqual(invoiceSendGate(open, 25001), { warn: true, sendAllowed: false, text: ADDRESS_INCOMPLETE_TEXT })
|
||||
assert.deepEqual(invoiceSendGate(open, 120050), { warn: true, sendAllowed: false, text: ADDRESS_INCOMPLETE_TEXT })
|
||||
// genau 250,00 € und darunter: Kleinbetragsrechnung, Warnung ja, Senden erlaubt
|
||||
assert.deepEqual(invoiceSendGate(open, 25000), { warn: true, sendAllowed: true, text: ADDRESS_INCOMPLETE_TEXT })
|
||||
assert.deepEqual(invoiceSendGate(open, 0), { warn: true, sendAllowed: true, text: ADDRESS_INCOMPLETE_TEXT })
|
||||
// vollständige Anschrift: nie eine Sperre
|
||||
assert.deepEqual(invoiceSendGate(full, 99999999), { warn: false, sendAllowed: true, text: '' })
|
||||
// unbekannte Summe gilt nicht als klein
|
||||
assert.equal(invoiceSendGate(open, null).sendAllowed, false)
|
||||
assert.equal(invoiceSendGate(open, undefined).sendAllowed, false)
|
||||
// noch kein Kunde gewählt: weder Warnung noch Sperre (die Formularprüfung meldet das)
|
||||
assert.deepEqual(invoiceSendGate(null, 99999), { warn: false, sendAllowed: true, text: '' })
|
||||
})
|
||||
Reference in new issue
Block a user