diff --git a/server/routes/team/arbeitsblaetter.js b/server/routes/team/arbeitsblaetter.js index 675a291..0da67dd 100644 --- a/server/routes/team/arbeitsblaetter.js +++ b/server/routes/team/arbeitsblaetter.js @@ -1,9 +1,25 @@ /** - * Gerüst von Strang L0: Arbeitsblätter fürs Ticketsystem. Mount: /api/team/arbeitsblaetter, in team/index.js hinter - * requireStaffJwt (jeder Mitarbeiter, daher kein eigener Schutz). L1 ergänzt GET /abgerechnet (?ticketId=). + * Arbeitsblaetter fuers Ticketsystem. Mount: /api/team/arbeitsblaetter, in team/index.js hinter requireStaffJwt (jeder + * Mitarbeiter, daher kein eigener Schutz). + * GET /abgerechnet?ticketId= { abgerechnet: { : '' | 'Entwurf' } }, ohne Betraege */ import { Router } from 'express' +import { sendeFehler } from '../../services/auftraege/fehler.js' +import { abgerechnetFuerTicket } from '../../services/auftraege/projektkonto.js' const router = Router() +// Appwrite-UID: erst pruefen, dann an Appwrite geben +const KENNUNG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,35}$/ + +router.get('/abgerechnet', async (req, res) => { + const ticketId = req.query.ticketId + if (typeof ticketId !== 'string' || !KENNUNG.test(ticketId)) return res.status(400).json({ error: 'ticketId fehlt oder ist ungültig.' }) + try { + res.json({ abgerechnet: await abgerechnetFuerTicket(ticketId) }) + } catch (err) { + sendeFehler(res, 'Abgerechnete Arbeitsblätter laden', err) + } +}) + export default router diff --git a/test/auftraegeRoutesProjektkonten.test.js b/test/auftraegeRoutesProjektkonten.test.js index 5807f52..d20cb4e 100644 --- a/test/auftraegeRoutesProjektkonten.test.js +++ b/test/auftraegeRoutesProjektkonten.test.js @@ -131,3 +131,42 @@ test('Fehler in Appwrite: 500 mit fester Meldung, ohne Text von Appwrite', async assert.equal(res.status, 500) assert.deepEqual(res.body, { error: 'Interner Fehler.' }) }) + +// ---------- Team-Route (Task 8) ---------- + +const ABGERECHNET = '/api/team/arbeitsblaetter/abgerechnet' +const seedPr = (invoiceId, { arbeitsblattIds = [], ...over } = {}) => + aw.seed(config.collections.projektRechnungen, { + $id: invoiceId, invoiceId, kundenId: 'c1', projektId: 'p1', auftragId: '', art: 'festbetrag', betragCents: 0, + arbeitsblattIdsJson: JSON.stringify(arbeitsblattIds), rechnungsnummer: '', aktiv: true, + erstelltVon: 'kenso@webklar.com', erstelltAm: '2026-10-01T10:00:00.000Z', ...over, + }) + +test('Abzeichen-Route: ohne JWT 401, Konto ohne Label 403, Mitarbeiter und Admin dürfen', async () => { + const path = `${ABGERECHNET}?ticketId=t1` + assert.equal((await rawGet(path)).status, 401) + assert.equal((await rawGet(path, ohneLabel)).status, 403) + assert.equal((await rawGet(path, staff)).status, 200) + assert.equal((await rawGet(path, admin)).status, 200) +}) + +test('Abzeichen-Route: ticketId fehlt, ist leer, doppelt oder unmöglich -> 400', async () => { + for (const query of ['', '?ticketId=', '?ticketId=t1&ticketId=t2', '?ticketId=../x', `?ticketId=${'t'.repeat(37)}`, '?ticketId[a]=b']) { + const res = await rawGet(`${ABGERECHNET}${query}`, staff) + assert.equal(res.status, 400, query) + assert.deepEqual(res.body, { error: 'ticketId fehlt oder ist ungültig.' }, query) + } +}) + +test('Abzeichen-Route: { abgerechnet: { Blatt: Rechnungsnummer | Entwurf } } ohne Beträge, unbekanntes Ticket leer', async () => { + seedBasis() + seedPr('in_1', { rechnungsnummer: 'WEBKLAR-0007', arbeitsblattIds: ['w1'] }) + seedPr('in_2', { rechnungsnummer: '', arbeitsblattIds: ['w2'] }) + const res = await rawGet(`${ABGERECHNET}?ticketId=t1`, staff) + assert.equal(res.status, 200) + assert.deepEqual(res.body, { abgerechnet: { w1: 'WEBKLAR-0007', w2: 'Entwurf' } }) + assert.ok(!/cents|betrag|in_1|in_2/i.test(JSON.stringify(res.body)), 'weder Beträge noch Rechnungs-IDs') + const unbekannt = await rawGet(`${ABGERECHNET}?ticketId=gibtsnicht`, staff) + assert.equal(unbekannt.status, 200) + assert.deepEqual(unbekannt.body, { abgerechnet: {} }) +})