// Shared fixture of the lane L16 tests (scripts/test-lotse-chat-*.ts): two zz test tenants with // technicians (assigned / not assigned), admin, tenant B, customers with contact data (for the data // minimisation checks) and work orders in different statuses. Not a test itself. import { prisma, dbForTenant } from "../../src/server/db"; import { ROLE_DEFS, type RoleKey } from "../../src/server/rbac"; import { ServiceError, type ServiceCtx } from "../../src/server/services/context"; export let failures = 0; export const ok = (cond: boolean, msg: string) => { console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`); if (!cond) failures++; }; export async function expectErr(fn: () => Promise, code: ServiceError["code"], msg: string, reason?: string) { try { await fn(); ok(false, `${msg} — kein Fehler (erwartet ${code}${reason ? `/${reason}` : ""})`); } catch (err) { if (!(err instanceof ServiceError)) return ok(false, `${msg} — ${(err as Error).message}`); const r = (err.details as { reason?: string } | undefined)?.reason; ok(err.code === code && (!reason || r === reason), `${msg} (${err.code}${r ? `/${r}` : ""})`); } } export const ctxOf = (tenantId: string, userId: string, role: RoleKey): ServiceCtx => ({ db: dbForTenant(tenantId), tenantId, userId, permissions: new Set(ROLE_DEFS[role].permissions), }); export async function cleanupChatTenants(prefix: string) { const slugs = [`zz-${prefix}-a`, `zz-${prefix}-b`]; const tenants = await prisma.tenant.findMany({ where: { slug: { in: slugs } }, select: { id: true } }); const ids = tenants.map((t) => t.id); if (ids.length) { const w = { where: { tenantId: { in: ids } } }; await prisma.lotseActionProposal.deleteMany(w); await prisma.lotseMessage.deleteMany(w); await prisma.lotseConversation.deleteMany(w); await prisma.notification.deleteMany(w); await prisma.mailLog.deleteMany(w); await prisma.aiGeneration.deleteMany(w); await prisma.signature.deleteMany(w); await prisma.report.deleteMany(w); await prisma.activityNote.deleteMany(w); await prisma.voiceNote.deleteMany(w); await prisma.materialUsage.deleteMany(w); await prisma.materialPlan.deleteMany(w); await prisma.timeEntry.deleteMany(w); await prisma.workSession.deleteMany(w); await prisma.photo.deleteMany(w); await prisma.checklistItem.deleteMany(w); await prisma.photoRequirement.deleteMany(w); await prisma.workOrderStatusChange.deleteMany(w); await prisma.workOrderAssignee.deleteMany(w); await prisma.syncOperation.deleteMany(w); await prisma.document.deleteMany(w); await prisma.workOrder.deleteMany(w); await prisma.site.deleteMany(w); await prisma.contact.deleteMany(w); await prisma.customer.deleteMany(w); await prisma.numberSequence.deleteMany(w); await prisma.auditLog.deleteMany(w); await prisma.tenantModule.deleteMany(w); await prisma.tenantSettings.deleteMany(w); await prisma.user.deleteMany(w); await prisma.tenant.deleteMany({ where: { id: { in: ids } } }); } await prisma.identity.deleteMany({ where: { email: { endsWith: `@zz-${prefix}.test` } } }); } export async function createChatFixture(prefix: string) { await cleanupChatTenants(prefix); const domain = `@zz-${prefix}.test`; const tA = await prisma.tenant.create({ data: { name: `Lotse-Chat ${prefix} A`, slug: `zz-${prefix}-a` } }); const tB = await prisma.tenant.create({ data: { name: `Lotse-Chat ${prefix} B`, slug: `zz-${prefix}-b` } }); await prisma.tenantSettings.create({ data: { tenantId: tA.id, orgName: "Chat Test A GmbH", phone: "040 1234567", email: "buero@lotsechat-a.test", address: "Werftstraße 5, 20457 Hamburg" } }); await prisma.tenantSettings.create({ data: { tenantId: tB.id, orgName: "Chat Test B" } }); const mkUser = async (tenantId: string, key: string, name: string) => { const identity = await prisma.identity.create({ data: { email: `${key}${domain}`, passwordHash: "x" } }); return prisma.user.create({ data: { tenantId, identityId: identity.id, email: identity.email, name } }); }; const tech = await mkUser(tA.id, "tech", "Max Monteur"); const tech2 = await mkUser(tA.id, "tech2", "Nora Nordmann"); const outsider = await mkUser(tA.id, "outsider", "Otto Fremd"); const admin = await mkUser(tA.id, "admin", "Anna Admin"); const techB = await mkUser(tB.id, "techb", "Tom Bader"); const now = Date.now(); const today = { plannedStart: new Date(now - 3_600_000), plannedEnd: new Date(now + 3 * 3_600_000) }; const tomorrow = { plannedStart: new Date(now + 26 * 3_600_000), plannedEnd: new Date(now + 30 * 3_600_000) }; const customer = await prisma.customer.create({ data: { tenantId: tA.id, firstName: "Erika", lastName: "Mustermann", phone: "0171 9876543", email: "erika@kunde.test", street: "Lindenallee", houseNumber: "7", postalCode: "22111", city: "Hamburg" }, }); const contact = await prisma.contact.create({ data: { tenantId: tA.id, customerId: customer.id, name: "Klaus Kontakt", phone: "040 555666", email: "klaus@kunde.test" } }); const site = await prisma.site.create({ data: { tenantId: tA.id, customerId: customer.id, name: "Haus Mustermann", street: "Lindenallee", houseNumber: "7", postalCode: "22111", city: "Hamburg", accessNotes: "Schlüsselkasten Code 4711, Hausmeister 0170 1112223" }, }); const company = await prisma.customer.create({ data: { tenantId: tA.id, companyName: "Müller Haustechnik GmbH", city: "Buxtehude" } }); const site2 = await prisma.site.create({ data: { tenantId: tA.id, customerId: company.id, name: "Objekt Müller", city: "Buxtehude" } }); const order = async (number: string, data: Record, assignees: string[]) => { const wo = await prisma.workOrder.create({ data: { tenantId: tA.id, number, ...(data as object) } as never }); for (const userId of assignees) await prisma.workOrderAssignee.create({ data: { tenantId: tA.id, workOrderId: wo.id, userId } }); return wo as unknown as { id: string; number: string; status: string }; }; const wo1 = await order( "A-LC-1", { customerId: customer.id, siteId: site.id, contactId: contact.id, title: "Speicher tauschen", description: "Kundin unter 0171 9876543 oder erika@kunde.test erreichbar, Lindenallee 7.", status: "in_progress", signatureRequired: false, ...today }, [tech.id, tech2.id], ); const plan = await prisma.materialPlan.create({ data: { tenantId: tA.id, workOrderId: wo1.id, name: "Filter", plannedQuantity: 1, unit: "Stk" } }); const wo2 = await order("A-LC-2", { customerId: company.id, siteId: site2.id, title: "Wartung Heizung", status: "assigned", signatureRequired: false, ...today }, [tech.id, tech2.id]); const wo3 = await order("A-LC-3", { customerId: company.id, siteId: site2.id, title: "Pumpe montieren", status: "in_progress", signatureRequired: false, ...tomorrow }, [tech.id]); await prisma.photoRequirement.create({ data: { tenantId: tA.id, workOrderId: wo3.id, key: "fertige_montage", label: "Fertige Montage" } }); const wo4 = await order("A-LC-4", { customerId: company.id, siteId: site2.id, title: "Regler einstellen", status: "in_progress", signatureRequired: false, ...tomorrow }, [tech.id]); const customerB = await prisma.customer.create({ data: { tenantId: tB.id, companyName: "Kunde B" } }); const woB = await prisma.workOrder.create({ data: { tenantId: tB.id, number: "B-LC-1", customerId: customerB.id, title: "Auftrag B", status: "in_progress", ...today } }); await prisma.workOrderAssignee.create({ data: { tenantId: tB.id, workOrderId: woB.id, userId: techB.id } }); return { tA, tB, users: { tech, tech2, outsider, admin, techB }, ctx: { tech: ctxOf(tA.id, tech.id, "technician"), tech2: ctxOf(tA.id, tech2.id, "technician"), outsider: ctxOf(tA.id, outsider.id, "technician"), admin: ctxOf(tA.id, admin.id, "tenant-admin"), techB: ctxOf(tB.id, techB.id, "technician"), }, customer, contact, site, plan, orders: { wo1, wo2, wo3, wo4, woB }, }; }