Files
craftvia/src/server/events.ts
T
msolarczekandClaude Opus 5 ac2d857e26 L14 Abrechnungsübersicht: Services für Einträge, Aufstellung, Abrechnen/Stornieren, Meilensteine und PDF
syncBillingCandidates (idempotent, Event-Hook für work_order.released_for_billing und report.approved), Aufstellung ohne Preise (nur freigegebene Zeiten, Fahrzeit getrennt, Pausen informativ, Anfahrten-Zählung, Material), markBilled mit eingefrorenem Snapshot und Positionszuordnung, voidBilling mit Grund und neuem offenen Eintrag (billed → released_for_billing nur über applyTransition-Option billingVoid), Meilenstein-Flow mit Events, Job billing-pdf (Dokument other/backoffice_only), Sync-Op milestone.reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 10:35:38 +02:00

26 lines
1.2 KiB
TypeScript

import type { DomainEvent } from "@/lib/events";
import type { ServiceCtx } from "@/server/services/context";
/**
* Emit a domain event AFTER the mutation succeeded. Never throws: a failing notification
* must not roll back business data — failures are logged and surfaced via sync/notification
* monitoring. Lanes call ONLY this function; the notifications lane owns the handler.
*/
export async function emitEvent(ctx: ServiceCtx, event: DomainEvent): Promise<void> {
try {
const { handleEvent } = await import("@/server/services/notifications/handle-event");
await handleEvent(ctx, event);
} catch (err) {
console.error(`[events] ${event.type} for ${event.entityType}:${event.entityId} failed:`, (err as Error).message);
}
// L14 Abrechnungsübersicht: billing candidates after billing release / daily report approval (idempotent, never throws)
if (event.type === "work_order.released_for_billing" || event.type === "report.approved") {
try {
const { onBillingEvent } = await import("@/server/services/billing/events");
await onBillingEvent(ctx, event);
} catch (err) {
console.error(`[events] billing hook for ${event.type} ${event.entityId} failed:`, (err as Error).message);
}
}
}