Kundenansicht P6: Ticket-Texte zum Stundenkonto je Abrechnung (Abo, Projekt)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df53cd039b
commit
bb1efa5c9e
5 files changed
+80
-7
No files matched your search
@@ -0,0 +1,45 @@
|
||||
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'
|
||||
|
||||
// Ticket-Texte zum Stundenkonto je Abrechnung (P6 Task 14): Dokumente des Abo-Modells (einrichtungAbrechnung abo oder projekt) sagen im
|
||||
// Ticket nirgends "nicht anrechnen"; die Abrechnung steht allein in der Schlusszeile aus createFeatureTicket (ABRECHNUNGS_ZEILEN).
|
||||
const aw = await startAppwriteMock()
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
after(() => Promise.all([aw.close(), st.close()]))
|
||||
const { termineTicketDetails } = await import('../server/services/termine/ticketDetails.js')
|
||||
const { shopTicketDetails } = await import('../server/services/shop/ticketDetails.js')
|
||||
const { unterkunftTicketDetails } = await import('../server/services/unterkunft/ticketDetails.js')
|
||||
const { einbauanleitung } = await import('../server/services/termine/einbauanleitung.js')
|
||||
const { ausVorlage } = await import('../server/services/termine/vorlagen.js')
|
||||
|
||||
const NOW = new Date('2026-10-17T08:00:00.000Z')
|
||||
// Salon-Vorlage als gespeicherte Einstellung: erst damit erzeugt der Generator alle Abschnitte (Checkliste, Abrechnung), ohne Einstellung nur das Snippet
|
||||
const EINSTELLUNG = () => ausVorlage('salon', { name: 'Salon Muster', email: 'inhaber@salon-muster.example', telefon: '0631 123456', adresse: 'Hauptstraße 1, 67655 Kaiserslautern', kleinunternehmer: false, impressumUrl: 'https://salon-muster.example/impressum', datenschutzUrl: 'https://salon-muster.example/datenschutz', bundesland: 'RP' }, NOW)
|
||||
const ALT = /nicht auf (das )?Stundenkontingent|nicht auf Abo-Stunden|angerechnet/i
|
||||
const ctx = (key, doc) => ({ feature: { key }, project: { projectName: 'Bäckerei', subdomain: 'baeckerei' }, customer: { name: 'Kunde' }, doc: { $id: `f_${key}`, siteKey: 'abcdefghijklmnop', customerId: 'c1', projectId: 'p1', featureKey: key, ...doc } })
|
||||
const BAUER = { termine: termineTicketDetails, shop: shopTicketDetails, unterkunft: unterkunftTicketDetails }
|
||||
for (const [key, bau] of Object.entries(BAUER)) {
|
||||
test(`${key}: mit Abrechnung abo und projekt keine „nicht anrechnen“-Aussage im Ticket`, async () => {
|
||||
for (const einrichtungAbrechnung of ['abo', 'projekt']) assert.doesNotMatch(await bau(ctx(key, { einrichtungAbrechnung, unlockedBy: einrichtungAbrechnung })), ALT, `${key} ${einrichtungAbrechnung}`)
|
||||
})
|
||||
test(`${key}: Kauf-Bestand und Altdokumente behalten ihren Wortlaut`, async () => { assert.match(await bau(ctx(key, key === 'termine' ? { unlockedByPurchase: true } : {})), ALT) })
|
||||
}
|
||||
test('Terminplaner-Anleitung: Text und Kurzfassung je Abrechnung; Notfalltext ohne Kontingent-Satz', async (t) => {
|
||||
t.mock.method(console, 'error', () => {})
|
||||
const gen = (feature) => einbauanleitung({ feature: { $id: 'f1', siteKey: 'abcdefghijklmnop', customerId: 'c1', projectId: 'p1', ...feature }, project: { projectName: 'B' }, einstellung: EINSTELLUNG(), fragebogen: null, version: 1, updatedAt: null, kunde: { name: 'K' }, now: NOW })
|
||||
for (const a of ['abo', 'projekt']) { const g = gen({ einrichtungAbrechnung: a, unlockedBy: a }); assert.doesNotMatch(g.text, ALT, a); assert.doesNotMatch(g.kurz, ALT, a) }
|
||||
assert.match(gen({ einrichtungAbrechnung: 'abo', unlockedBy: 'abo' }).text, /Einrichtung aus dem Abo, Zeit im Arbeitsblatt aufs Abo-Stundenkonto anrechnen \(Vorschuss möglich\)\./)
|
||||
assert.match(gen({ einrichtungAbrechnung: 'projekt', unlockedBy: 'projekt' }).text, /im Projekt enthalten, Zeit im Arbeitsblatt, nicht aufs Abo anrechnen\./)
|
||||
assert.match(gen({ unlockedByPurchase: true }).kurz, /Nicht auf das Stundenkontingent anrechnen/)
|
||||
// Ersatztexte der Datenschutz-Regel und Schritt 6
|
||||
assert.match(gen({ einrichtungAbrechnung: 'abo', unlockedBy: 'abo' }).kurz, /das gehört zur Einrichtung und zählt mit zum Stundenkonto/)
|
||||
assert.match(gen({ einrichtungAbrechnung: 'projekt', unlockedBy: 'projekt' }).kurz, /das gehört zur Einrichtung und ist im Projekt enthalten/)
|
||||
assert.match(gen({ einrichtungAbrechnung: 'abo', unlockedBy: 'abo' }).kurz, /6\. Ticket erledigen und die Zeit im Arbeitsblatt buchen\.(\n|$)/)
|
||||
// Notfalltext (Anleitung nicht erzeugbar, hier: leere $id, ladeStand wirft): im Abo-Modell ohne Kontingent-Satz, ohne Marker wie heute
|
||||
assert.doesNotMatch(await termineTicketDetails(ctx('termine', { einrichtungAbrechnung: 'abo', unlockedBy: 'abo', $id: '' })), ALT)
|
||||
assert.match(await termineTicketDetails(ctx('termine', { $id: '' })), /Nicht auf das Stundenkontingent anrechnen\./)
|
||||
})
|
||||
Reference in new issue
Block a user