From 8d3eb73db738807cf859f61580ba58087b0ae86a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 15:48:07 +0000 Subject: [PATCH] Kundenansicht P5: Kundenart-Helfer und Felder in /api/auth/me Co-Authored-By: Claude Sonnet 5.5 --- server/routes/auth.js | 3 +++ server/services/kundenart.js | 21 +++++++++++++++ test/kundenart.test.js | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 server/services/kundenart.js create mode 100644 test/kundenart.test.js diff --git a/server/routes/auth.js b/server/routes/auth.js index c264d06..3aa0f81 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -42,6 +42,7 @@ import { import { logViewAction } from '../services/customerViewLog.js' import { isStaffAccount } from '../services/staffLabels.js' import { neueAnsichtFuerSession } from '../services/kundenansicht/schalter.js' +import { kundenartGespeichert, kundenartWirksam } from '../services/kundenart.js' const router = Router() @@ -56,6 +57,8 @@ function sanitizeCustomer(customer) { location: customer.location || '', customerStatus: customer.customerStatus || '', portalAccessEnabled: Boolean(customer.portalAccessEnabled), + kundenart: kundenartGespeichert(customer), + kundenartWirksam: kundenartWirksam(customer), } } diff --git a/server/services/kundenart.js b/server/services/kundenart.js new file mode 100644 index 0000000..97047d1 --- /dev/null +++ b/server/services/kundenart.js @@ -0,0 +1,21 @@ +import { KUNDENARTEN } from './auftraege/konstanten.js' + +/** + * Kundenart (V4, Spec E4): Der Server ist die einzige Quelle; er nimmt die Kundenart nie vom Client. + * Gespeichert wird sie am Kundendokument (customers.kundenart). Vergleich exakt: kein Trimmen, kein Kleinschreiben. + * Eine leere oder unbekannte Kundenart gilt ueberall als privat (kundenartWirksam). + */ +export { KUNDENARTEN } + +/** 'unternehmen' | 'privat' | '' (leer, unbekannt, kein Dokument) */ +export function kundenartGespeichert(customerDoc) { + const wert = customerDoc?.kundenart + return KUNDENARTEN.includes(wert) ? wert : '' +} + +/** 'unternehmen' | 'privat'; leer oder unbekannt gilt als 'privat' */ +export function kundenartWirksam(customerDoc) { + return kundenartGespeichert(customerDoc) || 'privat' +} + +export const NUR_BETRIEB_TEXT = 'Diese Funktion gibt es nur für Betriebe. Schreib uns im Chat, wenn du Fragen dazu hast.' diff --git a/test/kundenart.test.js b/test/kundenart.test.js new file mode 100644 index 0000000..89aff58 --- /dev/null +++ b/test/kundenart.test.js @@ -0,0 +1,52 @@ +import { test, after } from 'node:test' +import assert from 'node:assert/strict' +import { startAppwriteMock } from './support/appwriteMock.js' +import { startFakeStripe } from './support/fakeStripe.js' +import { useTestEnv } from './support/testEnv.js' +import { schemaKeys } from './support/schema.js' +import { KUNDENARTEN, kundenartGespeichert, kundenartWirksam, NUR_BETRIEB_TEXT } from '../server/services/kundenart.js' + +// V4: Kundenart-Hilfen (Server ist die einzige Quelle) und die Felder in /api/auth/me und Login +const aw = await startAppwriteMock({ schema: { customerViewLog: schemaKeys('customerViewLog') } }) +const st = await startFakeStripe() +useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) +const { config } = await import('../server/config.js') +const { startTestApp, post, rawGet } = await import('./support/testApp.js') +const { kundenansichtCookie } = await import('./support/fixtures.js') +const { default: authRoutes } = await import('../server/routes/auth.js') +const app = await startTestApp({ '/api/auth': authRoutes }) +after(() => Promise.all([app.close(), aw.close(), st.close()])) + +aw.addUser({ $id: 'u1', email: 'k@example.invalid', password: 'passwort-eins' }) +aw.addUser({ $id: 'u2', email: 'k2@example.invalid', password: 'passwort-zwei' }) +aw.seed('customers', { $id: 'c1', appwriteUserId: 'u1', email: 'k@example.invalid', name: 'Kunde Eins', customerStatus: 'active', portalAccessEnabled: true, kundenart: 'unternehmen' }) +aw.seed('customerPortalAccess', { $id: 'pa1', customerId: 'c1', enabled: true }) +aw.seed('customers', { $id: 'c2', appwriteUserId: 'u2', email: 'k2@example.invalid', name: 'Kunde Zwei', customerStatus: 'active', portalAccessEnabled: true }) +aw.seed('customerPortalAccess', { $id: 'pa2', customerId: 'c2', enabled: true }) + +test('nur exakte Werte zählen, alles andere ist leer bzw. privat', () => { + for (const doc of [undefined, null, {}, { kundenart: '' }, { kundenart: 'Unternehmen' }, { kundenart: ' privat' }, { kundenart: 'firma' }, { kundenart: 7 }, 'unternehmen']) { + assert.equal(kundenartGespeichert(doc), '') + assert.equal(kundenartWirksam(doc), 'privat') + } + assert.deepEqual([kundenartGespeichert({ kundenart: 'unternehmen' }), kundenartWirksam({ kundenart: 'unternehmen' })], ['unternehmen', 'unternehmen']) + assert.deepEqual([kundenartGespeichert({ kundenart: 'privat' }), kundenartWirksam({ kundenart: 'privat' })], ['privat', 'privat']) + assert.deepEqual(KUNDENARTEN, ['unternehmen', 'privat']) + assert.equal(NUR_BETRIEB_TEXT, 'Diese Funktion gibt es nur für Betriebe. Schreib uns im Chat, wenn du Fragen dazu hast.') + assert.ok(!NUR_BETRIEB_TEXT.includes('€')) +}) + +test('/api/auth/me und Login liefern kundenart und kundenartWirksam in allen drei Zweigen', async () => { + const login = await post('/api/auth/login', { email: 'k@example.invalid', password: 'passwort-eins' }) + assert.equal(login.status, 200) + assert.deepEqual([login.body.customer.kundenart, login.body.customer.kundenartWirksam], ['unternehmen', 'unternehmen']) + const cookie = login.headers.getSetCookie().find((c) => c.startsWith(`${config.cookieName}=`)).split(';')[0] // echter Cookie des Logins + const me = await rawGet('/api/auth/me', { cookie }) + assert.equal(me.body.authenticated, true) + assert.deepEqual([me.body.customer.kundenart, me.body.customer.kundenartWirksam], ['unternehmen', 'unternehmen']) + const leer = await rawGet('/api/auth/me', { cookie: kundenansichtCookie({ customerId: 'c2' }) }) // Kundenansicht-Zweig, c2 ohne Feld + assert.equal(leer.body.viewAs, true) + assert.deepEqual([leer.body.customer.kundenart, leer.body.customer.kundenartWirksam], ['', 'privat']) + const login2 = await post('/api/auth/login', { email: 'k2@example.invalid', password: 'passwort-zwei' }) + assert.deepEqual([login2.body.customer.kundenart, login2.body.customer.kundenartWirksam], ['', 'privat']) +})