L14 Abrechnungsübersicht: Tests, Backfill, Smoke und Lane-Bericht
test-abrechnung-service (118 Prüfungen inkl. Mandantentrennung und PDF-Render-Smoke), test-abrechnung-sync (21), Fixture-Zeilen für die neuen Tenant-Modelle, Rollen-Matrix billing:*, smoke-auth um /billing, Detail, Druckansicht, Auftrags-Tab und mobile Meilensteine erweitert, scripts/billing-backfill.ts, docs/craftvia/lanes/abrechnung.md, ABNAHME §3 „Abrechnungsübersicht“ (keine Buchhaltung). Gate 69/69, RLS 69/69. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+31
-2
@@ -39,13 +39,32 @@ async function ids() {
|
||||
const inReview = d16 ? await prisma.report.findFirst({ where: { workOrderId: d16.id } }) : null;
|
||||
const imp = await prisma.importJob.findFirst({ where: { tenantId: demo.id, status: "review_required" }, orderBy: { createdAt: "desc" } });
|
||||
const photo = await prisma.photo.findFirst({ where: { workOrderId: d07.id }, select: { documentId: true } });
|
||||
return { d07, d08, d14, d15, d16, d17, d18, emergency, customer, foreignCustomer, site, approved, inReview, imp, photo };
|
||||
// L14: open billing record (DEMO-18 released for billing → event hook / scripts/billing-backfill.ts) + smoke milestone on DEMO-07
|
||||
const billing = await prisma.billingRecord.findFirst({ where: { tenantId: demo.id, workOrderId: d18.id, status: "open" }, select: { id: true } });
|
||||
const milestone =
|
||||
(await prisma.workOrderMilestone.findFirst({ where: { tenantId: demo.id, workOrderId: d07.id, title: "Smoke-Meilenstein", deletedAt: null }, select: { id: true } })) ??
|
||||
(await prisma.workOrderMilestone.create({ data: { tenantId: demo.id, workOrderId: d07.id, title: "Smoke-Meilenstein", sortOrder: 10 }, select: { id: true } }));
|
||||
return { d07, d08, d14, d15, d16, d17, d18, emergency, customer, foreignCustomer, site, approved, inReview, imp, photo, billing, milestone };
|
||||
}
|
||||
|
||||
function plans(x: Awaited<ReturnType<typeof ids>>): Plan[] {
|
||||
const wo = (o: { id: string } | null, suffix = "") => (o ? `/work-orders/${o.id}${suffix}` : "/work-orders");
|
||||
const m = (o: { id: string } | null, suffix = "") => (o ? `/m/orders/${o.id}${suffix}` : "/m/orders");
|
||||
const tabs = ["overview", "checklist", "material", "times", "photos", "notes", "reports", "documents", "history"];
|
||||
const tabs = ["overview", "checklist", "material", "times", "photos", "notes", "reports", "billing", "documents", "history"];
|
||||
// L14 Abrechnungsübersicht
|
||||
const billingBackoffice: Check[] = [
|
||||
{ path: "/billing", mustContain: ["Abrechnungsübersicht", "Offen"] },
|
||||
{ path: "/billing?status=billed", mustContain: "Abgerechnet" },
|
||||
{ path: "/dashboard", mustContain: "Bereit zur Abrechnung" },
|
||||
{ path: wo(x.d07, "?tab=billing"), mustContain: ["Smoke-Meilenstein", "Abrechnungseinträge"] },
|
||||
...(x.billing
|
||||
? [
|
||||
{ path: `/billing?q=${x.d18.number}`, mustContain: x.d18.number },
|
||||
{ path: `/billing/${x.billing.id}`, mustContain: ["Abrechnungsblatt", x.d18.number, "Anfahrten", "Als abgerechnet markieren"] },
|
||||
{ path: `/billing/print?ids=${x.billing.id}`, mustContain: ["Abrechnungsblatt", x.d18.number] },
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return [
|
||||
{
|
||||
email: "admin@demo.example",
|
||||
@@ -81,6 +100,7 @@ function plans(x: Awaited<ReturnType<typeof ids>>): Plan[] {
|
||||
...tabs.map((t) => ({ path: wo(x.d07, `?tab=${t}`), mustContain: x.d07.number })),
|
||||
{ path: wo(x.d18, "?tab=reports"), mustContain: x.d18.number },
|
||||
{ path: "/work-orders/conflicts" },
|
||||
...billingBackoffice,
|
||||
// L12 Zeiterfassung
|
||||
{ path: "/work-orders/time-approvals", mustContain: "Zeiten zur Freigabe" },
|
||||
{ path: "/dashboard", mustContain: "Zeiten zur Freigabe" },
|
||||
@@ -151,6 +171,11 @@ function plans(x: Awaited<ReturnType<typeof ids>>): Plan[] {
|
||||
{ path: "/m/time/new", mustContain: ["Zeit nachtragen", "Begründung"] },
|
||||
{ path: "/m/approvals", mustContain: "nicht freigeschaltet" },
|
||||
{ path: m(x.d07, "/time"), mustContain: x.d07.number },
|
||||
// L14 Abrechnungsübersicht: Meilensteine mobil, Backoffice-Übersicht ohne Recht
|
||||
{ path: m(x.d07), mustContain: ["Meilensteine", "Smoke-Meilenstein", "Erreicht melden"] },
|
||||
// order numbers appear in the monteur's own notifications and all UI texts in the serialized messages → check the record link
|
||||
{ path: "/billing", expect: [200, 307], mustContain: "nicht freigeschaltet", mustNotContain: x.billing ? [`/billing/${x.billing.id}`] : [] },
|
||||
...(x.billing ? [{ path: `/billing/${x.billing.id}`, expect: [404, 307] }] : []),
|
||||
...(x.photo ? [{ path: `/files/${x.photo.documentId}`, expect: [200] }] : []),
|
||||
{ path: m(x.d08), expect: [404] },
|
||||
{ path: `/customers/${x.foreignCustomer.id}`, expect: [404, 307] },
|
||||
@@ -181,6 +206,10 @@ function plans(x: Awaited<ReturnType<typeof ids>>): Plan[] {
|
||||
...(x.emergency ? [{ path: `/work-orders/emergency-review/${x.emergency.id}`, expect: [404] }] : []),
|
||||
...(x.imp ? [{ path: `/imports/${x.imp.id}`, expect: [404] }] : []),
|
||||
...(x.photo ? [{ path: `/files/${x.photo.documentId}`, expect: [404] }] : []),
|
||||
// L14 Abrechnungsübersicht: keine Einträge von demo
|
||||
{ path: "/billing", mustContain: "Abrechnungsübersicht", mustNotContain: [x.d18.number, "Elbblick"] },
|
||||
...(x.billing ? [{ path: `/billing/${x.billing.id}`, expect: [404] }, { path: `/billing/print?ids=${x.billing.id}`, mustNotContain: [x.d18.number] }] : []),
|
||||
{ path: wo(x.d07, "?tab=billing"), expect: [404] },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user