L10b Betrieb & Aufräumen: Sync – Berichts-Ops, Konflikt-Übernahme, eigene Session im Bundle

- Aufräumpunkt j: report.save_draft und report.submit mit Zod-Schemas (lib/sync/ops.ts) und
  Registry-Einträgen → services/reports/sync-ops.ts. report.submit reicht baseVersion als
  expectedWorkOrderVersion und aiReviewed an submitReport durch; Lotse-Entwürfe ohne Bestätigung →
  rejected invalid. signature.capture bleibt unregistriert (Upload-Art für Unterschriftsbild fehlt).
- Aufräumpunkt b: „Übernehmen" in der Konfliktliste delegiert an den Sync-Dispatcher
  (apply.ts#reapplyOperation, ohne baseVersion) statt des L2-Stubs; unterstützt
  work_order.transition und report.submit. Hinweistext der Konfliktliste angepasst.
- Aufräumpunkt c: getFieldBundle liefert je Auftrag mySession (eigene aktive WorkSession); die
  Offline-Ansicht leitet den Zeitstatus daraus ab (alte Bundles: Näherung über Auftragsstatus).
- scripts/test-betrieb-sync.ts (Bundle, clientId je Mandant, Berichts-Ops, Konflikt-Übernahme,
  Mandant B, Monteur ohne Zuweisung); test-einsatz-sync.ts prüft „nicht verfügbare Op" jetzt mit
  signature.capture, weil report.save_draft registriert ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 18:19:19 +02:00
co-authored by Claude Opus 5
parent 5f08df324f
commit 85bae832d0
12 changed files with 316 additions and 40 deletions
+22 -3
View File
@@ -2,6 +2,7 @@ import { z } from "zod";
import type { SyncOpType } from "./envelope";
import { WORK_ORDER_STATUSES } from "@/lib/work-orders/status";
import { emergencyCreatePayload } from "@/lib/emergency/schemas";
import { reportTextsSchema } from "@/lib/reports/content";
/**
* Payload schemas per sync opType (ARCHITEKTUR §4.6). Client-safe: used by the mobile UI to
@@ -113,7 +114,25 @@ export const voiceAttachPayload = z.object({
kind: z.enum(NOTE_KINDS).optional(),
});
/** Schemas of ops owned by other lanes are validated there (reports: L5, emergency: L8). */
/**
* report.save_draft / report.submit (L10b, registered in services/sync/external-ops.ts →
* services/reports/sync-ops.ts). `workOrderId` is required: the sync pipeline checks scope and the
* conflict version (`baseVersion` = WorkOrder.version seen by the device) on that order.
*/
export const reportSaveDraftPayload = z.object({
workOrderId: id,
reportId: id,
texts: reportTextsSchema.partial(),
});
export const reportSubmitPayload = z.object({
workOrderId: id,
reportId: id,
/** L9 Freigabeprinzip: "Ich habe den Vorschlag vom Lotsen geprüft" — mandatory for Lotse drafts */
aiReviewed: z.boolean().optional(),
});
/** Schemas of ops owned by other lanes are validated there (signature.capture: not offline-capable yet). */
const passthrough = z.record(z.string(), z.unknown());
export const OP_PAYLOAD_SCHEMAS = {
@@ -127,8 +146,8 @@ export const OP_PAYLOAD_SCHEMAS = {
"material.upsert": materialUpsertPayload,
"photo.attach": photoAttachPayload,
"voice.attach": voiceAttachPayload,
"report.save_draft": passthrough,
"report.submit": passthrough,
"report.save_draft": reportSaveDraftPayload,
"report.submit": reportSubmitPayload,
"signature.capture": passthrough,
"emergency.create": emergencyCreatePayload,
} satisfies Record<SyncOpType, z.ZodType>;