/** * L14 Abrechnungsübersicht — Backfill der offenen Abrechnungseinträge für Bestandsdaten. * * Legt je Mandant fehlende `open`-Einträge an (idempotent, services/billing/candidates.ts): * Aufträge in „Zur Abrechnung freigegeben“, bestätigte Meilensteine ohne Eintrag und freigegebene * Tagesberichte von Aufträgen ohne Meilensteine. Bereits abgerechnete Aufträge (Status billed) bekommen * bewusst KEINEN offenen Eintrag. * * Lauf: npx tsx scripts/billing-backfill.ts (alle Mandanten) * npx tsx scripts/billing-backfill.ts --tenant demo (ein Mandant) */ import "dotenv/config"; import { dbForTenant, prisma } from "../src/server/db"; import type { ServiceCtx } from "../src/server/services/context"; import { syncBillingCandidates } from "../src/server/services/billing/candidates"; async function main() { const idx = process.argv.indexOf("--tenant"); const slug = idx > 0 ? process.argv[idx + 1] : undefined; const tenants = await prisma.tenant.findMany({ where: slug ? { slug } : {}, select: { id: true, slug: true } }); if (slug && tenants.length === 0) throw new Error(`Mandant ${slug} nicht gefunden`); let total = 0; for (const t of tenants) { const ctx: ServiceCtx = { db: dbForTenant(t.id), tenantId: t.id, userId: "system", permissions: new Set() }; const orders = await ctx.db.workOrder.findMany({ where: { deletedAt: null, OR: [ { status: "released_for_billing" }, { milestones: { some: { status: "confirmed", billingRecordId: null, deletedAt: null } } }, { reports: { some: { type: "daily", status: "approved" } } }, ], }, select: { id: true }, }); let created = 0; for (const o of orders) created += (await syncBillingCandidates(ctx, { workOrderId: o.id })).created; total += created; console.log(`✔ ${t.slug}: ${orders.length} Aufträge geprüft, ${created} Einträge angelegt`); } console.log(`\nFertig — ${total} Abrechnungseinträge angelegt.`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); try { const { closeJobQueues } = await import("../src/server/jobs/queues"); await closeJobQueues(); const { closeQueues } = await import("../src/server/mail/queue"); await closeQueues(); } catch { /* queues optional */ } });