Kundenansicht P5: Setup-Skript für Kundenart und Einrichtungs-Marker

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 15:47:04 +00:00
1 parent c2b7073d99
commit c5b1333953
4 files changed
+171 -4

No files matched your search

+2 -2
View File
@@ -188,8 +188,8 @@ test('Schema featureConsents: drei Indizes mit den Attributen aus der Vorgabe (i
})
test('schemaKeys(featureConsents) liefert dieselben Schluessel, der Nachbau nimmt sie an und lehnt andere ab', async () => {
assert.deepEqual(schemaKeys('featureConsents'), CONSENT_KEYS)
assert.deepEqual(schemaKeys('featureConsents'), FEATURE_CONSENT_ATTRIBUTES.map((a) => a.key))
assert.deepEqual(schemaKeys('featureConsents'), [...CONSENT_KEYS, 'kundenart'])
assert.deepEqual(schemaKeys('featureConsents'), [...FEATURE_CONSENT_ATTRIBUTES.map((a) => a.key), 'kundenart'])
schema.featureConsents = schemaKeys('featureConsents')
const { createDocument } = await import('../server/services/appwriteAdmin.js')
const doc = await createDocument(config.collections.featureConsents, {
+75
View File
@@ -0,0 +1,75 @@
import { test, after } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { startAppwriteMock } from './support/appwriteMock.js'
import { useTestEnv } from './support/testEnv.js'
import { schemaKeys } from './support/schema.js'
import { KUNDENANSICHT_ATTRIBUTE, main } from '../scripts/kundenansicht-setup.mjs'
// Setup-Skript der Kundenansicht neu (Paket P5): Kundenart im Nachweis, Einrichtungs-Marker an Ticket und Funktion.
// Das Skript laeuft hier nur gegen Mock-Clients, nie gegen Produktion.
const aw = await startAppwriteMock({ schema: {} })
after(() => aw.close())
useTestEnv({ appwriteUrl: aw.url, stripeUrl: 'http://127.0.0.1:1' })
const { config } = await import('../server/config.js')
const client = (vorhanden = {}) => {
const calls = { attrs: [], waits: [] }
return {
calls,
collectionExists: async () => true,
listAttributes: async (id) => (vorhanden[id] ?? []).map((key) => ({ key })),
createAttribute: async (id, a) => calls.attrs.push(`${id}.${a.key}`),
waitForAttribute: async (id, k) => calls.waits.push(`${id}.${k}`),
}
}
const ID = () => config.collections
const ALLE = () => [
`${ID().featureConsents}.kundenart`,
`${ID().portalFeatures}.einrichtungAbrechnung`,
`${ID().portalFeatures}.unlockedBy`,
`${ID().workorders}.einrichtungAbrechnung`,
`${ID().workorders}.einrichtungFuer`,
]
test('Schema: Schlüssel, Größen, nur optionale Strings; längste Werte passen hinein', () => {
const kurz = Object.fromEntries(Object.entries(KUNDENANSICHT_ATTRIBUTE).map(([c, l]) => [c, l.map((a) => `${a.key}:${a.size}`)]))
assert.deepEqual(kurz, {
featureConsents: ['kundenart:12'],
workorders: ['einrichtungFuer:16', 'einrichtungAbrechnung:8'],
portalFeatures: ['unlockedBy:8', 'einrichtungAbrechnung:8'],
})
assert.ok(Object.values(KUNDENANSICHT_ATTRIBUTE).flat().every((a) => a.type === 'string' && a.required === false))
assert.ok('unternehmen'.length <= 12 && 'projekt'.length <= 8 && 'unterkunft'.length <= 16)
})
test('--pruefen meldet alle fünf Lücken und legt nichts an', async () => {
const c = client()
const log = []
const r = await main({ pruefen: true, client: c, log: (z) => log.push(z) })
assert.deepEqual(r.missing.map((a) => `${a.collection}.${a.key}`).sort(), ALLE())
assert.deepEqual(c.calls, { attrs: [], waits: [] })
assert.ok(log.some((z) => z === `- ${ID().featureConsents}.kundenart FEHLT`))
})
test('ohne --pruefen: legt an und wartet; der zweite Lauf ist idempotent', async () => {
const c = client()
await main({ client: c, log: () => {} })
assert.deepEqual([...c.calls.attrs].sort(), ALLE())
assert.deepEqual([...c.calls.waits].sort(), ALLE())
const voll = client({
[ID().featureConsents]: ['kundenart'],
[ID().workorders]: ['einrichtungFuer', 'einrichtungAbrechnung'],
[ID().portalFeatures]: ['unlockedBy', 'einrichtungAbrechnung'],
})
assert.deepEqual((await main({ client: voll, log: () => {} })).missing, [])
assert.deepEqual(voll.calls.attrs, [])
})
test('schemaKeys liest das Skript; das Skript löscht nichts', () => {
assert.equal(schemaKeys('featureConsents').at(-1), 'kundenart')
const f = schemaKeys('portalFeatures')
assert.ok(f.includes('einrichtungAbrechnung'))
assert.equal(new Set(f).size, f.length)
assert.doesNotMatch(readFileSync(new URL('../scripts/kundenansicht-setup.mjs', import.meta.url), 'utf8'), /delete/i)
})
+6 -2
View File
@@ -5,6 +5,7 @@ import { UNTERKUNFT_COLLECTIONS } from '../../server/services/unterkunft/schema.
import { ZEITEN_COLLECTIONS, ZEITEN_FEATURE_ATTRIBUTES } from '../../server/services/zeiten/schema.js'
import { AUFTRAEGE_COLLECTIONS, AUFTRAEGE_ZUSATZ_ATTRIBUTE } from '../../server/services/auftraege/schema.js'
import { FEATURE_CONSENT_ATTRIBUTES, PORTAL_FEATURE_ATTRIBUTES } from '../../scripts/feature-setup.mjs'
import { KUNDENANSICHT_ATTRIBUTE } from '../../scripts/kundenansicht-setup.mjs'
// Setup-Skripte, die Collections in Appwrite anlegen (Blockformat: 'id', ... \n ],)
const SETUP_SCRIPTS = ['setup-abo-collections.mjs', 'setup-finanzen.mjs']
@@ -20,6 +21,8 @@ const SETUP_SCRIPTS = ['setup-abo-collections.mjs', 'setup-finanzen.mjs']
* portalFeatures bekommt zusaetzlich die Attribute aus scripts/feature-setup.mjs (PORTAL_FEATURE_ATTRIBUTES) und die
* Zeiten-Felder aus server/services/zeiten/schema.js (ZEITEN_FEATURE_ATTRIBUTES, angelegt von scripts/zeiten-setup.mjs);
* featureConsents (Zustimmungsnachweis) steht ganz dort (FEATURE_CONSENT_ATTRIBUTES);
* scripts/kundenansicht-setup.mjs (KUNDENANSICHT_ATTRIBUTE) ergaenzt featureConsents um kundenart und portalFeatures um
* unlockedBy und einrichtungAbrechnung;
* die Aufträge-Collections kommen aus server/services/auftraege/schema.js (scripts/auftraege-setup.mjs), und ihre
* Zusatzfelder bestehender Collections (AUFTRAEGE_ZUSATZ_ATTRIBUTE, z. B. financeSettings.steuernummer) werden angehängt.
*/
@@ -29,7 +32,7 @@ export function schemaKeys(collection) {
if (Object.hasOwn(SHOP_COLLECTIONS, collection)) return SHOP_COLLECTIONS[collection].attributes.map((a) => a.key)
if (Object.hasOwn(UNTERKUNFT_COLLECTIONS, collection)) return UNTERKUNFT_COLLECTIONS[collection].attributes.map((a) => a.key)
if (Object.hasOwn(ZEITEN_COLLECTIONS, collection)) return ZEITEN_COLLECTIONS[collection].attributes.map((a) => a.key)
if (collection === 'featureConsents') return FEATURE_CONSENT_ATTRIBUTES.map((a) => a.key)
if (collection === 'featureConsents') return [...FEATURE_CONSENT_ATTRIBUTES, ...KUNDENANSICHT_ATTRIBUTE.featureConsents].map((a) => a.key)
for (const script of SETUP_SCRIPTS) {
const src = readFileSync(new URL(`../../scripts/${script}`, import.meta.url), 'utf8')
const start = src.indexOf(`'${collection}',`)
@@ -37,7 +40,8 @@ export function schemaKeys(collection) {
const block = src.slice(start, src.indexOf('\n ],', start))
const keys = [...block.matchAll(/key: '(\w+)'/g)].map((m) => m[1])
if (collection === 'portalFeatures') {
return [...keys, ...PORTAL_FEATURE_ATTRIBUTES.map((a) => a.key), ...ZEITEN_FEATURE_ATTRIBUTES.map((a) => a.key)]
const zusatz = [...PORTAL_FEATURE_ATTRIBUTES, ...ZEITEN_FEATURE_ATTRIBUTES, ...KUNDENANSICHT_ATTRIBUTE.portalFeatures]
return [...new Set([...keys, ...zusatz.map((a) => a.key)])]
}
return Object.hasOwn(AUFTRAEGE_ZUSATZ_ATTRIBUTE, collection)
? [...keys, ...AUFTRAEGE_ZUSATZ_ATTRIBUTE[collection].map((a) => a.key)]