Kundenansicht P5: Einrichtungs-Ticket mit Abrechnung, unlockedBy für alle Funktionen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b000dba698
commit
1d149f50b1
4 files changed
+126
-12
No files matched your search
@@ -0,0 +1,88 @@
|
||||
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'
|
||||
|
||||
// Einrichtungs-Ticket mit Abrechnung (V7) und unlockedBy in featureView fuer alle Funktionen.
|
||||
// workorders, websiteProjects und customers legt das Setup-Skript nicht an: ohne Schema im Nachbau.
|
||||
const aw = await startAppwriteMock({ schema: { portalFeatures: schemaKeys('portalFeatures') } })
|
||||
const st = await startFakeStripe()
|
||||
after(() => Promise.all([aw.close(), st.close()]))
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { createFeatureTicket, ABRECHNUNGS_ZEILEN } = await import('../server/services/ticketAdmin.js')
|
||||
const { featureView, ticketNachlegen } = await import('../server/services/siteFeatures.js')
|
||||
|
||||
aw.seed('customers', { $id: 'c1', name: 'Kunde Eins', location: 'Berlin', email: 'kunde1@example.test' })
|
||||
|
||||
const ALT = ['createdAt', 'customerId', 'customerLocation', 'customerName', 'details', 'priority', 'serviceType', 'status', 'systemType', 'topic', 'type', 'woid']
|
||||
const base = { customerId: 'c1', project: { $id: 'p1', projectName: 'Bäckerei' }, featureKey: 'termine', siteKey: 'abcdefghijklmnop', topic: 'T', details: 'Zeile 1' }
|
||||
const nutzer = (doc) => Object.keys(doc).filter((k) => !k.startsWith('$')).sort()
|
||||
// aw.docs() liefert eine Kopie des Standes zum Aufruf: das Ticket erst anlegen, dann nachsehen
|
||||
const ticketVon = async (args) => {
|
||||
const { ticketId } = await createFeatureTicket(args)
|
||||
return aw.docs('workorders').get(ticketId)
|
||||
}
|
||||
|
||||
test('abrechnung abo und projekt: Felder und letzte Zeile', async () => {
|
||||
assert.deepEqual(ABRECHNUNGS_ZEILEN, {
|
||||
abo: 'Zeit im Arbeitsblatt erfassen und aufs Abo-Stundenkonto anrechnen (Einrichtung aus dem Abo, Vorschuss möglich).',
|
||||
projekt: 'Zeit im Arbeitsblatt erfassen, nicht aufs Abo anrechnen (im Projekt enthalten).',
|
||||
})
|
||||
const t = await ticketVon({ ...base, abrechnung: 'abo' })
|
||||
assert.deepEqual([t.einrichtungFuer, t.einrichtungAbrechnung], ['termine', 'abo'])
|
||||
assert.equal(t.details, `Zeile 1\n${ABRECHNUNGS_ZEILEN.abo}`)
|
||||
assert.ok((await ticketVon({ ...base, abrechnung: 'projekt' })).details.endsWith('\nZeit im Arbeitsblatt erfassen, nicht aufs Abo anrechnen (im Projekt enthalten).'))
|
||||
})
|
||||
|
||||
test('ohne abrechnung: keine neuen Felder, Details unverändert (alte Ansicht)', async () => {
|
||||
for (const abrechnung of [undefined, '']) {
|
||||
const t = await ticketVon({ ...base, abrechnung })
|
||||
assert.deepEqual(nutzer(t), ALT)
|
||||
assert.equal(t.details, 'Zeile 1')
|
||||
}
|
||||
})
|
||||
|
||||
test('ungültiger Wert: TypeError, kein Ticket', async () => {
|
||||
const n = aw.docs('workorders').size
|
||||
for (const abrechnung of ['frei', 'Abo', null, 5]) await assert.rejects(() => createFeatureTicket({ ...base, abrechnung }), TypeError, String(abrechnung))
|
||||
assert.equal(aw.docs('workorders').size, n)
|
||||
})
|
||||
|
||||
test('lange Details: gekürzt auf 7000, die Schlusszeile bleibt; auch im Notfallweg nach 400', async () => {
|
||||
const lang = Array.from({ length: 400 }, (_, i) => `Zeile ${i} ${'x'.repeat(30)}`).join('\n')
|
||||
const t = await ticketVon({ ...base, details: lang, abrechnung: 'abo' })
|
||||
assert.ok(t.details.length <= 7000 && t.details.endsWith(ABRECHNUNGS_ZEILEN.abo))
|
||||
assert.ok(t.details.length > 6800, 'es wird nicht zu stark gekürzt')
|
||||
aw.failNext({ collection: 'workorders', method: 'POST', status: 400, message: 'zu lang' })
|
||||
const n = await ticketVon({ ...base, details: lang, fallbackDetails: 'Notfall', abrechnung: 'projekt' })
|
||||
assert.ok(n.details.startsWith('Notfall') && n.details.endsWith(ABRECHNUNGS_ZEILEN.projekt))
|
||||
})
|
||||
|
||||
test('featureView: unlockedBy für alle Funktionen, aboEndeAt/einbau nur bei Zeiten', async () => {
|
||||
assert.equal((await featureView({ state: 'setup', unlockedBy: 'abo' }, 'news')).unlockedBy, 'abo')
|
||||
assert.equal((await featureView({ state: 'setup', unlockedBy: 'projekt' }, 'shop')).unlockedBy, 'projekt')
|
||||
assert.equal((await featureView({ state: 'active', unlockedByPurchase: true }, 'termine')).unlockedBy, 'kauf')
|
||||
assert.equal((await featureView(undefined, 'news')).unlockedBy, null)
|
||||
assert.equal((await featureView({ state: 'off', unlockedBy: 'abo' }, 'news')).unlockedBy, null)
|
||||
assert.equal(Object.hasOwn(await featureView({ state: 'setup', unlockedBy: 'abo' }, 'news'), 'einbau'), false)
|
||||
assert.equal(Object.hasOwn(await featureView({ state: 'setup', unlockedBy: 'abo' }, 'news'), 'aboEndeAt'), false)
|
||||
const zeiten = await featureView({ state: 'setup', unlockedBy: 'abo', einbau: 'angestossen' }, 'zeiten')
|
||||
assert.deepEqual([zeiten.unlockedBy, zeiten.einbau, Object.hasOwn(zeiten, 'aboEndeAt')], ['abo', 'angestossen', true])
|
||||
})
|
||||
|
||||
test('ersetztes Ticket trägt die Abrechnung des Dokuments', async () => {
|
||||
aw.seed('websiteProjects', { $id: 'p1', customerId: 'c1', projectName: 'Bäckerei' })
|
||||
aw.seed('portalFeatures', { $id: 'f_p1_news', customerId: 'c1', projectId: 'p1', featureKey: 'news', siteKey: 'abcdefghijklmnop', state: 'setup', enabled: false, unlockedBy: 'abo', einrichtungAbrechnung: 'abo', ticketId: 'weg' })
|
||||
const { doc } = await ticketNachlegen({ customerId: 'c1', projectId: 'p1', key: 'news', by: 'Kenso' })
|
||||
const ticket = aw.docs('workorders').get(doc.ticketId)
|
||||
assert.deepEqual([ticket.einrichtungAbrechnung, ticket.einrichtungFuer], ['abo', 'news'])
|
||||
assert.ok(ticket.details.endsWith(ABRECHNUNGS_ZEILEN.abo))
|
||||
})
|
||||
|
||||
test('ersetztes Ticket ohne Abrechnung am Dokument: wie bisher, keine neuen Felder', async () => {
|
||||
aw.seed('portalFeatures', { $id: 'f_p1_termine', customerId: 'c1', projectId: 'p1', featureKey: 'termine', siteKey: 'bbcdefghijklmnop', state: 'setup', enabled: false, unlockedByPurchase: true, ticketId: 'weg2' })
|
||||
const { doc } = await ticketNachlegen({ customerId: 'c1', projectId: 'p1', key: 'termine', by: 'Kenso' })
|
||||
assert.deepEqual(nutzer(aw.docs('workorders').get(doc.ticketId)), ALT)
|
||||
})
|
||||
@@ -613,9 +613,10 @@ test('Ansicht features.zeiten: unlockedBy, aboEndeAt, einbau; loeschungAm nur be
|
||||
const team = await ansicht({ state: 'disabled', unlockedBy: 'team', disabledReason: 'team', disabledAt: '2026-10-04T23:30:00.000Z' })
|
||||
assert.equal(team.loeschungAm, null)
|
||||
|
||||
// andere Funktionen behalten ihre Ansicht
|
||||
// andere Funktionen behalten ihre Ansicht; einzige Erweiterung (V7, Kundenansicht neu): unlockedBy gilt fuer jede Funktion (null solange sie aus ist)
|
||||
const news = (await liste()).body.projects[0].features.news
|
||||
assert.equal('einbau' in news || 'unlockedBy' in news || 'aboEndeAt' in news, false)
|
||||
assert.equal('einbau' in news || 'aboEndeAt' in news, false)
|
||||
assert.equal(news.unlockedBy, null)
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------- Projektliste
|
||||
|
||||
Reference in new issue
Block a user