import test from 'node:test' import assert from 'node:assert/strict' import { loadModule } from './load.mjs' const { AB_HINWEIS, ENTWURF_HINWEIS, startProjekt, projektOptionen, wirksamesProjekt, gueltigeFestpreisAbs, aufwandAuftrag, abLabel, berechnetFuerAuftrag, hatEntwurfZurAuftrag, artOptionen, standardArt, standardAuftrag, anzahlungStandard, ausgewaehlteIds, blaetterSumme, blattAusAppwrite, ticketOptionen, } = await loadModule('src/lib/rechnungDialogView.js') const ab = (over = {}) => ({ id: 'a1', nummer: 'AB-2026-0002', status: 'bestaetigt', art: 'festpreis', summeCents: 80000, gesendetAm: '2026-10-06T08:15:00.000Z', zahlungsplan: { art: 'anzahlung', prozent: 50, text: '' }, stundensatzCents: null, ...over, }) const rk = (over = {}) => ({ id: 'in_1', number: 'WEBKLAR-0007', status: 'offen', createdDate: '2026-10-08', totalCents: 40000, paidCents: 0, art: 'anzahlung', auftragId: 'a1', ...over, }) const bl = (id, minuten, over = {}) => ({ id, ticketId: 't1', woid: '100131', datum: '2026-10-06', minuten, text: 'Startseite', aboZeit: false, kommentar: false, ...over, }) const KONTEN = [ { projektId: 'p1', name: 'Website Relaunch', tickets: [{ id: 't1', woid: '100131' }] }, { projektId: 'p2', name: 'Shop', tickets: [] }, ] const optionen = (args) => artOptionen({ projektId: 'p1', auftraege: [], rechnungen: [], auftraegeAktiv: true, ...args }) const zustand = (o) => o.map((x) => [x.key, x.enabled]) test('startProjekt: Vorauswahl der Aufrufer (undefined = keine, null = Ohne Projekt, Text = Projekt)', () => { assert.equal(startProjekt(undefined), null) assert.equal(startProjekt(null), '') assert.equal(startProjekt('p1'), 'p1') }) test('projektOptionen: Projekte mit Ticketnummer, am Ende „Ohne Projekt“', () => { assert.deepEqual(projektOptionen(KONTEN), [ { value: 'p1', label: 'Website Relaunch · #100131' }, { value: 'p2', label: 'Shop' }, { value: '', label: 'Ohne Projekt' }, ]) assert.deepEqual(projektOptionen([]), [{ value: '', label: 'Ohne Projekt' }]) assert.deepEqual(projektOptionen(undefined), [{ value: '', label: 'Ohne Projekt' }]) }) test('wirksamesProjekt: null = noch zu wählen, „“ = Ohne Projekt, Kunde ohne Projekte = Ohne Projekt', () => { assert.equal(wirksamesProjekt('', KONTEN), '') assert.equal(wirksamesProjekt('p2', KONTEN), 'p2') assert.equal(wirksamesProjekt('gibtsnicht', KONTEN), null) assert.equal(wirksamesProjekt('p1', undefined), 'p1') // Liste noch nicht geladen: die Vorauswahl bleibt assert.equal(wirksamesProjekt(null, KONTEN), null) assert.equal(wirksamesProjekt(null, []), '') // Kunde ohne Projekte: gleich „Ohne Projekt“ assert.equal(wirksamesProjekt(null, undefined), null) }) test('gueltigeFestpreisAbs: nur Festpreis mit Status versendet oder bestätigt, neueste zuerst', () => { const list = [ ab({ id: 'a1' }), ab({ id: 'a2', status: 'entwurf' }), ab({ id: 'a3', art: 'aufwand' }), ab({ id: 'a4', status: 'abgerechnet' }), ab({ id: 'a5', status: 'versendet', gesendetAm: '2026-10-07T08:00:00.000Z' }), ab({ id: 'a6', status: 'storniert' }), ] assert.deepEqual(gueltigeFestpreisAbs(list).map((a) => a.id), ['a5', 'a1']) assert.deepEqual(gueltigeFestpreisAbs(undefined), []) }) test('aufwandAuftrag: jüngste gültige Aufwands-AB', () => { const list = [ ab({ id: 'x1', art: 'aufwand', status: 'bestaetigt', gesendetAm: '2026-10-01T08:00:00.000Z' }), ab({ id: 'x2', art: 'aufwand', status: 'versendet', gesendetAm: '2026-10-05T08:00:00.000Z' }), ab({ id: 'x3', art: 'aufwand', status: 'storniert', gesendetAm: '2026-10-09T08:00:00.000Z' }), ab({ id: 'a1' }), ] assert.equal(aufwandAuftrag(list).id, 'x2') assert.equal(aufwandAuftrag([ab()]), null) assert.equal(aufwandAuftrag(undefined), null) }) test('abLabel: Nummer, Datum des Sendens und Summe bzw. „nach Aufwand“', () => { assert.equal(abLabel(ab()), 'AB-2026-0002 vom 06.10.2026 · 800,00 €') assert.equal(abLabel(ab({ art: 'aufwand', summeCents: null })), 'AB-2026-0002 vom 06.10.2026 · nach Aufwand') }) test('berechnetFuerAuftrag und hatEntwurfZurAuftrag: nur finalisierte zählen, ein Entwurf blockiert', () => { const list = [ rk(), rk({ id: 'in_2', status: 'bezahlt', totalCents: 10000 }), rk({ id: 'in_3', status: 'entwurf', totalCents: 5000 }), rk({ id: 'in_4', status: 'storniert', totalCents: 7000 }), rk({ id: 'in_5', auftragId: 'a2', totalCents: 9000 }), ] assert.equal(berechnetFuerAuftrag(list, 'a1'), 50000) assert.equal(berechnetFuerAuftrag(list, 'a2'), 9000) assert.equal(berechnetFuerAuftrag(undefined, 'a1'), 0) assert.equal(hatEntwurfZurAuftrag(list, 'a1'), true) assert.equal(hatEntwurfZurAuftrag(list, 'a2'), false) }) test('artOptionen: mit gültiger Festpreis-AB sind alle vier Arten wählbar', () => { const o = optionen({ auftraege: [ab()] }) assert.deepEqual(zustand(o), [['anzahlung', true], ['schluss', true], ['aufwand', true], ['festbetrag', true]]) assert.deepEqual(o.map((x) => x.label), ['Anzahlung', 'Schlussrechnung', 'Nach Aufwand', 'Festbetrag']) }) test('artOptionen: ohne AB oder bei ausgeschaltetem Schalter erklärt ein Hinweis die Sperre', () => { for (const args of [{ auftraege: [] }, { auftraege: [ab()], auftraegeAktiv: false }, { auftraege: [ab({ status: 'entwurf' })] }]) { const o = optionen(args) assert.deepEqual(zustand(o), [['anzahlung', false], ['schluss', false], ['aufwand', true], ['festbetrag', true]]) assert.equal(o[0].hint, AB_HINWEIS) assert.equal(o[1].hint, AB_HINWEIS) } assert.equal(AB_HINWEIS, 'Anzahlung und Schlussrechnung brauchen eine Auftragsbestätigung.') }) test('artOptionen: ein offener Entwurf zur AB sperrt die Schlussrechnung, nicht die Anzahlung', () => { const o = optionen({ auftraege: [ab()], rechnungen: [rk({ status: 'entwurf' })] }) assert.deepEqual(zustand(o), [['anzahlung', true], ['schluss', false], ['aufwand', true], ['festbetrag', true]]) assert.equal(o[1].hint, ENTWURF_HINWEIS) assert.equal(ENTWURF_HINWEIS, 'Zu dieser Auftragsbestätigung gibt es noch einen Entwurf. Erst senden oder löschen.') // zwei ABs: die Schlussrechnung bleibt wählbar, solange eine AB ohne Entwurf da ist const zwei = optionen({ auftraege: [ab(), ab({ id: 'a2', nummer: 'AB-2026-0003' })], rechnungen: [rk({ status: 'entwurf' })] }) assert.equal(zwei[1].enabled, true) }) test('artOptionen: ohne Wahl keine Arten, ohne Projekt nur die freie Rechnung', () => { assert.deepEqual(artOptionen({ projektId: null, auftraege: [ab()], rechnungen: [], auftraegeAktiv: true }), []) const frei = artOptionen({ projektId: '', auftraege: [ab()], rechnungen: [], auftraegeAktiv: true }) assert.deepEqual(zustand(frei), [['frei', true]]) }) test('standardArt: Anzahlung, Schlussrechnung, Aufwand oder Festbetrag je nach Lage', () => { const lage = (args, extra = {}) => { const eingabe = { auftraege: [], rechnungen: [], offeneMinuten: 0, ...args, ...extra } return standardArt(optionen({ auftraege: eingabe.auftraege, rechnungen: eingabe.rechnungen }), eingabe) } assert.equal(lage({ auftraege: [ab()] }), 'anzahlung') // Zahlungsplan Anzahlung, noch nichts berechnet assert.equal(lage({ auftraege: [ab()], rechnungen: [rk()] }), 'schluss') // Anzahlung schon berechnet const nachAbnahme = ab({ zahlungsplan: { art: 'nach_abnahme', prozent: null, text: '' } }) assert.equal(lage({ auftraege: [nachAbnahme] }), 'schluss') assert.equal(lage({}, { offeneMinuten: 90 }), 'aufwand') assert.equal(lage({}), 'festbetrag') const ohneProjekt = artOptionen({ projektId: '', auftraege: [], rechnungen: [], auftraegeAktiv: true }) assert.equal(standardArt(ohneProjekt, { auftraege: [], rechnungen: [], offeneMinuten: 0 }), 'frei') assert.equal(standardArt([], { auftraege: [], rechnungen: [], offeneMinuten: 0 }), '') }) test('standardAuftrag: die jüngste passende AB, bei Schlussrechnung ohne offenen Entwurf', () => { const neu = ab({ id: 'a5', gesendetAm: '2026-10-07T08:00:00.000Z' }) assert.equal(standardAuftrag('anzahlung', [ab(), neu]), 'a5') assert.equal(standardAuftrag('schluss', [ab(), neu]), 'a5') assert.equal(standardAuftrag('schluss', [ab(), neu], [rk({ auftragId: 'a5', status: 'entwurf' })]), 'a1') assert.equal(standardAuftrag('anzahlung', []), '') assert.equal(standardAuftrag('aufwand', [ab({ id: 'x2', art: 'aufwand' })]), '') // die Aufwands-AB ordnet der Server zu assert.equal(standardAuftrag('festbetrag', [ab()]), '') }) test('anzahlungStandard: Prozent aus dem Zahlungsplan, sonst 50', () => { const vorgabe = { modus: 'prozent', wert: '50', bezeichnung: 'Anzahlung' } assert.deepEqual(anzahlungStandard(ab()), vorgabe) assert.deepEqual(anzahlungStandard(ab({ zahlungsplan: { art: 'anzahlung', prozent: 33.5, text: '' } })), { ...vorgabe, wert: '33,5' }) assert.deepEqual(anzahlungStandard(ab({ zahlungsplan: { art: 'nach_abnahme', prozent: null, text: '' } })), vorgabe) assert.deepEqual(anzahlungStandard(null), vorgabe) }) test('ausgewaehlteIds und blaetterSumme: alle sind vorausgewählt, abgewählte fallen heraus', () => { const B = [bl('b1', 60), bl('b2', 90), bl('b3', 45)] assert.deepEqual(ausgewaehlteIds(B, []), ['b1', 'b2', 'b3']) assert.deepEqual(ausgewaehlteIds(B, ['b2']), ['b1', 'b3']) assert.deepEqual(ausgewaehlteIds(undefined, []), []) assert.deepEqual(blaetterSumme(B, ['b1', 'b3']), { anzahl: 2, minuten: 105 }) assert.deepEqual(blaetterSumme(B, []), { anzahl: 0, minuten: 0 }) }) const ws = (over = {}) => ({ $id: 'w1', woid: '100140', workorderId: 't2', totalTime: 60, supporters: '', isComment: false, countsToPlan: false, startDate: '06.10.2026', endDate: '06.10.2026', details: 'Kontaktformular gebaut', $createdAt: '2026-10-06T07:00:00.000Z', ...over, }) test('blattAusAppwrite: abrechenbares Blatt mit Minuten (inkl. Unterstützung), Datum und kurzem Text', () => { assert.deepEqual(blattAusAppwrite(ws(), {}), { id: 'w1', ticketId: 't2', woid: '100140', datum: '2026-10-06', minuten: 60, text: 'Kontaktformular gebaut', aboZeit: false, kommentar: false, }) // Unterstützung zählt mit: 60 + 1 × 30 const mit = ws({ supporters: JSON.stringify([{ employeeId: 'e1', name: 'A', task: 'Review' }]) }) assert.equal(blattAusAppwrite(mit, {}).minuten, 90) // ein Altwert außerhalb des 15-Minuten-Rasters wird aufgerundet assert.equal(blattAusAppwrite(ws({ totalTime: 70 }), {}).minuten, 75) // ohne gültiges Datum zählt der Erstellungstag, der Text ist die erste Zeile, höchstens 120 Zeichen assert.equal(blattAusAppwrite(ws({ startDate: '', endDate: '' }), {}).datum, '2026-10-06') assert.equal(blattAusAppwrite(ws({ details: 'Zeile 1\nZeile 2' }), {}).text, 'Zeile 1') assert.equal(blattAusAppwrite(ws({ details: 'a'.repeat(300) }), {}).text.length, 120) }) test('blattAusAppwrite: Kommentar, Abo-Zeit, 0 Minuten und schon Abgerechnetes sind null', () => { assert.equal(blattAusAppwrite(ws({ isComment: true }), {}), null) assert.equal(blattAusAppwrite(ws({ countsToPlan: true }), {}), null) assert.equal(blattAusAppwrite(ws({ totalTime: 0 }), {}), null) assert.equal(blattAusAppwrite(ws(), { w1: 'WEBKLAR-0009' }), null) assert.equal(blattAusAppwrite(ws(), { w1: 'Entwurf' }), null) assert.equal(blattAusAppwrite({}, {}), null) }) test('ticketOptionen: Tickets des Kunden mit Nummer, ohne die schon gewählten', () => { const T = [ { $id: 't1', woid: '100131', topic: 'Relaunch' }, { $id: 't2', woid: '100140', topic: 'Kontaktformular' }, { $id: 't3', woid: '', topic: '', title: 'Alt' }, { $id: 't4', woid: '100150', topic: '', title: '' }, ] assert.deepEqual(ticketOptionen(T, { ausgeschlossen: ['t1'] }), [ { value: 't2', ticketId: 't2', woid: '100140', label: '#100140 · Kontaktformular' }, { value: 't4', ticketId: 't4', woid: '100150', label: '#100150 · Kein Betreff' }, ]) assert.deepEqual(ticketOptionen(undefined), []) })