From 35b0438d00d3d7807fe1a06a063d3ab4ab7ab58e Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Wed, 7 Oct 2026 14:46:30 +0000 Subject: [PATCH] Buchungskalender: Team-API wie Webshop (anfrage null, Stripe-Status) Co-Authored-By: Claude Opus 5.5 --- server/routes/team/unterkunft.js | 7 ++--- server/routes/unterkunft.js | 7 ++++- server/services/unterkunft/anfrageFunktion.js | 2 +- test/unterkunftAnfrageFunktion.test.js | 26 ++++++++++++++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/server/routes/team/unterkunft.js b/server/routes/team/unterkunft.js index 372bbff..3fe9bd6 100644 --- a/server/routes/team/unterkunft.js +++ b/server/routes/team/unterkunft.js @@ -30,7 +30,8 @@ async function zaehle(collection, queries, was) { } } -const stripeZustand = (einst) => (!einst?.stripeAccountId ? 'keins' : einst.stripeStatus?.charges_enabled === true ? 'aktiv' : 'onboarding') +/** stripeStatus (JSON-Objekt in ukEinstellungen, schon geparst) oder null. */ +const stripeStatus = (roh) => (roh !== null && typeof roh === 'object' && !Array.isArray(roh) ? roh : null) async function projektStand(project, feature) { let einst = null @@ -53,8 +54,8 @@ async function projektStand(project, feature) { projectId: project.$id, name: project.projectName || '', state: feature?.state || 'off', - anfrage: anfrageAnsichtTeam(einst), - stripe: { status: stripeZustand(einst) }, + anfrage: einst?.anfrageStatus ? anfrageAnsichtTeam(einst) : null, + stripe: { accountId: Boolean(einst?.stripeAccountId), status: stripeStatus(einst?.stripeStatus) }, unterkuenfte, offeneAnfragen, } diff --git a/server/routes/unterkunft.js b/server/routes/unterkunft.js index 5a49d44..aca1cb9 100644 --- a/server/routes/unterkunft.js +++ b/server/routes/unterkunft.js @@ -234,7 +234,12 @@ router.get('/status', route(async (req, res) => { const antwort = { state, stripe: { status: 'keins', vorhandenesShopKonto: false }, onlineZahlung: false, unterkuenfte: 0, rechnungenVorhanden: false } if (state === 'off') { // Anfrage der Funktion: offen | abgelehnt | angenommen (zurueckgezogen und keine zaehlen als keine) - const ansicht = doc ? funktionAnfrageAnsicht(await sp.ladeEinstellungen(doc.$id)) : null + let ansicht = null + try { + ansicht = doc ? funktionAnfrageAnsicht(await sp.ladeEinstellungen(doc.$id)) : null + } catch (err) { + console.warn(`[unterkunft] Anfrage im Status nicht lesbar: ${err?.message}`) + } antwort.anfrage = ansicht && ansicht.status !== 'zurueckgezogen' ? ansicht : null return res.json(antwort) } diff --git a/server/services/unterkunft/anfrageFunktion.js b/server/services/unterkunft/anfrageFunktion.js index 901c839..9163e4a 100644 --- a/server/services/unterkunft/anfrageFunktion.js +++ b/server/services/unterkunft/anfrageFunktion.js @@ -247,7 +247,7 @@ export async function entscheideAnfrage({ customerId, projectId, entscheidung, b throw err?.status === 400 && err.type ? stop(403, 'Kein Zugriff auf dieses Projekt') : err } const feature = await getFeatureDoc(customerId, projectId, 'unterkunft') - if (!feature) throw stop(409, MELDUNG.nichtEntscheidbar, 'nicht_offen') + if (!feature || feature.state !== 'off') throw stop(409, MELDUNG.nichtEntscheidbar, 'nicht_offen') const neu = await withLocks([`uk-einst:${feature.$id}`], async () => { const einst = await ladeEinstellungen(feature.$id) if (!einst || einst.anfrageStatus !== 'offen') throw stop(409, MELDUNG.nichtEntscheidbar, 'nicht_offen') diff --git a/test/unterkunftAnfrageFunktion.test.js b/test/unterkunftAnfrageFunktion.test.js index e1bcf5d..23cccb5 100644 --- a/test/unterkunftAnfrageFunktion.test.js +++ b/test/unterkunftAnfrageFunktion.test.js @@ -104,6 +104,7 @@ test('Anfrage stellen: gespeichert, T22 ans Team, Status-Block fuer state off', assert.equal(aufrufe.admin[0].values.einheiten, '3') assert.equal(aufrufe.admin[0].values.art, 'Monteurwohnung') assert.equal(aufrufe.admin[0].values.portale, 'Booking.com, Airbnb') + assert.equal(aufrufe.admin[0].values.woms_url, `${config.womsBaseUrl}/customers/c1?tab=funktionen`) assert.equal(aufrufe.kunde.length, 0) const s = (await status()).body.anfrage assert.equal(s.status, 'offen') @@ -167,10 +168,7 @@ test('Team: Uebersicht je Projekt und Entscheidung annehmen/ablehnen mit K34', a assert.equal((await rawGet('/api/team/unterkunft/c1', {})).status, 401) const leer = (await rawGet('/api/team/unterkunft/c1', staff)).body.projects assert.equal(leer.length, 1) - assert.deepEqual(leer[0], { - projectId: P, name: 'Donau', state: 'off', unterkuenfte: 0, offeneAnfragen: 0, stripe: { status: 'keins' }, - anfrage: { status: '', at: null, art: '', einheiten: null, portale: false, nachricht: '', entschiedenAt: null, entschiedenVon: '', begruendung: '' }, - }) + assert.deepEqual(leer[0], { projectId: P, name: 'Donau', state: 'off', unterkuenfte: 0, offeneAnfragen: 0, stripe: { accountId: false, status: null }, anfrage: null }) await stelle({ art: 'monteur', portale: [] }) const p = (await rawGet('/api/team/unterkunft/c1', staff)).body.projects[0] assert.equal(p.anfrage.status, 'offen') @@ -191,6 +189,7 @@ test('Team: Uebersicht je Projekt und Entscheidung annehmen/ablehnen mit K34', a assert.equal(aufrufe.kunde[0].template, 'K34') assert.equal(aufrufe.kunde[0].an, customers.c1.email) assert.equal(aufrufe.kunde[0].values.begruendung, 'Passt nicht.') + assert.equal(aufrufe.kunde[0].values.portal_url, `${config.publicBaseUrl}/dashboard.html#buchungen?projekt=${P}`) assert.deepEqual(aufrufe.kunde[0].blocks, { angenommen: false, abgelehnt: true }) assert.equal((await status()).body.anfrage.begruendung, 'Passt nicht.') assert.equal((await entscheide({ entscheidung: 'annehmen' })).status, 409, 'nicht mehr offen') @@ -211,6 +210,25 @@ test('Team: Uebersicht je Projekt und Entscheidung annehmen/ablehnen mit K34', a assert.equal((await rawPost('/api/team/unterkunft/c1/anfrage', { projectId: 'pfremd', entscheidung: 'annehmen' }, staff)).status, 403) }) +test('Team: Stripe-Form wie Webshop, Entscheidung nur bei state off, Status ohne Lesefehler-500', async () => { + await stelle() + aw.seed('ukEinstellungen', { ...einst(), stripeAccountId: 'acct_1', stripeStatus: JSON.stringify({ charges_enabled: true }) }) + const p = (await rawGet('/api/team/unterkunft/c1', staff)).body.projects[0] + assert.deepEqual(p.stripe, { accountId: true, status: { charges_enabled: true } }) + aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(FEATURE), state: 'setup' }) + assert.equal((await entscheide({ entscheidung: 'annehmen' })).status, 409) + aw.seed('portalFeatures', { ...aw.docs('portalFeatures').get(FEATURE), state: 'off' }) + const echt = config.collections.ukEinstellungen + config.collections.ukEinstellungen = 'gibt_es_nicht' + try { + const s = await status() + assert.equal(s.status, 200) + assert.equal(s.body.anfrage, null) + } finally { + config.collections.ukEinstellungen = echt + } +}) + test('anfrageOffenePunkte: ohne Dokumente offen, Lesefehler wird weitergereicht', async () => { assert.equal((await af.anfrageOffenePunkte({ customerId: 'c1', projectId: P })).length, 1) aw.seed('portalFeatures', { $id: FEATURE, customerId: 'c1', projectId: P, featureKey: 'unterkunft', siteKey: 'abcdefghijklmnop', state: 'off', enabled: false })