import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' const { AUFTRAG_LIMITS, STUNDENSATZ_STANDARD_CENTS, AUFTRAG_WEGE, SCHRITTE, zahlungsplanOptionen, parseStunden, stundenText, parseProzent, parseTage, heuteBerlin, neuePosition, neuesFormular, auftragZuFormular, waehleProjekt, wechselArt, formularSummeCents, pruefeSchritt, formularZuNutzlast, zahlungsplanVorschau, sendeGate, zeitquelleOptionen, schalteZeitquelle, kundenartWirksam, VOR_ORT_HINWEIS, vorOrtHinweis, auftragHinweise, } = await loadModule('src/lib/auftragView.js') const KONTEN = [ { projektId: 'p1', name: 'Website Praxis Müller', tickets: [{ id: 't1', woid: '100131' }] }, { projektId: 'p2', name: 'Shop Relaunch', tickets: [{ id: 't2', woid: '100140' }, { id: 't3', woid: '100141' }] }, ] const KUNDE = { $id: 'c1', name: 'Praxis Müller', email: 'info@praxis.test', street: 'Hauptstr. 1', postalCode: '67722', city: 'Winnweiler', kundenart: '', customerStatus: 'customer' } const festpreisForm = (over = {}) => ({ ...neuesFormular({ heute: '2026-10-06' }), titel: 'Website', kundenart: 'privat', auftragWeg: 'telefon', positionen: [{ uid: 1, description: 'Startseite', quantity: '1', price: '800,00' }], ...over, }) const aufwandForm = (over = {}) => festpreisForm({ art: 'aufwand', positionen: [], ...over }) const OK = { ok: true, fehler: {}, erstes: '' } test('Konstanten gleichen dem Vertrag V2 und der Spec (Limits, Wege, Zahlungspläne, Schritte)', () => { assert.deepEqual(AUFTRAG_LIMITS, { maxPositionen: 20, maxBeschreibung: 200, maxMenge: 999, maxEinzelpreisCents: 10_000_000, maxSummeCents: 99_999_999, maxTitel: 200, maxZahlungsplanText: 500, maxZahlungszielTage: 60, standardZahlungszielTage: 14, standardAnzahlungProzent: 50, maxZeitquellen: 10, maxSchaetzungMinuten: 99_960, maxLeistungszeitraum: 140, }) assert.equal(STUNDENSATZ_STANDARD_CENTS, 10000) assert.deepEqual(AUFTRAG_WEGE, [ { value: 'muendlich', label: 'Mündlich' }, { value: 'telefon', label: 'Telefonisch' }, { value: 'email', label: 'Per E-Mail' }, { value: 'vor_ort', label: 'Vor Ort' }, { value: 'chat', label: 'Im Chat' }, ]) assert.deepEqual(zahlungsplanOptionen('festpreis'), [ { value: 'nach_abnahme', label: 'Alles nach Abnahme' }, { value: 'anzahlung', label: 'Anzahlung bei Auftrag, Rest nach Abnahme' }, { value: 'frei', label: 'Frei (eigener Text)' }, ]) assert.deepEqual(zahlungsplanOptionen('aufwand'), [ { value: 'nach_abnahme', label: 'Alles nach Abnahme' }, { value: 'monatlich', label: 'Monatlich nach Aufwand' }, { value: 'frei', label: 'Frei (eigener Text)' }, ]) assert.deepEqual(SCHRITTE, [ { id: 1, label: 'Projekt' }, { id: 2, label: 'Auftrag' }, { id: 3, label: 'Zahlung und Termine' }, { id: 4, label: 'Vorschau' }, ]) }) test('parseStunden: Stunden mit Komma in 15-Minuten-Schritten werden Minuten', () => { const gut = { '10': 600, '10,25': 615, '10,5': 630, '10,50': 630, '10,75': 645, '0,25': 15, '1': 60, '12,5 h': 750, '2 Std.': 120, ' 8 ': 480, '1666': 99960 } for (const [text, minuten] of Object.entries(gut)) assert.equal(parseStunden(text), minuten, text) for (const schlecht of ['', '0', '0,00', '10,3', '10,30', '10,1', '10.5', '-1', 'abc', '1666,25', '1667', '10,255', '1e3', ',5', '10 Minuten', null, undefined]) { assert.equal(parseStunden(schlecht), null, String(schlecht)) } }) test('stundenText: Minuten als Stunden mit Komma, ohne überflüssige Nullen', () => { assert.deepEqual([15, 30, 45, 60, 75, 120, 135, 150, 600, 99960].map(stundenText), ['0,25', '0,5', '0,75', '1', '1,25', '2', '2,25', '2,5', '10', '1666']) assert.equal(stundenText(null), '–') assert.equal(stundenText('x'), '–') }) test('parseProzent und parseTage', () => { assert.deepEqual(['50', ' 30 % ', '1', '99'].map(parseProzent), [50, 30, 1, 99]) for (const schlecht of ['', '0', '100', '-5', '33,3', '5a', 'x', null]) assert.equal(parseProzent(schlecht), null, String(schlecht)) assert.deepEqual(['14', '0', '60', ' 7 '].map(parseTage), [14, 0, 60, 7]) for (const schlecht of ['', '61', '-1', '1,5', 'x', '1000', null]) assert.equal(parseTage(schlecht), null, String(schlecht)) }) test('heuteBerlin: Berliner Kalendertag, nicht der UTC-Tag', () => { assert.equal(heuteBerlin(Date.parse('2026-10-05T22:30:00.000Z')), '2026-10-06') assert.equal(heuteBerlin(Date.parse('2026-10-05T21:59:00.000Z')), '2026-10-05') assert.match(heuteBerlin(), /^\d{4}-\d{2}-\d{2}$/) }) test('neuesFormular: Vorgaben; ein Projekt füllt Titel und Zeitquelle', () => { const f = neuesFormular({ projektId: 'p2', konten: KONTEN, heute: '2026-10-06' }) const { positionen, ...rest } = f assert.equal(positionen.length, 1) const { uid, ...zeile } = positionen[0] assert.equal(typeof uid, 'number') assert.deepEqual(zeile, { description: '', quantity: '1', price: '' }) assert.deepEqual(rest, { projektId: 'p2', titel: 'Shop Relaunch', titelGeaendert: false, kundenart: '', art: 'festpreis', auftragVom: '2026-10-06', auftragWeg: '', stundensatz: '100,00', schaetzung: '', zahlungsart: 'nach_abnahme', prozent: '50', zahlungstext: '', zahlungsziel: '14', startAm: '', fertigBis: '', zeitquelle: ['t2', 't3'], zeitquelleGeaendert: false, }) assert.deepEqual(neuesFormular({ konten: KONTEN, heute: '2026-10-06' }).zeitquelle, []) assert.equal(neuesFormular({ projektId: 'gibtsnicht', konten: KONTEN, heute: '2026-10-06' }).projektId, '') assert.equal(neuesFormular({ projektId: 'p1', heute: '2026-10-06' }).projektId, '') assert.notEqual(neuePosition().uid, neuePosition().uid) }) test('auftragZuFormular: Entwurf vom Server wird zum Formular (Euro- und Stundentexte)', () => { const entwurf = { id: 'a1', status: 'entwurf', projektId: 'p1', titel: 'Website', art: 'festpreis', positionen: [{ beschreibung: 'Startseite', menge: 2, einzelpreisCents: 40000 }], stundensatzCents: null, schaetzungMinuten: null, summeCents: 80000, zahlungsplan: { art: 'anzahlung', prozent: 30, text: '' }, zahlungszielTage: 21, auftragVom: '2026-10-05', auftragWeg: 'email', startAm: '2026-11-01', fertigBis: '', zeitquelle: ['t1'], } const f = auftragZuFormular(entwurf) assert.deepEqual(f.positionen.map(({ uid, ...p }) => p), [{ description: 'Startseite', quantity: '2', price: '400,00' }]) const { positionen, ...rest } = f assert.deepEqual(rest, { projektId: 'p1', titel: 'Website', titelGeaendert: true, kundenart: '', art: 'festpreis', auftragVom: '2026-10-05', auftragWeg: 'email', stundensatz: '100,00', schaetzung: '', zahlungsart: 'anzahlung', prozent: '30', zahlungstext: '', zahlungsziel: '21', startAm: '2026-11-01', fertigBis: '', zeitquelle: ['t1'], zeitquelleGeaendert: true, }) const aufwand = auftragZuFormular({ ...entwurf, art: 'aufwand', positionen: [], stundensatzCents: 9550, schaetzungMinuten: 750, summeCents: null, zahlungsplan: { art: 'frei', prozent: null, text: 'Nach Absprache' }, }) assert.equal(aufwand.stundensatz, '95,50') assert.equal(aufwand.schaetzung, '12,5') assert.equal(aufwand.positionen.length, 1) // eine leere Zeile für den Wechsel auf Festpreis assert.deepEqual([aufwand.zahlungsart, aufwand.prozent, aufwand.zahlungstext], ['frei', '50', 'Nach Absprache']) }) test('waehleProjekt: Titel und Zeitquelle folgen dem Projekt, bis sie von Hand geändert wurden', () => { const f0 = neuesFormular({ konten: KONTEN, heute: '2026-10-06' }) const f1 = waehleProjekt(f0, 'p1', KONTEN) assert.deepEqual([f1.projektId, f1.titel, f1.zeitquelle], ['p1', 'Website Praxis Müller', ['t1']]) const f2 = waehleProjekt(f1, 'p2', KONTEN) assert.deepEqual([f2.titel, f2.zeitquelle], ['Shop Relaunch', ['t2', 't3']]) const f3 = waehleProjekt(f2, '', KONTEN) assert.deepEqual([f3.projektId, f3.titel, f3.zeitquelle], ['', '', []]) assert.equal(waehleProjekt(f1, 'gibtsnicht', KONTEN).projektId, '') const hand = { ...f1, titel: 'Eigener Titel', titelGeaendert: true, zeitquelle: ['t7'], zeitquelleGeaendert: true } const f4 = waehleProjekt(hand, 'p2', KONTEN) assert.deepEqual([f4.projektId, f4.titel, f4.zeitquelle], ['p2', 'Eigener Titel', ['t7']]) assert.equal(f0.projektId, '') // die Eingabe bleibt unverändert }) test('wechselArt: ein unpassender Zahlungsplan wird zurückgesetzt', () => { const f = { ...neuesFormular({ heute: '2026-10-06' }), zahlungsart: 'anzahlung' } assert.equal(wechselArt(f, 'aufwand').art, 'aufwand') assert.equal(wechselArt(f, 'aufwand').zahlungsart, 'nach_abnahme') assert.equal(wechselArt(f, 'festpreis').zahlungsart, 'anzahlung') assert.equal(wechselArt({ ...f, art: 'aufwand', zahlungsart: 'monatlich' }, 'festpreis').zahlungsart, 'nach_abnahme') assert.equal(wechselArt({ ...f, zahlungsart: 'frei' }, 'aufwand').zahlungsart, 'frei') assert.equal(wechselArt(f, 'quatsch'), f) }) test('formularSummeCents: Festpreis summiert gültige Positionen, Aufwand hat keine Summe', () => { const f = festpreisForm({ positionen: [ { uid: 1, description: 'a', quantity: '2', price: '100,00' }, { uid: 2, description: 'b', quantity: '1', price: '50' }, { uid: 3, description: 'c', quantity: 'x', price: '9,99' }, ], }) assert.equal(formularSummeCents(f), 25000) assert.equal(formularSummeCents(festpreisForm({ positionen: [] })), 0) assert.equal(formularSummeCents(aufwandForm()), null) }) test('pruefeSchritt 1: Titel ist Pflicht, die Kundenart nur, wenn sie am Kunden fehlt', () => { const f = neuesFormular({ heute: '2026-10-06' }) const r = pruefeSchritt(1, f, { kunde: KUNDE }) assert.equal(r.ok, false) assert.deepEqual(r.fehler, { titel: 'Bitte einen Titel eingeben (höchstens 200 Zeichen).', kundenart: 'Bitte die Kundenart wählen.' }) assert.equal(r.erstes, 'titel') assert.deepEqual(pruefeSchritt(1, { ...f, titel: ' Website ', kundenart: 'privat' }, { kunde: KUNDE }), OK) assert.deepEqual(pruefeSchritt(1, { ...f, titel: 'x' }, { kunde: { ...KUNDE, kundenart: 'privat' } }), OK) assert.equal(pruefeSchritt(1, { ...f, titel: 'x'.repeat(201), kundenart: 'privat' }, { kunde: KUNDE }).ok, false) assert.equal(pruefeSchritt(1, { ...f, titel: 'x'.repeat(200), kundenart: 'privat' }, { kunde: KUNDE }).ok, true) assert.equal(pruefeSchritt(1, { ...f, titel: 'x', kundenart: 'firma' }, { kunde: KUNDE }).fehler.kundenart, 'Bitte die Kundenart wählen.') }) test('pruefeSchritt 2 (Festpreis): Datum, Weg, Positionen und Summe', () => { const pruefe = (over) => pruefeSchritt(2, festpreisForm(over), { kunde: KUNDE }) assert.deepEqual(pruefe(), OK) const r = pruefe({ auftragVom: '', auftragWeg: '' }) assert.deepEqual(r.fehler, { auftragVom: 'Bitte das Datum der Absprache angeben.', auftragWeg: 'Bitte den Weg der Absprache wählen.' }) assert.equal(r.erstes, 'auftragVom') for (const schlecht of ['2026-02-30', '06.10.2026', '2026-1-1', 'morgen']) { assert.equal(pruefe({ auftragVom: schlecht }).fehler.auftragVom, 'Bitte das Datum der Absprache angeben.', schlecht) } assert.equal(pruefe({ auftragWeg: 'brieftaube' }).fehler.auftragWeg, 'Bitte den Weg der Absprache wählen.') assert.equal(pruefe({ art: 'xyz' }).fehler.art, 'Bitte die Art des Auftrags wählen.') const pos = (o = {}) => ({ uid: 1, description: 'x', quantity: '1', price: '10,00', ...o }) const meldung = (positionen) => pruefe({ positionen }).fehler.positionen const PREIS = 'Der Einzelpreis muss ein Betrag zwischen 0,01 € und 100.000,00 € sein (z. B. 120,00).' assert.equal(meldung([pos({ description: ' ' })]), 'Position 1: Bitte eine Beschreibung eingeben.') assert.equal(meldung([pos(), pos({ description: 'y'.repeat(201) })]), 'Position 2: Die Beschreibung darf höchstens 200 Zeichen lang sein.') assert.equal(meldung([pos({ quantity: '0' })]), 'Position 1: Die Menge muss eine ganze Zahl von 1 bis 999 sein.') assert.equal(meldung([pos({ quantity: '1000' })]), 'Position 1: Die Menge muss eine ganze Zahl von 1 bis 999 sein.') assert.equal(meldung([pos({ price: '' })]), `Position 1: ${PREIS}`) assert.equal(meldung([pos({ price: '100.000,01' })]), `Position 1: ${PREIS}`) assert.equal(meldung([]), 'Eine Auftragsbestätigung braucht 1 bis 20 Positionen.') assert.equal(meldung(Array.from({ length: 21 }, () => pos())), 'Eine Auftragsbestätigung braucht 1 bis 20 Positionen.') assert.equal(meldung(Array.from({ length: 20 }, () => pos())), undefined) // Summe: genau 999.999,99 € gehen, 1.000.000,00 € nicht const neun = Array.from({ length: 9 }, () => pos({ price: '100.000,00' })) assert.equal(meldung([...neun, pos({ price: '99.999,99' })]), undefined) assert.equal(meldung([...neun, pos({ price: '100.000,00' })]), 'Die Summe ist zu hoch (höchstens 999.999,99 €).') }) test('pruefeSchritt 2 (Aufwand): Stundensatz und Schätzung, Positionen zählen nicht', () => { const pruefe = (over) => pruefeSchritt(2, aufwandForm(over), { kunde: KUNDE }) const SATZ = 'Der Stundensatz muss ein Betrag zwischen 0,01 € und 100.000,00 € sein (z. B. 100,00).' assert.deepEqual(pruefe(), OK) assert.equal(pruefe({ schaetzung: '12,5' }).ok, true) assert.equal(pruefe({ stundensatz: '' }).fehler.stundensatz, SATZ) assert.equal(pruefe({ stundensatz: '12.50' }).fehler.stundensatz, SATZ) assert.equal(pruefe({ schaetzung: '10,3' }).fehler.schaetzung, 'Die Schätzung geht in Schritten von 15 Minuten (z. B. 10 oder 12,5 Stunden, höchstens 1666).') assert.equal(pruefe({ positionen: [{ uid: 1, description: '', quantity: 'x', price: '' }] }).ok, true) }) test('pruefeSchritt 3: Zahlungsplan, Zahlungsziel, Termine, Zeitquelle', () => { const pruefe = (f) => pruefeSchritt(3, f, { kunde: KUNDE }) assert.deepEqual(pruefe(festpreisForm()), OK) assert.equal(pruefe(festpreisForm({ zahlungsart: 'anzahlung', prozent: '50' })).ok, true) for (const schlecht of ['', '0', '100', '33,3']) { assert.equal(pruefe(festpreisForm({ zahlungsart: 'anzahlung', prozent: schlecht })).fehler.prozent, 'Die Anzahlung muss eine ganze Zahl von 1 bis 99 Prozent sein.', schlecht) } assert.equal(pruefe(festpreisForm({ zahlungsart: 'nach_abnahme', prozent: 'x' })).ok, true) // Prozent zählt nur bei „Anzahlung“ const TEXT = 'Bitte den Zahlungsplan als Text eingeben (höchstens 500 Zeichen).' assert.equal(pruefe(festpreisForm({ zahlungsart: 'frei', zahlungstext: ' ' })).fehler.zahlungstext, TEXT) assert.equal(pruefe(festpreisForm({ zahlungsart: 'frei', zahlungstext: 'x'.repeat(501) })).fehler.zahlungstext, TEXT) assert.equal(pruefe(festpreisForm({ zahlungsart: 'frei', zahlungstext: 'x'.repeat(500) })).ok, true) for (const schlecht of ['', '61', '-1', '1,5']) { assert.equal(pruefe(festpreisForm({ zahlungsziel: schlecht })).fehler.zahlungsziel, 'Das Zahlungsziel muss eine ganze Zahl von 0 bis 60 Tagen sein.', schlecht) } assert.equal(pruefe(festpreisForm({ zahlungsziel: '0' })).ok, true) assert.equal(pruefe(festpreisForm({ startAm: '2026-11-01', fertigBis: '2026-11-30' })).ok, true) assert.equal(pruefe(festpreisForm({ startAm: '2026-11-01', fertigBis: '2026-11-01' })).ok, true) assert.equal(pruefe(festpreisForm({ startAm: '2026-11-02', fertigBis: '2026-11-01' })).fehler.fertigBis, 'Die Fertigstellung liegt vor dem Start.') assert.equal(pruefe(festpreisForm({ startAm: '2026-13-01' })).fehler.startAm, 'Bitte ein gültiges Datum wählen.') assert.equal(pruefe(festpreisForm({ fertigBis: 'bald' })).fehler.fertigBis, 'Bitte ein gültiges Datum wählen.') const ids = (n) => Array.from({ length: n }, (_, i) => `t${i}`) assert.equal(pruefe(festpreisForm({ zeitquelle: ids(10) })).ok, true) assert.equal(pruefe(festpreisForm({ zeitquelle: ids(11) })).fehler.zeitquelle, 'Höchstens 10 Tickets als Zeitquelle.') const PASST = 'Dieser Zahlungsplan passt nicht zur Art des Auftrags.' assert.equal(pruefe(festpreisForm({ zahlungsart: 'monatlich' })).fehler.zahlungsart, PASST) assert.equal(pruefe(aufwandForm({ zahlungsart: 'anzahlung' })).fehler.zahlungsart, PASST) }) test('formularZuNutzlast: Festpreis in Cent, getrimmte Texte, Kundenart nur wenn sie am Kunden fehlt', () => { const f = festpreisForm({ titel: ' Website Praxis ', projektId: 'p1', positionen: [ { uid: 1, description: ' Startseite ', quantity: '2', price: '400,00' }, { uid: 2, description: 'Kontaktformular', quantity: '1', price: '1.234,50' }, ], zahlungsart: 'anzahlung', prozent: '30', zahlungsziel: '21', startAm: '2026-11-01', fertigBis: '2026-11-30', zeitquelle: ['t1', 't2'], }) const r = formularZuNutzlast(f, { kunde: KUNDE }) assert.equal(r.ok, true) assert.equal(r.summeCents, 203450) assert.deepEqual(r.payload, { projektId: 'p1', titel: 'Website Praxis', kundenart: 'privat', art: 'festpreis', positionen: [ { beschreibung: 'Startseite', menge: 2, einzelpreisCents: 40000 }, { beschreibung: 'Kontaktformular', menge: 1, einzelpreisCents: 123450 }, ], stundensatzCents: null, schaetzungMinuten: null, zahlungsplan: { art: 'anzahlung', prozent: 30, text: '' }, zahlungszielTage: 21, auftragVom: '2026-10-06', auftragWeg: 'telefon', startAm: '2026-11-01', fertigBis: '2026-11-30', zeitquelle: ['t1', 't2'], }) assert.equal('kundenart' in formularZuNutzlast(f, { kunde: { ...KUNDE, kundenart: 'unternehmen' } }).payload, false) const frei = formularZuNutzlast(festpreisForm({ zahlungsart: 'frei', zahlungstext: ' Zahlung in zwei Raten ' }), { kunde: KUNDE }) assert.deepEqual(frei.payload.zahlungsplan, { art: 'frei', prozent: null, text: 'Zahlung in zwei Raten' }) assert.equal(frei.payload.projektId, '') assert.deepEqual([frei.payload.startAm, frei.payload.fertigBis], ['', '']) }) test('formularZuNutzlast: Aufwand mit Satz in Cent und Schätzung in Minuten, ohne Positionen und Summe', () => { const r = formularZuNutzlast(aufwandForm({ stundensatz: '95,50', schaetzung: '12,5', zahlungsart: 'monatlich' }), { kunde: { ...KUNDE, kundenart: 'privat' } }) assert.equal(r.ok, true) assert.equal(r.summeCents, null) assert.deepEqual(r.payload.positionen, []) assert.equal(r.payload.art, 'aufwand') assert.equal(r.payload.stundensatzCents, 9550) assert.equal(r.payload.schaetzungMinuten, 750) assert.deepEqual(r.payload.zahlungsplan, { art: 'monatlich', prozent: null, text: '' }) assert.equal(formularZuNutzlast(aufwandForm({ schaetzung: '' }), { kunde: KUNDE }).payload.schaetzungMinuten, null) }) test('formularZuNutzlast: Fehler nennen den Schritt und das erste Feld', () => { const r1 = formularZuNutzlast(festpreisForm({ titel: '', zahlungsziel: '99' }), { kunde: KUNDE }) assert.deepEqual([r1.ok, r1.schritt, r1.erstes], [false, 1, 'titel']) assert.equal(r1.fehler.titel, 'Bitte einen Titel eingeben (höchstens 200 Zeichen).') const r2 = formularZuNutzlast(festpreisForm({ auftragWeg: '' }), { kunde: KUNDE }) assert.deepEqual([r2.ok, r2.schritt, r2.erstes], [false, 2, 'auftragWeg']) const r3 = formularZuNutzlast(festpreisForm({ zahlungsziel: '99' }), { kunde: KUNDE }) assert.deepEqual([r3.ok, r3.schritt, r3.erstes], [false, 3, 'zahlungsziel']) }) test('zahlungsplanVorschau: Wortlaut sinngemäß wie in der Auftragsbestätigung', () => { const v = (o) => zahlungsplanVorschau({ art: 'nach_abnahme', prozent: null, text: '', summeCents: 80000, zielTage: 14, ...o }) assert.equal(v({}), 'Gesamtbetrag (800,00 €) nach Abnahme; zahlbar innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ summeCents: null }), 'Alles nach Abnahme; zahlbar innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'anzahlung', prozent: 50 }), 'Anzahlung 50 % (400,00 €) nach Auftragsbestätigung, Rest (400,00 €) nach Abnahme; zahlbar jeweils innerhalb von 14 Tagen nach Rechnung.') // Rundung wie im Vertrag V3: Anzahlung = Math.round(Summe × Prozent / 100), Rest = Summe − Anzahlung assert.equal(v({ art: 'anzahlung', prozent: 50, summeCents: 33333 }), 'Anzahlung 50 % (166,67 €) nach Auftragsbestätigung, Rest (166,66 €) nach Abnahme; zahlbar jeweils innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'anzahlung', prozent: 33, summeCents: 33333 }), 'Anzahlung 33 % (110,00 €) nach Auftragsbestätigung, Rest (223,33 €) nach Abnahme; zahlbar jeweils innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'anzahlung', prozent: 50, summeCents: null }), 'Anzahlung 50 % nach Auftragsbestätigung, Rest nach Abnahme; zahlbar jeweils innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'anzahlung', prozent: null }), '') assert.equal(v({ art: 'monatlich', summeCents: null }), 'Monatlich nach Aufwand; zahlbar innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'frei', text: ' Zwei Raten, je zur Hälfte ' }), 'Zwei Raten, je zur Hälfte (zahlbar innerhalb von 14 Tagen nach Rechnung).') assert.equal(v({ art: 'frei', text: '' }), '') assert.equal(v({ zielTage: 0 }), 'Gesamtbetrag (800,00 €) nach Abnahme; zahlbar sofort nach Rechnung.') assert.equal(v({ zielTage: 1 }), 'Gesamtbetrag (800,00 €) nach Abnahme; zahlbar innerhalb von 1 Tag nach Rechnung.') assert.equal(v({ zielTage: null }), 'Gesamtbetrag (800,00 €) nach Abnahme; zahlbar innerhalb von 14 Tagen nach Rechnung.') assert.equal(v({ art: 'unbekannt' }), '') }) test('sendeGate: Lead, E-Mail, Anschrift und Kundenart halten das Senden auf, der Entwurf bleibt möglich', () => { assert.deepEqual(sendeGate(KUNDE, 'privat'), { erlaubt: true, grund: '', zurUebersicht: false }) assert.deepEqual(sendeGate({ ...KUNDE, kundenart: 'privat' }, ''), { erlaubt: true, grund: '', zurUebersicht: false }) assert.deepEqual(sendeGate(KUNDE, ''), { erlaubt: false, grund: 'Die Kundenart fehlt. Bitte im ersten Schritt wählen.', zurUebersicht: false }) assert.deepEqual(sendeGate({ ...KUNDE, street: '' }, 'privat'), { erlaubt: false, grund: 'Adresse des Kunden unvollständig (Straße, PLZ, Ort). Für den Versand bitte zuerst ergänzen.', zurUebersicht: true, }) assert.deepEqual(sendeGate({ ...KUNDE, email: ' ' }, 'privat'), { erlaubt: false, grund: 'E-Mail fehlt', zurUebersicht: true }) assert.deepEqual(sendeGate({ ...KUNDE, customerStatus: 'lead' }, 'privat'), { erlaubt: false, grund: 'Noch kein fester Kunde', zurUebersicht: true }) assert.deepEqual(sendeGate(null, 'privat'), { erlaubt: false, grund: 'Kein Kunde ausgewählt', zurUebersicht: false }) }) const TICKETS = [ { $id: 't9', woid: '100149', topic: 'Neueste Anfrage' }, { $id: 't2', woid: '100140', topic: 'Shop Relaunch' }, { $id: 't1', woid: '100131', topic: 'Website Praxis' }, { $id: 'xyz12345', woid: '', title: 'Ohne Nummer' }, ] test('zeitquelleOptionen: Projekt-Tickets zuerst, dann gewählte, dann der Rest; unbekannte Auswahl bleibt sichtbar', () => { const o = zeitquelleOptionen(TICKETS, ['t9', 'alt77'], ['t1']) assert.deepEqual(o.map((x) => x.id), ['t1', 't9', 'alt77', 't2', 'xyz12345']) assert.deepEqual(o.map((x) => x.gewaehlt), [false, true, true, false, false]) assert.deepEqual(o.map((x) => x.projekt), [true, false, false, false, false]) assert.equal(o[0].label, '#100131 Website Praxis') assert.equal(o[1].label, '#100149 Neueste Anfrage') assert.equal(o[2].label, 'Ticket alt77') assert.equal(o[4].label, '#12345 Ohne Nummer') assert.deepEqual(zeitquelleOptionen(null, [], []), []) }) test('schalteZeitquelle: höchstens 10, keine Doppelten, Reihenfolge der Auswahl', () => { assert.deepEqual(schalteZeitquelle(['a'], 'b', true), ['a', 'b']) assert.deepEqual(schalteZeitquelle(['a', 'b'], 'a', false), ['b']) assert.deepEqual(schalteZeitquelle(['a'], 'a', true), ['a']) assert.deepEqual(schalteZeitquelle(undefined, 'a', true), ['a']) const zehn = Array.from({ length: 10 }, (_, i) => `t${i}`) assert.deepEqual(schalteZeitquelle(zehn, 'neu', true), zehn) assert.deepEqual(schalteZeitquelle(zehn, 't3', false), zehn.filter((id) => id !== 't3')) }) test('kundenartWirksam: die Kundenart am Kunden gilt, sonst die im Formular gewählte', () => { assert.equal(kundenartWirksam({ kundenart: 'unternehmen' }, 'privat'), 'unternehmen') assert.equal(kundenartWirksam({ kundenart: '' }, 'privat'), 'privat') assert.equal(kundenartWirksam({}, 'unternehmen'), 'unternehmen') assert.equal(kundenartWirksam(null, 'privat'), 'privat') assert.equal(kundenartWirksam({ kundenart: 'privat' }, ''), 'privat') assert.equal(kundenartWirksam({ kundenart: 'firma' }, ''), '') assert.equal(kundenartWirksam({ kundenart: '' }, 'firma'), '') assert.equal(kundenartWirksam(null, undefined), '') }) test('vorOrtHinweis: nur bei „Vor Ort“ und Privatperson (§ 312f Abs. 1 BGB), nie als Sperre', () => { const TEXT = 'Bei Aufträgen vor Ort mit Privatpersonen die Auftragsbestätigung zusätzlich ausdrucken und übergeben.' assert.equal(VOR_ORT_HINWEIS, TEXT) assert.equal(vorOrtHinweis('vor_ort', 'privat'), TEXT) for (const [weg, art] of [['vor_ort', 'unternehmen'], ['vor_ort', ''], ['telefon', 'privat'], ['muendlich', 'privat'], ['email', 'privat'], ['chat', 'privat'], ['', 'privat'], [undefined, 'privat']]) { assert.equal(vorOrtHinweis(weg, art), '', `${weg}/${art}`) } }) test('auftragHinweise: fehlende Steuernummer, dann „vor Ort“ bei Privatperson; sonst nichts', () => { const STN = 'Die Steuernummer fehlt noch – sie steht dann nicht auf der Auftragsbestätigung. Eintragen unter Finanzen → Einstellungen.' const VOR = 'Bei Aufträgen vor Ort mit Privatpersonen die Auftragsbestätigung zusätzlich ausdrucken und übergeben.' const hinweise = (o) => auftragHinweise({ kunde: KUNDE, form: festpreisForm(), einstellungen: { steuernummer: '12/345/67890' }, ...o }) assert.deepEqual(hinweise(), []) // festpreisForm: Privatperson, Weg „telefon“, Steuernummer vorhanden assert.deepEqual(hinweise({ einstellungen: { steuernummer: '' } }), [{ key: 'steuernummer', text: STN }]) assert.deepEqual(hinweise({ einstellungen: {} }).map((h) => h.key), ['steuernummer']) // Einstellungen noch nicht geladen (undefined) oder nicht ladbar (null): keine Behauptung assert.deepEqual(hinweise({ einstellungen: undefined }), []) assert.deepEqual(hinweise({ einstellungen: null }), []) assert.deepEqual(hinweise({ form: festpreisForm({ auftragWeg: 'vor_ort' }) }), [{ key: 'vorOrt', text: VOR }]) assert.deepEqual(hinweise({ form: festpreisForm({ auftragWeg: 'vor_ort', kundenart: 'unternehmen' }) }), []) assert.deepEqual(hinweise({ form: festpreisForm({ auftragWeg: 'vor_ort', kundenart: '' }) }), []) // die Kundenart des Kunden hat Vorrang vor dem Formular const privatAmKunden = { kunde: { ...KUNDE, kundenart: 'privat' }, form: festpreisForm({ auftragWeg: 'vor_ort', kundenart: '' }) } assert.deepEqual(hinweise(privatAmKunden).map((h) => h.key), ['vorOrt']) assert.deepEqual(hinweise({ ...privatAmKunden, einstellungen: { steuernummer: '' } }).map((h) => h.key), ['steuernummer', 'vorOrt']) })