Kontrolle: Rechnungsdialog übernimmt das Zahlungsziel der gewählten AB
Bei Anzahlung, Schlussrechnung und Aufwand wird 'Zahlungsziel in Tagen' mit auftrag.zahlungszielTage vorbelegt (sonst 14); eine Eingabe des Nutzers bleibt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7b3e834f8
commit
dec7bea7d2
5 files changed
+63
-3
No files matched your search
@@ -41,6 +41,7 @@ import {
|
|||||||
startProjekt,
|
startProjekt,
|
||||||
ticketOptionen,
|
ticketOptionen,
|
||||||
wirksamesProjekt,
|
wirksamesProjekt,
|
||||||
|
zahlungszielVorschlag,
|
||||||
} from '../../lib/rechnungDialogView'
|
} from '../../lib/rechnungDialogView'
|
||||||
import {
|
import {
|
||||||
BEZEICHNUNG_MAX,
|
BEZEICHNUNG_MAX,
|
||||||
@@ -169,6 +170,12 @@ function RechnungFormular({ onClose, onCreated, projektId, auftraegeAktiv, onSho
|
|||||||
const gate = invoiceSendGate(kunde, totalCents)
|
const gate = invoiceSendGate(kunde, totalCents)
|
||||||
const darf = darfSpeichern(status) && busy === null && Boolean(kunde) && projekt !== null && art !== ''
|
const darf = darfSpeichern(status) && busy === null && Boolean(kunde) && projekt !== null && art !== ''
|
||||||
|
|
||||||
|
// Das Zahlungsziel der gewählten AB (bzw. 14); der Reducer lässt eine Eingabe des Nutzers unberührt
|
||||||
|
const tageVorschlag = zahlungszielVorschlag({ art, ab, auftraege })
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({ type: 'tage-vorschlag', wert: tageVorschlag })
|
||||||
|
}, [tageVorschlag])
|
||||||
|
|
||||||
// --- Effekte auf die Vorschau -----------------------------------------------------------------------------
|
// --- Effekte auf die Vorschau -----------------------------------------------------------------------------
|
||||||
const vorschauZeitraum = status.state === 'bereit' ? vorschau.daten.leistungszeitraum || '' : null
|
const vorschauZeitraum = status.state === 'bereit' ? vorschau.daten.leistungszeitraum || '' : null
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function anfangszustand({ projektId = null } = {}) {
|
|||||||
zeitraum: '',
|
zeitraum: '',
|
||||||
zeitraumGeaendert: false,
|
zeitraumGeaendert: false,
|
||||||
days: String(INVOICE_LIMITS.defaultDays),
|
days: String(INVOICE_LIMITS.defaultDays),
|
||||||
|
tageGeaendert: false,
|
||||||
memo: '',
|
memo: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,7 @@ function neueUid(s) {
|
|||||||
export function reduce(s, a) {
|
export function reduce(s, a) {
|
||||||
switch (a?.type) {
|
switch (a?.type) {
|
||||||
case 'projekt':
|
case 'projekt':
|
||||||
return { ...anfangszustand({ projektId: a.projektId }), days: s.days, memo: s.memo }
|
return { ...anfangszustand({ projektId: a.projektId }), days: s.days, tageGeaendert: s.tageGeaendert, memo: s.memo }
|
||||||
case 'art':
|
case 'art':
|
||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
@@ -84,6 +85,10 @@ export function reduce(s, a) {
|
|||||||
if (s.zeitraumGeaendert || s.zeitraum === a.text) return s
|
if (s.zeitraumGeaendert || s.zeitraum === a.text) return s
|
||||||
return { ...s, zeitraum: a.text }
|
return { ...s, zeitraum: a.text }
|
||||||
case 'tage':
|
case 'tage':
|
||||||
|
return { ...s, days: a.wert, tageGeaendert: true }
|
||||||
|
case 'tage-vorschlag':
|
||||||
|
// das Zahlungsziel der gewählten AB; eine Eingabe des Nutzers überschreibt er nie
|
||||||
|
if (s.tageGeaendert || s.days === a.wert) return s
|
||||||
return { ...s, days: a.wert }
|
return { ...s, days: a.wert }
|
||||||
case 'notiz':
|
case 'notiz':
|
||||||
return { ...s, memo: a.wert }
|
return { ...s, memo: a.wert }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Aufgaben: Projektwahl, waehlbare Rechnungsarten, gültige Auftragsbestätigungen (AB), Vorgaben für Anzahlung und Standardart,
|
// Aufgaben: Projektwahl, waehlbare Rechnungsarten, gültige Auftragsbestätigungen (AB), Vorgaben für Anzahlung und Standardart,
|
||||||
// Arbeitsblätter (Auswahl, Summen, Umwandlung aus Appwrite-Dokumenten) und die Ticketliste für „Weiteres Ticket hinzunehmen“.
|
// Arbeitsblätter (Auswahl, Summen, Umwandlung aus Appwrite-Dokumenten) und die Ticketliste für „Weiteres Ticket hinzunehmen“.
|
||||||
import { euro, dateDe, ymdBerlin } from './financeFormat'
|
import { euro, dateDe, ymdBerlin } from './financeFormat'
|
||||||
|
import { INVOICE_LIMITS } from './financeView'
|
||||||
import { worksheetMinutes } from './support'
|
import { worksheetMinutes } from './support'
|
||||||
import { germanDateToIso } from './ticketForm'
|
import { germanDateToIso } from './ticketForm'
|
||||||
import { ticketsText } from './projektkontoView'
|
import { ticketsText } from './projektkontoView'
|
||||||
@@ -145,6 +146,19 @@ export function standardAuftrag(art, auftraege, rechnungen = []) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vorbelegung „Zahlungsziel in Tagen“: bei Anzahlung und Schlussrechnung das Ziel der gewählten AB, bei Aufwand das der
|
||||||
|
* jüngsten gültigen Aufwands-AB (`auftraege`), sonst 14. Nur ganze Zahlen von 0 bis 60 zählen. -> Text
|
||||||
|
*/
|
||||||
|
export function zahlungszielVorschlag({ art, ab = null, auftraege = [] } = {}) {
|
||||||
|
const standard = String(INVOICE_LIMITS.defaultDays)
|
||||||
|
let quelle = null
|
||||||
|
if (art === 'anzahlung' || art === 'schluss') quelle = ab
|
||||||
|
else if (art === 'aufwand') quelle = aufwandAuftrag(auftraege)
|
||||||
|
const tage = quelle?.zahlungszielTage
|
||||||
|
return Number.isSafeInteger(tage) && tage >= 0 && tage <= INVOICE_LIMITS.maxDays ? String(tage) : standard
|
||||||
|
}
|
||||||
|
|
||||||
/** Vorgabe der Anzahlung: Prozent aus dem Zahlungsplan, sonst 50. */
|
/** Vorgabe der Anzahlung: Prozent aus dem Zahlungsplan, sonst 50. */
|
||||||
export function anzahlungStandard(auftrag) {
|
export function anzahlungStandard(auftrag) {
|
||||||
const plan = auftrag?.zahlungsplan
|
const plan = auftrag?.zahlungsplan
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ test('anfangszustand: nichts gewählt, eine leere Position, Zahlungsziel 14', ()
|
|||||||
projektId: 'p1', art: '', auftragId: '',
|
projektId: 'p1', art: '', auftragId: '',
|
||||||
items: [{ uid: 1, description: '', quantity: '1', price: '' }], zusatz: [],
|
items: [{ uid: 1, description: '', quantity: '1', price: '' }], zusatz: [],
|
||||||
abgewaehlt: [], weitereTickets: [], anzahlung: {}, zeilentexte: {},
|
abgewaehlt: [], weitereTickets: [], anzahlung: {}, zeilentexte: {},
|
||||||
zeitraum: '', zeitraumGeaendert: false, days: '14', memo: '',
|
zeitraum: '', zeitraumGeaendert: false, days: '14', tageGeaendert: false, memo: '',
|
||||||
})
|
})
|
||||||
assert.equal(anfangszustand().projektId, null)
|
assert.equal(anfangszustand().projektId, null)
|
||||||
assert.equal(anfangszustand({ projektId: '' }).projektId, '')
|
assert.equal(anfangszustand({ projektId: '' }).projektId, '')
|
||||||
@@ -113,3 +113,20 @@ test('anzahlung und auftrag: Eingaben ergänzen sich, eine andere AB setzt sie z
|
|||||||
assert.deepEqual([s.auftragId, s.anzahlung], ['a2', {}])
|
assert.deepEqual([s.auftragId, s.anzahlung], ['a2', {}])
|
||||||
assert.equal(reduce(s, { type: 'gibtsnicht' }), s)
|
assert.equal(reduce(s, { type: 'gibtsnicht' }), s)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('tage-vorschlag: übernimmt das Zahlungsziel der AB, solange der Nutzer es nicht selbst geändert hat', () => {
|
||||||
|
let s = anfangszustand({ projektId: 'p1' })
|
||||||
|
s = reduce(s, { type: 'tage-vorschlag', wert: '30' })
|
||||||
|
assert.equal(s.days, '30')
|
||||||
|
assert.equal(s.tageGeaendert, false)
|
||||||
|
assert.equal(reduce(s, { type: 'tage-vorschlag', wert: '30' }), s) // derselbe Wert: nichts zu tun
|
||||||
|
s = reduce(s, { type: 'tage-vorschlag', wert: '14' }) // andere AB gewählt: der Vorschlag folgt
|
||||||
|
assert.equal(s.days, '14')
|
||||||
|
// eine Eingabe des Nutzers wird nie überschrieben, auch nicht nach einem Projektwechsel
|
||||||
|
s = reduce(s, { type: 'tage', wert: '7' })
|
||||||
|
assert.equal(s.tageGeaendert, true)
|
||||||
|
assert.equal(reduce(s, { type: 'tage-vorschlag', wert: '30' }), s)
|
||||||
|
s = reduce(s, { type: 'projekt', projektId: 'p2' })
|
||||||
|
assert.equal(s.days, '7')
|
||||||
|
assert.equal(reduce(s, { type: 'tage-vorschlag', wert: '30' }).days, '7')
|
||||||
|
})
|
||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
startProjekt, projektOptionen, wirksamesProjekt,
|
startProjekt, projektOptionen, wirksamesProjekt,
|
||||||
gueltigeFestpreisAbs, aufwandAuftrag, abLabel, berechnetFuerAuftrag, hatEntwurfZurAuftrag,
|
gueltigeFestpreisAbs, aufwandAuftrag, abLabel, berechnetFuerAuftrag, hatEntwurfZurAuftrag,
|
||||||
artOptionen, standardArt, standardAuftrag, anzahlungStandard,
|
artOptionen, standardArt, standardAuftrag, anzahlungStandard,
|
||||||
ausgewaehlteIds, blaetterSumme, blattAusAppwrite, ticketOptionen,
|
ausgewaehlteIds, blaetterSumme, blattAusAppwrite, ticketOptionen, zahlungszielVorschlag,
|
||||||
} = await loadModule('src/lib/rechnungDialogView.js')
|
} = await loadModule('src/lib/rechnungDialogView.js')
|
||||||
|
|
||||||
const ab = (over = {}) => ({
|
const ab = (over = {}) => ({
|
||||||
@@ -215,3 +215,20 @@ test('ticketOptionen: Tickets des Kunden mit Nummer, ohne die schon gewählten',
|
|||||||
])
|
])
|
||||||
assert.deepEqual(ticketOptionen(undefined), [])
|
assert.deepEqual(ticketOptionen(undefined), [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('zahlungszielVorschlag: Anzahlung und Schluss übernehmen das Zahlungsziel der gewählten AB, sonst 14', () => {
|
||||||
|
const dreissig = ab({ zahlungszielTage: 30 })
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'anzahlung', ab: dreissig }), '30')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'schluss', ab: dreissig }), '30')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'anzahlung', ab: ab({ zahlungszielTage: 0 }) }), '0') // sofort fällig ist erlaubt
|
||||||
|
// Aufwand: die jüngste gültige Aufwands-AB
|
||||||
|
const aufwand = ab({ id: 'x2', art: 'aufwand', zahlungszielTage: 21 })
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'aufwand', auftraege: [ab(), aufwand] }), '21')
|
||||||
|
// ohne AB, ohne Wert oder mit unbrauchbarem Wert bleibt es bei 14; frei und Festbetrag kennen keine AB
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'anzahlung', ab: undefined }), '14')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'anzahlung', ab: ab() }), '14')
|
||||||
|
for (const kaputt of [-1, 61, 1.5, '30', null, NaN]) assert.equal(zahlungszielVorschlag({ art: 'schluss', ab: ab({ zahlungszielTage: kaputt }) }), '14')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'festbetrag', ab: dreissig }), '14')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: 'frei', ab: dreissig }), '14')
|
||||||
|
assert.equal(zahlungszielVorschlag({ art: '', auftraege: [aufwand] }), '14')
|
||||||
|
})
|
||||||
Reference in new issue
Block a user