@happyvertical/smrt-timesheets 0.55.23 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +156 -11
  3. package/agents/period-rollup.md +20 -0
  4. package/dist/attendance/context.d.ts +80 -0
  5. package/dist/attendance/context.d.ts.map +1 -0
  6. package/dist/attendance/correction-models.d.ts +39 -0
  7. package/dist/attendance/correction-models.d.ts.map +1 -0
  8. package/dist/attendance/correction-service.d.ts +118 -0
  9. package/dist/attendance/correction-service.d.ts.map +1 -0
  10. package/dist/attendance/models.d.ts +57 -0
  11. package/dist/attendance/models.d.ts.map +1 -1
  12. package/dist/attendance/segments.d.ts +20 -0
  13. package/dist/attendance/segments.d.ts.map +1 -0
  14. package/dist/attendance/service.d.ts +106 -22
  15. package/dist/attendance/service.d.ts.map +1 -1
  16. package/dist/attendance/write.d.ts +12 -0
  17. package/dist/attendance/write.d.ts.map +1 -0
  18. package/dist/attendance.d.ts +2 -0
  19. package/dist/attendance.d.ts.map +1 -1
  20. package/dist/attendance.js +3 -323
  21. package/dist/chunks/{attendance-BVeStEBv.js → attendance-B1jCbqSb.js} +3 -3
  22. package/dist/chunks/{attendance-BVeStEBv.js.map → attendance-B1jCbqSb.js.map} +1 -1
  23. package/dist/chunks/attendance-DGVy-iHI.js +1246 -0
  24. package/dist/chunks/attendance-DGVy-iHI.js.map +1 -0
  25. package/dist/chunks/models-0-YD5F_o.js +372 -0
  26. package/dist/chunks/models-0-YD5F_o.js.map +1 -0
  27. package/dist/chunks/{rollup-U1IO_CMA.js → rollup-CDqzCxI2.js} +211 -6
  28. package/dist/chunks/rollup-CDqzCxI2.js.map +1 -0
  29. package/dist/chunks/service-time-entry-t7P-bkOQ.js +227 -0
  30. package/dist/chunks/service-time-entry-t7P-bkOQ.js.map +1 -0
  31. package/dist/index.js +5 -5
  32. package/dist/manifest.json +2965 -1358
  33. package/dist/rollup/correction-guard.d.ts +12 -0
  34. package/dist/rollup/correction-guard.d.ts.map +1 -0
  35. package/dist/rollup/models.d.ts +31 -1
  36. package/dist/rollup/models.d.ts.map +1 -1
  37. package/dist/rollup/service.d.ts +25 -7
  38. package/dist/rollup/service.d.ts.map +1 -1
  39. package/dist/rollup/types.d.ts +18 -0
  40. package/dist/rollup/types.d.ts.map +1 -1
  41. package/dist/rollup/write.d.ts +2 -0
  42. package/dist/rollup/write.d.ts.map +1 -1
  43. package/dist/rollup.d.ts +3 -2
  44. package/dist/rollup.d.ts.map +1 -1
  45. package/dist/rollup.js +2 -2
  46. package/dist/smrt-knowledge.json +700 -34
  47. package/package.json +7 -7
  48. package/dist/attendance.js.map +0 -1
  49. package/dist/chunks/models-zU8Um7c2.js +0 -176
  50. package/dist/chunks/models-zU8Um7c2.js.map +0 -1
  51. package/dist/chunks/rollup-U1IO_CMA.js.map +0 -1
  52. package/dist/chunks/service-time-entry-CBv-FF-W.js +0 -227
  53. package/dist/chunks/service-time-entry-CBv-FF-W.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attendance-DGVy-iHI.js","names":["unpaidMilliseconds","outcome"],"sources":["../../src/attendance/correction-models.ts","../../src/attendance/context.ts","../../src/attendance/segments.ts","../../src/attendance/service.ts","../../src/attendance/correction-service.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { parseTimesheetJson } from '../models/json.js';\nimport { AttendancePunch } from './models.js';\nimport { isCorrectionWrite } from './write.js';\n\n/** What a correction did. Punch kinds target the punch itself. */\nexport const ATTENDANCE_CORRECTION_KINDS = [\n 'punch.corrected',\n 'punch.added',\n 'break.added',\n 'break.changed',\n 'break.removed',\n 'segment.added',\n 'segment.changed',\n 'segment.removed',\n] as const;\nexport type AttendanceCorrectionKind =\n (typeof ATTENDANCE_CORRECTION_KINDS)[number];\n\nconst IMMUTABLE_COLUMNS = [\n 'tenant_id',\n 'subject_profile_id',\n 'actor_profile_id',\n 'kind',\n 'punch_id',\n 'target_id',\n 'before_state',\n 'after_state',\n 'reason',\n] as const;\n\n/**\n * One append-only change to closed attendance: who changed whose time, why, and\n * the before/after values as JSON (`null` for an addition's before or a\n * removal's after). Written only by `AttendanceCorrectionService`, in the same\n * transaction as the change. After insert only the acknowledgement may be set,\n * once, by the subject; rows are never deleted.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_corrections',\n sensitive: true,\n indexes: [\n {\n name: 'attendance_corrections_punch_idx',\n columns: ['tenantId', 'punchId'],\n },\n {\n name: 'attendance_corrections_subject_idx',\n columns: ['tenantId', 'subjectProfileId'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class AttendanceCorrection extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n /** Whose time was corrected. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n subjectProfileId = '';\n /** Who made the correction. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n actorProfileId = '';\n @field({ type: 'text' }) kind: AttendanceCorrectionKind = 'punch.corrected';\n @foreignKey(AttendancePunch) punchId = '';\n /** The break or segment id for those kinds; the punch id for punch kinds. */\n targetId = '';\n @field({ type: 'text' }) beforeState = 'null';\n @field({ type: 'text' }) afterState = 'null';\n @field({ type: 'text' }) reason = '';\n acknowledgementRequired = false;\n acknowledgedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { nullable: true })\n acknowledgedByProfileId: string | null = null;\n\n /** The values before the change, or null for an addition. */\n getBefore<T = Record<string, unknown>>(): T | null {\n return parseTimesheetJson<T | null>(this.beforeState, null);\n }\n /** The values after the change, or null for a removal. */\n getAfter<T = Record<string, unknown>>(): T | null {\n return parseTimesheetJson<T | null>(this.afterState, null);\n }\n\n override async save(): Promise<this> {\n // Insert and acknowledgement are both service-only: a generic save() or\n // collection create() must not fabricate or countersign ledger history.\n if (!isCorrectionWrite(this))\n throw new Error(\n 'Attendance corrections are writable only through AttendanceCorrectionService.',\n );\n if (\n !this.tenantId ||\n !this.subjectProfileId ||\n !this.actorProfileId ||\n !this.punchId ||\n !this.targetId ||\n !ATTENDANCE_CORRECTION_KINDS.includes(this.kind) ||\n typeof this.reason !== 'string' ||\n !this.reason.trim() ||\n typeof this.acknowledgementRequired !== 'boolean'\n )\n throw new Error('Invalid attendance correction.');\n for (const json of [this.beforeState, this.afterState]) JSON.parse(json);\n if (!!this.acknowledgedAt !== !!this.acknowledgedByProfileId)\n throw new Error('Acknowledgement needs both a time and a profile.');\n if (this.acknowledgedAt) {\n if (!this.acknowledgementRequired)\n throw new Error('This correction does not require acknowledgement.');\n if (this.acknowledgedByProfileId !== this.subjectProfileId)\n throw new Error('Only the subject can acknowledge a correction.');\n }\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior) {\n const row = this as unknown as Record<string, unknown>;\n const camel = (column: string) =>\n column.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n if (\n IMMUTABLE_COLUMNS.some(\n (column) => prior[column] !== row[camel(column)],\n ) ||\n Boolean(prior.acknowledgement_required) !==\n this.acknowledgementRequired ||\n (prior.acknowledged_at &&\n (new Date(prior.acknowledged_at as string).getTime() !==\n this.acknowledgedAt?.getTime() ||\n prior.acknowledged_by_profile_id !== this.acknowledgedByProfileId))\n )\n throw new Error(\n 'Attendance corrections are append-only; only a first acknowledgement may change.',\n );\n }\n this.id ??= crypto.randomUUID();\n return super.save();\n }\n\n override async delete(): Promise<void> {\n throw new Error('Attendance correction history cannot be deleted.');\n }\n}\n\n/** The correction ledger. */\nexport class AttendanceCorrectionCollection extends SmrtCollection<AttendanceCorrection> {\n static readonly _itemClass = AttendanceCorrection;\n}\n","import { isPostgresDatabase } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type {\n AttendanceBreakCollection,\n AttendancePunchCollection,\n AttendanceReplayCollection,\n AttendanceSegmentCollection,\n} from './models.js';\n\n/** Resolve these identifiers from the authenticated session, never from tap input. */\nexport interface AttendanceActor {\n tenantId: string;\n profileId: string;\n}\n/**\n * Explicit work attribution for a closed punch's draft evidence entry or for a\n * running work segment. `taskRefType`/`taskRefId` (both or neither) name an\n * optional second reference such as an operation; `kind` is an optional short\n * consumer lane label.\n */\nexport interface AttendanceWork {\n workRefType: string;\n workRefId: string;\n description: string;\n taskRefType?: string;\n taskRefId?: string;\n kind?: string;\n}\n\n/**\n * Where a punch was captured: a consumer-owned type (for example a class name\n * such as a station or tablet) and that device's id. Both or neither.\n */\nexport interface AttendanceDeviceRef {\n type: string;\n id: string;\n}\n\n/** Optional provenance recorded when a punch opens. */\nexport interface AttendancePunchOptions {\n /** Short consumer-owned label such as `device`, `web`, `import`, `correction`. */\n source?: string;\n /** The device that captured the punch. */\n device?: AttendanceDeviceRef;\n}\n\n/** Optional detail recorded when a break starts. */\nexport interface AttendanceBreakOptions {\n /** Short consumer-owned label such as `meal` or `rest`. */\n kind?: string;\n}\n\nconst MAX_LABEL = 64;\n\n/** Validate an optional short label (`source`, `kind`, device type); returns the stored form. */\nexport function attendanceLabel(value: unknown, name: string): string {\n if (value === undefined || value === null) return '';\n if (\n typeof value !== 'string' ||\n value !== value.trim() ||\n value.length > MAX_LABEL\n )\n throw new AttendanceConflict(\n `${name} must be a trimmed string of at most ${MAX_LABEL} characters.`,\n 'invalid',\n );\n return value;\n}\n\n/** Validate punch provenance; returns the stored column values. */\nexport function attendanceProvenance(options?: AttendancePunchOptions): {\n source: string;\n deviceRefType: string;\n deviceRefId: string;\n} {\n const source = attendanceLabel(options?.source, 'source');\n const device = options?.device;\n if (device !== undefined && (!device || typeof device !== 'object'))\n throw new AttendanceConflict('device must be { type, id }.', 'invalid');\n const deviceRefType = attendanceLabel(device?.type, 'device.type');\n const deviceRefId = attendanceLabel(device?.id, 'device.id');\n if (!!deviceRefType !== !!deviceRefId)\n throw new AttendanceConflict(\n 'device.type and device.id must be provided together.',\n 'invalid',\n );\n return { source, deviceRefType, deviceRefId };\n}\n\n/** Expected state conflict, distinct from retryable database failures. */\nexport class AttendanceConflict extends Error {\n /**\n * Stable machine-readable reason (see {@link AttendanceRefusalCode}); stored\n * on a refused replay outcome so a device screen can say why.\n */\n readonly code: AttendanceRefusalCode;\n constructor(message: string, code: AttendanceRefusalCode = 'conflict') {\n super(message);\n this.code = code;\n }\n}\n\n/**\n * Built-in refusal codes. Consumers may store any other string through the\n * `refusalCode` replay hook.\n */\nexport type AttendanceRefusalCode =\n | 'conflict'\n | 'invalid'\n | 'predates_committed'\n | 'punch_open'\n | 'no_open_punch'\n | 'break_open'\n | 'no_open_break'\n | 'during_break'\n | 'work_open'\n | 'no_open_work'\n | 'no_resumable_work'\n | (string & {});\n\n/**\n * Collections bound to one transaction executor. Handed to\n * `AttendanceService.withTransaction` callbacks and to the segment helpers so\n * corrections and replay can compose several mutations atomically.\n */\nexport type AttendanceContext = {\n db: DatabaseInterface;\n punches: AttendancePunchCollection;\n breaks: AttendanceBreakCollection;\n segments: AttendanceSegmentCollection;\n replays: AttendanceReplayCollection;\n};\n\n/** Validate a work reference; throws {@link AttendanceConflict}. */\nexport function validateAttendanceWork(work: AttendanceWork): void {\n if (\n !work ||\n typeof work.workRefType !== 'string' ||\n !work.workRefType ||\n typeof work.workRefId !== 'string' ||\n !work.workRefId ||\n typeof work.description !== 'string'\n )\n throw new AttendanceConflict(\n 'A complete work reference and description are required.',\n );\n for (const key of ['taskRefType', 'taskRefId', 'kind'] as const)\n if (work[key] !== undefined && typeof work[key] !== 'string')\n throw new AttendanceConflict(`${key} must be a string.`);\n if (!!work.taskRefType !== !!work.taskRefId)\n throw new AttendanceConflict(\n 'taskRefType and taskRefId must be provided together.',\n );\n}\n\n/** Canonicalize a tenant/profile id: PostgreSQL requires UUIDs, other adapters take it verbatim. */\nexport function attendanceIdentity(\n db: DatabaseInterface,\n value: string,\n): string {\n if (!isPostgresDatabase(db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new Error('PostgreSQL attendance identities must be UUIDs.');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import {\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceWork,\n validateAttendanceWork,\n} from './context.js';\nimport type {\n AttendancePunch,\n AttendanceSegment,\n AttendanceSegmentCloseReason,\n} from './models.js';\n\n/*\n * Segment transitions on an already-open punch, run on the caller's transaction\n * context. `AttendanceService` composes them; corrections and replay call them\n * inside `AttendanceService.withTransaction`. Callers own the actor lock and\n * punch lookup; these helpers own break and one-open-segment rules.\n */\n\n/** The open segment of a punch, or null. */\nexport async function openSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n): Promise<AttendanceSegment | null> {\n const [segment] = await tx.segments.list({\n where: { tenantId: punch.tenantId, punchId: punch.id, endedAt: null },\n limit: 1,\n });\n return segment ?? null;\n}\n\n/** All segments of a punch, oldest first. */\nexport async function listSegments(\n tx: AttendanceContext,\n punch: AttendancePunch,\n): Promise<AttendanceSegment[]> {\n return tx.segments.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n orderBy: 'started_at ASC',\n });\n}\n\n/** Close the punch's open segment, if any. Returns it, or null when none was open. */\nexport async function closeOpenSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n reason: Exclude<AttendanceSegmentCloseReason, ''>,\n): Promise<AttendanceSegment | null> {\n const segment = await openSegment(tx, punch);\n if (!segment) return null;\n if (at < segment.startedAt)\n throw new AttendanceConflict('Segment cannot end before it starts.');\n segment.endedAt = at;\n segment.closedReason = reason;\n await segment.save();\n return segment;\n}\n\n/** Open a segment; refuses while a break or another segment is open. */\nexport async function beginSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n work: AttendanceWork,\n): Promise<AttendanceSegment> {\n validateAttendanceWork(work);\n if (punch.endedAt) throw new AttendanceConflict('Punch is closed.');\n const breaks = await tx.breaks.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n });\n if (breaks.some((b) => !b.endedAt))\n throw new AttendanceConflict(\n 'Cannot work during an open break.',\n 'during_break',\n );\n if (await openSegment(tx, punch))\n throw new AttendanceConflict(\n 'A work segment is already open.',\n 'work_open',\n );\n return tx.segments.create({\n tenantId: punch.tenantId,\n punchId: punch.id ?? undefined,\n workRefType: work.workRefType,\n workRefId: work.workRefId,\n taskRefType: work.taskRefType ?? '',\n taskRefId: work.taskRefId ?? '',\n kind: work.kind ?? '',\n description: work.description,\n startedAt: at,\n _insertOnly: true,\n });\n}\n\n/** Atomically close the open segment and open the next at the same instant. */\nexport async function replaceSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n work: AttendanceWork,\n): Promise<AttendanceSegment> {\n validateAttendanceWork(work);\n if (!(await closeOpenSegment(tx, punch, at, 'switch')))\n throw new AttendanceConflict(\n 'No open work segment to switch.',\n 'no_open_work',\n );\n return beginSegment(tx, punch, at, work);\n}\n\n/** Close the open segment (`stop`); refuses when none is open. */\nexport async function endSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n): Promise<AttendanceSegment> {\n const segment = await closeOpenSegment(tx, punch, at, 'stop');\n if (!segment)\n throw new AttendanceConflict('No open work segment.', 'no_open_work');\n return segment;\n}\n\n/**\n * Reopen the work a break interrupted: a new segment with the same references\n * as the latest segment, which must have been closed by `startBreak`.\n */\nexport async function continueSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n): Promise<AttendanceSegment> {\n const all = await listSegments(tx, punch);\n const last = all[all.length - 1];\n // Only the latest break may be the one that interrupted the work: a later,\n // idle break (taken after the work was left unresumed) must not reopen it.\n const breaks = await tx.breaks.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n orderBy: 'started_at DESC',\n limit: 1,\n });\n if (\n last?.closedReason !== 'break' ||\n !last.endedAt ||\n last.endedAt.getTime() !== breaks[0]?.startedAt.getTime()\n )\n throw new AttendanceConflict(\n 'No work interrupted by a break to resume.',\n 'no_resumable_work',\n );\n return beginSegment(tx, punch, at, last);\n}\n","import {\n isEmbeddedDatabase,\n isPostgresDatabase,\n withEmbeddedWriteTransaction,\n} from '@happyvertical/smrt-core';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { parseTimesheetJson } from '../models/json.js';\nimport { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport {\n type AttendanceActor,\n type AttendanceBreakOptions,\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceDeviceRef,\n type AttendancePunchOptions,\n type AttendanceRefusalCode,\n type AttendanceWork,\n attendanceIdentity,\n attendanceLabel,\n attendanceProvenance,\n validateAttendanceWork,\n} from './context.js';\nimport {\n AttendanceBreakCollection,\n AttendancePunch,\n AttendancePunchCollection,\n AttendanceReplayCollection,\n type AttendanceReplayOutcome,\n AttendanceSegment,\n AttendanceSegmentCollection,\n} from './models.js';\nimport {\n beginSegment,\n closeOpenSegment,\n continueSegment,\n endSegment,\n listSegments,\n openSegment,\n replaceSegment,\n} from './segments.js';\nimport { persistAutoClose } from './write.js';\n\n/** Whether stored evidence metadata carries this work's task reference and kind. */\nfunction sameAttribution(metadata: string, work: AttendanceWork): boolean {\n const stored = parseTimesheetJson<Record<string, unknown>>(metadata, {});\n return (\n (stored.taskRefType ?? '') === (work.taskRefType ?? '') &&\n (stored.taskRefId ?? '') === (work.taskRefId ?? '') &&\n (stored.kind ?? '') === (work.kind ?? '')\n );\n}\n\nexport {\n type AttendanceActor,\n type AttendanceBreakOptions,\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceDeviceRef,\n type AttendancePunchOptions,\n type AttendanceRefusalCode,\n type AttendanceWork,\n validateAttendanceWork,\n} from './context.js';\nexport type { AttendanceReplayOutcome } from './models.js';\nexport * from './segments.js';\n\n/** Offline command with a durable client-generated identifier and device timestamp. */\nexport interface AttendanceTap {\n clientId: string;\n at: string;\n action: AttendanceTapAction;\n paid?: boolean;\n /** Required by `startWork` and `switchWork`; also links evidence on `punchOut`. */\n work?: AttendanceWork;\n /** Recorded on the punch by `punchIn`; hashed with the tap. */\n source?: string;\n /** Recorded on the punch by `punchIn`; hashed with the tap. */\n device?: AttendanceDeviceRef;\n /** Recorded on the break by `startBreak`; hashed with the tap. */\n kind?: string;\n}\n\n/** Punch and break actions plus the work-segment actions (`stopWork` is \"pause\"). */\nexport type AttendanceTapAction =\n | 'punchIn'\n | 'punchOut'\n | 'startBreak'\n | 'endBreak'\n | 'startWork'\n | 'switchWork'\n | 'stopWork'\n | 'resumeWork';\n\nconst TAP_ACTIONS: readonly string[] = [\n 'punchIn',\n 'punchOut',\n 'startBreak',\n 'endBreak',\n 'startWork',\n 'switchWork',\n 'stopWork',\n 'resumeWork',\n];\n\n/** What a time policy sees for one tap that has not been replayed yet. */\nexport interface AttendanceTimeInput {\n /** The tap exactly as sent. */\n tap: AttendanceTap;\n /** The tap's device time. */\n deviceAt: Date;\n /** When the server received the batch. */\n serverNow: Date;\n /** `serverNow - deviceAt` in milliseconds: how long the tap waited. */\n waitedMs: number;\n /** The trusted actor the tap replays for. */\n actor: AttendanceActor;\n}\n\n/** The effective time a policy chose, and whether a person should look at it. */\nexport interface AttendanceResolvedTime {\n at: Date;\n /** Flags the resulting work segment (punches are always flagged offline). */\n reviewRequired?: boolean;\n}\n\n/** Why a consumer refuses or rewrites the stored reason of a refused tap. */\nexport interface AttendanceRefusal {\n code: AttendanceRefusalCode;\n /** Replaces the default message when given. */\n message?: string;\n}\n\n/** A resolved auto-close deadline and, optionally, why the punch closed then. */\nexport interface AttendanceAutoCloseDecision {\n deadline: Date;\n /** Recorded as `autoCloseReason` (1 to 200 characters); defaults to `deadline`. */\n reason?: string;\n}\n\n/**\n * Fixed maximum open seconds, or a per-punch resolver returning the deadline\n * (a Date or a decision) or null to leave the punch open.\n */\nexport type AttendanceAutoCloseDeadline =\n | number\n | ((\n punch: AttendancePunch,\n ) =>\n | Date\n | AttendanceAutoCloseDecision\n | null\n | undefined\n | Promise<Date | AttendanceAutoCloseDecision | null | undefined>);\n\nexport interface AttendanceReplayOptions {\n /**\n * Choose each tap's effective time (for example the device time inside a\n * window, else the server arrival time). The receipt still hashes the tap as\n * sent, so retries with the same clientId match; the resolved time is what is\n * stored. Called once per tap that has no receipt yet, inside its transaction.\n */\n resolveAt?: (\n input: AttendanceTimeInput,\n ) => AttendanceResolvedTime | Promise<AttendanceResolvedTime>;\n /** Clock for `serverNow`; defaults to `new Date()` per tap. */\n now?: () => Date;\n /**\n * Name the stored code (and optionally message) of a refused tap. Without it,\n * the refusal's own `AttendanceConflict.code` is stored.\n */\n refusal?: (\n tap: AttendanceTap,\n error: AttendanceConflict,\n ) => AttendanceRefusal | undefined | void;\n}\n\ntype Context = AttendanceContext;\n\n/**\n * Attendance mutations bound to a trusted tenant/profile. Applications authorize\n * the actor before constructing this service. Each transition is atomic; all\n * participating collections use the transaction executor. PostgreSQL serializes\n * this actor with an advisory lock; embedded adapters serialize writers.\n */\nexport class AttendanceService {\n constructor(\n private readonly db: DatabaseInterface,\n private readonly actor: AttendanceActor,\n ) {\n if (!actor.tenantId || !actor.profileId)\n throw new Error('Attendance requires a trusted tenant/profile actor.');\n this.actor = {\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n };\n }\n\n private identity(value: string): string {\n return attendanceIdentity(this.db, value);\n }\n\n /**\n * Run `operation` in the actor's serialized attendance transaction. The\n * context's collections share the executor, so corrections and replay can\n * compose several attendance mutations (with the segment helpers) atomically.\n * Results that are punches or segments are re-read after commit.\n */\n async withTransaction<T>(\n operation: (context: AttendanceContext) => Promise<T>,\n ): Promise<T> {\n return this.transact(operation);\n }\n\n private async transact<T>(\n operation: (context: Context) => Promise<T>,\n ): Promise<T> {\n const current = getTenantId();\n if (current && this.identity(current) !== this.actor.tenantId)\n throw new Error('Attendance actor differs from active tenant.');\n return withTenant({ tenantId: this.actor.tenantId }, async () => {\n const result = await withEmbeddedWriteTransaction(\n this.db,\n isEmbeddedDatabase(this.db),\n async (db) => {\n if (isPostgresDatabase(db))\n await db.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify([\n 'smrt.attendance',\n this.actor.tenantId,\n this.actor.profileId,\n ]),\n );\n return operation({\n db,\n punches: await AttendancePunchCollection.create({ db }),\n breaks: await AttendanceBreakCollection.create({ db }),\n segments: await AttendanceSegmentCollection.create({ db }),\n replays: await AttendanceReplayCollection.create({ db }),\n });\n },\n true,\n );\n if (result instanceof AttendancePunch) {\n const punches = await AttendancePunchCollection.create({ db: this.db });\n const [fresh] = await punches.list({\n where: { ...this.actor, id: result.id },\n limit: 1,\n });\n if (!fresh) throw new Error('Committed attendance punch disappeared.');\n return fresh as T;\n }\n if (result instanceof AttendanceSegment) {\n const segments = await AttendanceSegmentCollection.create({\n db: this.db,\n });\n const [fresh] = await segments.list({\n where: { tenantId: this.actor.tenantId, id: result.id },\n limit: 1,\n });\n if (!fresh)\n throw new Error('Committed attendance segment disappeared.');\n return fresh as T;\n }\n return result;\n });\n }\n\n private date(at: Date): Date {\n if (!(at instanceof Date) || !Number.isFinite(at.getTime()))\n throw new AttendanceConflict(\n 'Attendance timestamps must be valid dates.',\n );\n return at;\n }\n\n private async open(tx: Context): Promise<AttendancePunch> {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('No open punch.', 'no_open_punch');\n return punch;\n }\n\n private async chronological(tx: Context, at: Date): Promise<void> {\n const open = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n const rows = open.length\n ? open\n : await tx.punches.list({\n where: { ...this.actor },\n orderBy: 'ended_at DESC',\n limit: 1,\n });\n if (!rows.length) return;\n const punch = rows[0];\n const breaks = await tx.breaks.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const segments = await tx.segments.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const latest = Math.max(\n punch.startedAt.getTime(),\n ...segments.flatMap((g) => [\n g.startedAt.getTime(),\n g.endedAt?.getTime() ?? 0,\n ]),\n punch.endedAt?.getTime() ?? 0,\n ...breaks.flatMap((b) => [\n b.startedAt.getTime(),\n b.endedAt?.getTime() ?? 0,\n ]),\n );\n if (at.getTime() < latest)\n throw new AttendanceConflict(\n 'Tap predates committed attendance; manual review required.',\n 'predates_committed',\n );\n }\n\n /** Open one punch; the database unique slot also protects direct model writers. */\n async punchIn(\n at = new Date(),\n options?: AttendancePunchOptions,\n ): Promise<AttendancePunch> {\n const meta = attendanceProvenance(options);\n return this.transact((tx) =>\n this.transition(tx, 'punchIn', this.date(at), false, undefined, false, {\n ...meta,\n kind: '',\n }),\n );\n }\n /** Close the punch and any active break; optionally create linked draft evidence. */\n async punchOut(at: Date, work?: AttendanceWork): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(tx, 'punchOut', this.date(at), false, work),\n );\n }\n /** Begin a break; paid defaults false and must be explicitly configured by the caller. */\n async startBreak(\n at: Date,\n paid = false,\n options?: AttendanceBreakOptions,\n ): Promise<AttendancePunch> {\n const kind = attendanceLabel(options?.kind, 'kind');\n return this.transact((tx) =>\n this.transition(tx, 'startBreak', this.date(at), paid, undefined, false, {\n ...attendanceProvenance(),\n kind,\n }),\n );\n }\n /**\n * End the current break. With `{ resumeWork: true }` the work interrupted by\n * `startBreak` resumes at the same instant, atomically with the break end.\n */\n async endBreak(\n at: Date,\n options?: { resumeWork?: boolean },\n ): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(\n tx,\n 'endBreak',\n this.date(at),\n false,\n undefined,\n options?.resumeWork,\n ),\n );\n }\n\n /** Open a work segment in the open punch; refuses during a break or while one is open. */\n async startWork(at: Date, work: AttendanceWork): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n validateAttendanceWork(work);\n await this.chronological(tx, this.date(at));\n return beginSegment(tx, await this.open(tx), at, work);\n });\n }\n /** Atomically close the open segment and open the next one at `at`. */\n async switchWork(at: Date, work: AttendanceWork): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n validateAttendanceWork(work);\n await this.chronological(tx, this.date(at));\n return replaceSegment(tx, await this.open(tx), at, work);\n });\n }\n /** Close the open segment without starting another. */\n async stopWork(at: Date): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n await this.chronological(tx, this.date(at));\n return endSegment(tx, await this.open(tx), at);\n });\n }\n /** Reopen the work a break interrupted (the latest segment must have closed on `startBreak`). */\n async resumeWork(at: Date): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n await this.chronological(tx, this.date(at));\n return continueSegment(tx, await this.open(tx), at);\n });\n }\n /** The open work segment of the actor's open punch, or null. */\n async currentWork(): Promise<AttendanceSegment | null> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n return punch ? openSegment(tx, punch) : null;\n });\n }\n /** Work segments of one of the actor's punches, oldest first. */\n async segments(punchId: string): Promise<AttendanceSegment[]> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: punchId },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for actor.');\n return listSegments(tx, punch);\n });\n }\n\n private async transition(\n tx: Context,\n action: AttendanceTap['action'],\n at: Date,\n paid = false,\n work?: AttendanceWork,\n resumeWork = false,\n meta: ReturnType<typeof attendanceProvenance> & { kind: string } = {\n ...attendanceProvenance(),\n kind: '',\n },\n ): Promise<AttendancePunch> {\n await this.chronological(tx, at);\n if (action === 'punchIn') {\n if (\n (\n await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n })\n ).length\n )\n throw new AttendanceConflict('A punch is already open.', 'punch_open');\n return tx.punches.create({\n ...this.actor,\n startedAt: at,\n source: meta.source,\n deviceRefType: meta.deviceRefType,\n deviceRefId: meta.deviceRefId,\n _insertOnly: true,\n });\n }\n const punch = await this.open(tx);\n const breaks = await tx.breaks.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const active = breaks.find((b) => !b.endedAt);\n if (action === 'startBreak') {\n if (active)\n throw new AttendanceConflict('A break is already open.', 'break_open');\n if (typeof paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n await closeOpenSegment(tx, punch, at, 'break');\n await tx.breaks.create({\n tenantId: this.actor.tenantId,\n punchId: punch.id ?? undefined,\n startedAt: at,\n paid,\n kind: meta.kind,\n _insertOnly: true,\n });\n return punch;\n }\n if (action !== 'endBreak' && action !== 'punchOut')\n throw new AttendanceConflict('Unknown attendance action.');\n if (action === 'endBreak' && !active)\n throw new AttendanceConflict('No open break.', 'no_open_break');\n if (action === 'punchOut' && work) {\n this.validateWork(work);\n const unpaidMilliseconds = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + (b.endedAt ?? at).getTime() - b.startedAt.getTime(),\n 0,\n );\n if (\n Math.round(\n (at.getTime() - punch.startedAt.getTime() - unpaidMilliseconds) /\n 1000,\n ) <= 0\n )\n throw new AttendanceConflict(\n 'Only closed punches with positive duration can produce service evidence.',\n );\n }\n if (active) {\n active.endedAt = at;\n await active.save();\n }\n if (action === 'endBreak') {\n if (resumeWork) await continueSegment(tx, punch, at);\n return punch;\n }\n await closeOpenSegment(tx, punch, at, 'punchOut');\n punch.endedAt = at;\n const unpaidMilliseconds = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + b.endedAt!.getTime() - b.startedAt.getTime(),\n 0,\n );\n punch.unpaidBreakSeconds = Math.round(unpaidMilliseconds / 1000);\n punch.durationSeconds = Math.round(\n (at.getTime() - punch.startedAt.getTime() - unpaidMilliseconds) / 1000,\n );\n await punch.save();\n if (work) await this.link(tx, punch, work);\n return punch;\n }\n\n /** Run one replayed tap on `tx`; work actions share the punch changes' transaction. */\n private async apply(\n tx: Context,\n tap: AttendanceTap,\n at: Date,\n ): Promise<{ punch: AttendancePunch; segment?: AttendanceSegment }> {\n const work = (): AttendanceWork => {\n if (!tap.work)\n throw new AttendanceConflict('Work is required.', 'invalid');\n return tap.work;\n };\n let segment: AttendanceSegment;\n switch (tap.action) {\n case 'startWork':\n await this.chronological(tx, at);\n segment = await beginSegment(tx, await this.open(tx), at, work());\n break;\n case 'switchWork':\n await this.chronological(tx, at);\n segment = await replaceSegment(tx, await this.open(tx), at, work());\n break;\n case 'stopWork':\n await this.chronological(tx, at);\n segment = await endSegment(tx, await this.open(tx), at);\n break;\n case 'resumeWork':\n await this.chronological(tx, at);\n segment = await continueSegment(tx, await this.open(tx), at);\n break;\n default:\n return {\n punch: await this.transition(\n tx,\n tap.action,\n at,\n tap.paid,\n tap.work,\n false,\n {\n ...attendanceProvenance({\n source: tap.source,\n device: tap.device,\n }),\n kind: attendanceLabel(tap.kind, 'kind'),\n },\n ),\n };\n }\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: segment.punchId },\n limit: 1,\n });\n return { punch, segment };\n }\n\n private validateWork(work: AttendanceWork): void {\n validateAttendanceWork(work);\n }\n\n private async link(\n tx: Context,\n punch: AttendancePunch,\n work: AttendanceWork,\n ): Promise<void> {\n this.validateWork(work);\n if (!punch.endedAt || punch.durationSeconds <= 0)\n throw new AttendanceConflict(\n 'Only closed punches with positive duration can produce service evidence.',\n );\n const entries = await ServiceTimeEntryCollection.create({ db: tx.db });\n if (punch.serviceTimeEntryId) {\n const [existing] = await entries.list({\n where: { id: punch.serviceTimeEntryId, tenantId: this.actor.tenantId },\n limit: 1,\n });\n if (\n !existing ||\n existing.workRefType !== work.workRefType ||\n existing.workRefId !== work.workRefId ||\n existing.description !== work.description ||\n !sameAttribution(existing.metadata, work)\n )\n throw new AttendanceConflict('Punch already linked to different work.');\n return;\n }\n const entry = await entries.create({\n tenantId: this.actor.tenantId,\n participantKind: 'human',\n participantProfileId: this.actor.profileId,\n workRefType: work.workRefType,\n workRefId: work.workRefId,\n description: work.description,\n source: 'timer',\n startedAt: punch.startedAt,\n endedAt: punch.endedAt,\n durationSeconds: punch.durationSeconds,\n status: 'draft',\n metadata: JSON.stringify({\n attendancePunchId: punch.id,\n ...(work.taskRefType\n ? { taskRefType: work.taskRefType, taskRefId: work.taskRefId }\n : {}),\n ...(work.kind ? { kind: work.kind } : {}),\n }),\n _insertOnly: true,\n });\n if (!entry.id) throw new Error('Saved service entry has no id.');\n punch.serviceTimeEntryId = entry.id;\n await punch.save();\n }\n\n /** Idempotently link a closed, owned punch to draft service evidence. */\n async linkServiceTimeEntry(\n punchId: string,\n work: AttendanceWork,\n ): Promise<AttendancePunch> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: punchId },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for actor.');\n await this.link(tx, punch, work);\n return punch;\n });\n }\n\n /**\n * Close the open punch once its deadline has passed, flagging it for review.\n * `deadline` is either a fixed maximum open duration in seconds (closing at\n * `startedAt + seconds`, reason `maxDuration`) or a resolver called with the\n * open punch that returns its deadline (a `Date`, or `{ deadline, reason }`),\n * or null to leave it open. The consumer owns time zones and shop-day rules.\n * The open work segment and break close at the deadline; returns the closed\n * punch, or null when nothing was due.\n */\n async autoClose(\n now: Date,\n deadline: AttendanceAutoCloseDeadline,\n ): Promise<AttendancePunch | null> {\n this.date(now);\n if (\n typeof deadline !== 'function' &&\n (!Number.isSafeInteger(deadline) || deadline <= 0)\n )\n throw new AttendanceConflict(\n 'maxOpenSeconds must be a positive integer.',\n );\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n if (!punch) return null;\n let at: Date;\n let reason = 'maxDuration';\n if (typeof deadline === 'function') {\n const resolved = await deadline(punch);\n if (resolved === null || resolved === undefined) return null;\n const decision =\n resolved instanceof Date ? { deadline: resolved } : resolved;\n at = decision.deadline;\n if (!(at instanceof Date) || !Number.isFinite(at.getTime()))\n throw new AttendanceConflict('Auto-close deadline is invalid.');\n if (at < punch.startedAt)\n throw new AttendanceConflict(\n 'Auto-close deadline predates the punch.',\n );\n reason = decision.reason ?? 'deadline';\n if (typeof reason !== 'string' || !reason.trim() || reason.length > 200)\n throw new AttendanceConflict(\n 'Auto-close reason must be 1 to 200 characters.',\n );\n } else {\n at = new Date(punch.startedAt.getTime() + deadline * 1000);\n }\n if (at > now) return null;\n // Do not truncate a later committed break. The conflict requires manual review.\n const closed = await this.transition(tx, 'punchOut', at);\n closed.reviewRequired = true;\n closed.autoCloseReason = reason;\n await persistAutoClose(closed);\n return closed;\n });\n }\n\n /**\n * Replay device timestamps in order (ties use clientId). Committed attendance\n * wins: stale/state-conflicting taps get durable refusals; identical retries\n * return the same outcome, changed payloads using a key are rejected. A\n * database failure rolls back its tap and receipt and remains retryable.\n */\n async replay(\n taps: readonly AttendanceTap[],\n options: AttendanceReplayOptions = {},\n ): Promise<AttendanceReplayOutcome[]> {\n for (const tap of taps) {\n if (\n !tap ||\n typeof tap.at !== 'string' ||\n typeof tap.clientId !== 'string' ||\n !tap.clientId ||\n !TAP_ACTIONS.includes(tap.action)\n )\n throw new AttendanceConflict('Malformed attendance tap.');\n this.date(new Date(tap.at));\n if (tap.work !== undefined) this.validateWork(tap.work);\n if (\n (tap.action === 'startWork' || tap.action === 'switchWork') &&\n tap.work === undefined\n )\n throw new AttendanceConflict('Malformed attendance tap.');\n if (tap.paid !== undefined && typeof tap.paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n attendanceProvenance({ source: tap.source, device: tap.device });\n attendanceLabel(tap.kind, 'kind');\n }\n const ordered = [...taps].sort(\n (a, b) =>\n Date.parse(a.at) - Date.parse(b.at) ||\n (a.clientId < b.clientId ? -1 : a.clientId > b.clientId ? 1 : 0),\n );\n const outcomes: AttendanceReplayOutcome[] = [];\n for (const tap of ordered)\n outcomes.push(\n await this.transact(async (tx) => {\n const replayKey = JSON.stringify([\n this.actor.tenantId,\n this.actor.profileId,\n tap.clientId,\n ]);\n const content = JSON.stringify([\n tap.action,\n new Date(tap.at).toISOString(),\n tap.paid ?? false,\n tap.work?.workRefType ?? null,\n tap.work?.workRefId ?? null,\n tap.work?.description ?? null,\n ...(tap.work?.taskRefType || tap.work?.kind\n ? [\n tap.work.taskRefType ?? null,\n tap.work.taskRefId ?? null,\n tap.work.kind ?? null,\n ]\n : []),\n // Provenance joins the hash only when present, so receipts of taps\n // that carry none are unchanged.\n ...(tap.source || tap.device || tap.kind\n ? [\n {\n source: tap.source ?? '',\n deviceType: tap.device?.type ?? '',\n deviceId: tap.device?.id ?? '',\n kind: tap.kind ?? '',\n },\n ]\n : []),\n ]);\n const [receipt] = await tx.replays.list({\n where: { tenantId: this.actor.tenantId, replayKey },\n limit: 1,\n });\n if (receipt) {\n if (receipt.content !== content)\n throw new AttendanceConflict(\n 'Client id reused with different content.',\n );\n const outcome = receipt.getOutcome();\n if (outcome.clientId !== tap.clientId)\n throw new Error(\n 'Stored attendance receipt belongs to another client id.',\n );\n return outcome;\n }\n const deviceAt = new Date(tap.at);\n let at = deviceAt;\n let review = false;\n if (options.resolveAt) {\n const serverNow = this.date(options.now?.() ?? new Date());\n const resolved = await options.resolveAt({\n tap,\n deviceAt,\n serverNow,\n waitedMs: serverNow.getTime() - deviceAt.getTime(),\n actor: { ...this.actor },\n });\n if (\n !resolved ||\n !(resolved.at instanceof Date) ||\n !Number.isFinite(resolved.at.getTime())\n )\n throw new Error('resolveAt must return a valid date.');\n at = resolved.at;\n review = resolved.reviewRequired === true;\n }\n let outcome: AttendanceReplayOutcome;\n try {\n // Domain refusals are validated before the first write. Adapters with\n // savepoints isolate the tap; every failure remains in the outer atomic scope.\n // Any database failure escapes and rolls back the entire transaction.\n outcome = await withEmbeddedWriteTransaction(\n tx.db,\n isEmbeddedDatabase(tx.db),\n async (db) => {\n const inner = {\n db,\n punches: await AttendancePunchCollection.create({ db }),\n breaks: await AttendanceBreakCollection.create({ db }),\n segments: await AttendanceSegmentCollection.create({ db }),\n replays: tx.replays,\n };\n const { punch, segment } = await this.apply(inner, tap, at);\n punch.recordedOffline = true;\n punch.reviewRequired = true;\n await punch.save();\n if (segment && review) {\n segment.reviewRequired = true;\n await segment.save();\n }\n return {\n clientId: tap.clientId,\n punchId: punch.id!,\n ...(segment ? { segmentId: segment.id! } : {}),\n ...(at.getTime() !== deviceAt.getTime()\n ? { resolvedAt: at.toISOString() }\n : {}),\n };\n },\n true,\n );\n } catch (error) {\n if (!(error instanceof AttendanceConflict)) throw error;\n const custom = options.refusal?.(tap, error) || undefined;\n outcome = {\n clientId: tap.clientId,\n error: custom?.message || error.message,\n code: custom?.code || error.code,\n };\n }\n await tx.replays.create({\n tenantId: this.actor.tenantId,\n replayKey,\n content,\n outcome: JSON.stringify(outcome),\n _insertOnly: true,\n });\n return outcome;\n }),\n );\n return outcomes;\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport {\n type AttendanceActor,\n AttendanceConflict,\n type AttendanceContext,\n type AttendancePunchOptions,\n type AttendanceWork,\n attendanceIdentity,\n attendanceLabel,\n attendanceProvenance,\n validateAttendanceWork,\n} from './context.js';\nimport {\n AttendanceCorrection,\n AttendanceCorrectionCollection,\n type AttendanceCorrectionKind,\n} from './correction-models.js';\nimport {\n AttendanceBreak,\n type AttendancePunch,\n AttendanceSegment,\n} from './models.js';\nimport { AttendanceService } from './service.js';\nimport { deleteCorrection, persistCorrection } from './write.js';\n\n/** A closed time range a correction touches, for period-lock checks. */\nexport interface AttendanceCorrectionInterval {\n startsAt: Date;\n endsAt: Date;\n}\n\n/**\n * Optional period lock, run on the correction's transaction before any write.\n * Return a message to refuse the correction (for example \"overlaps a confirmed\n * timecard\"), or null to allow it. `confirmedTimecardGuard` in `./rollup` is the\n * ready-made one.\n */\nexport type AttendanceCorrectionGuard = (\n tx: AttendanceContext,\n subject: AttendanceActor,\n intervals: readonly AttendanceCorrectionInterval[],\n) => Promise<string | null>;\n\nexport interface AttendanceCorrectionOptions {\n guard?: AttendanceCorrectionGuard;\n}\n\n/** Fields every correction carries. */\nexport interface AttendanceCorrectionRequest {\n /** Whose time is being corrected. */\n subjectProfileId: string;\n /** Required, non-blank. Stored verbatim (trimmed) on the ledger row. */\n reason: string;\n /** When true the subject must `acknowledge()` the correction. */\n acknowledgementRequired?: boolean;\n}\n\nexport interface CorrectPunchInput extends AttendanceCorrectionRequest {\n punchId: string;\n startedAt?: Date;\n endedAt?: Date;\n}\nexport interface AddPunchInput\n extends AttendanceCorrectionRequest,\n AttendancePunchOptions {\n startedAt: Date;\n endedAt: Date;\n}\n/** Adds a break when `breakId` is absent; otherwise changes that break. */\nexport interface SaveBreakInput extends AttendanceCorrectionRequest {\n punchId: string;\n breakId?: string;\n startedAt?: Date;\n endedAt?: Date;\n paid?: boolean;\n /** Short consumer-owned label (for example `meal`); omit to keep, `''` to clear. */\n kind?: string;\n}\nexport interface RemoveBreakInput extends AttendanceCorrectionRequest {\n breakId: string;\n}\n/**\n * Adds a segment when `segmentId` is absent (`startedAt`, `endedAt` and `work`\n * are then required); otherwise changes that segment, keeping what is omitted.\n */\nexport interface SaveSegmentInput extends AttendanceCorrectionRequest {\n punchId: string;\n segmentId?: string;\n startedAt?: Date;\n endedAt?: Date;\n work?: AttendanceWork;\n}\nexport interface RemoveSegmentInput extends AttendanceCorrectionRequest {\n segmentId: string;\n}\n\ninterface Layout {\n startsAt: number;\n endsAt: number;\n breaks: { startedAt: Date; endedAt: Date; paid: boolean }[];\n segments: { startedAt: Date; endedAt: Date }[];\n}\n\nconst iso = (d: Date | null) => (d ? d.toISOString() : null);\nconst punchState = (p: AttendancePunch) => ({\n startedAt: iso(p.startedAt),\n endedAt: iso(p.endedAt),\n durationSeconds: p.durationSeconds,\n unpaidBreakSeconds: p.unpaidBreakSeconds,\n reviewRequired: p.reviewRequired,\n ...(p.source ? { source: p.source } : {}),\n ...(p.deviceRefType\n ? { deviceRefType: p.deviceRefType, deviceRefId: p.deviceRefId }\n : {}),\n});\nconst breakState = (b: AttendanceBreak) => ({\n startedAt: iso(b.startedAt),\n endedAt: iso(b.endedAt),\n paid: b.paid,\n ...(b.kind ? { kind: b.kind } : {}),\n});\nconst segmentState = (s: AttendanceSegment) => ({\n startedAt: iso(s.startedAt),\n endedAt: iso(s.endedAt),\n workRefType: s.workRefType,\n workRefId: s.workRefId,\n taskRefType: s.taskRefType,\n taskRefId: s.taskRefId,\n kind: s.kind,\n description: s.description,\n durationSeconds: s.durationSeconds,\n closedReason: s.closedReason,\n});\n\nfunction instant(value: Date | undefined, name: string): Date {\n if (!(value instanceof Date) || !Number.isFinite(value.getTime()))\n throw new AttendanceConflict(`${name} must be a valid date.`);\n return value;\n}\n\nfunction overlapping(\n a: { start: number; end: number },\n b: { start: number; end: number },\n): boolean {\n return a.start < b.end && b.start < a.end;\n}\n\nfunction validateLayout(layout: Layout): void {\n const inside = (r: { startedAt: Date; endedAt: Date }) =>\n r.endedAt.getTime() >= r.startedAt.getTime() &&\n r.startedAt.getTime() >= layout.startsAt &&\n r.endedAt.getTime() <= layout.endsAt;\n if (!layout.breaks.every(inside))\n throw new AttendanceConflict(\n 'A break must be an interval inside its punch.',\n );\n if (!layout.segments.every(inside))\n throw new AttendanceConflict(\n 'A work segment must be an interval inside its punch.',\n );\n const span = (r: { startedAt: Date; endedAt: Date }) => ({\n start: r.startedAt.getTime(),\n end: r.endedAt.getTime(),\n });\n const sets = [layout.breaks, layout.segments];\n for (const set of sets)\n for (let i = 0; i < set.length; i++)\n for (let j = i + 1; j < set.length; j++)\n if (overlapping(span(set[i]), span(set[j])))\n throw new AttendanceConflict(\n set === layout.breaks\n ? 'Breaks must not overlap.'\n : 'Work segments must not overlap.',\n );\n for (const b of layout.breaks)\n for (const s of layout.segments)\n if (overlapping(span(b), span(s)))\n throw new AttendanceConflict(\n 'A work segment must not overlap a break.',\n );\n}\n\n/**\n * Corrections to closed attendance, with a paper trail.\n *\n * `actor` is who makes the change; each request names the `subjectProfileId`\n * whose time it is. The application authorizes the actor (and decides whether\n * they may correct this subject) before constructing the service, exactly as\n * for `AttendanceService`. This service is the only path that may change a\n * closed punch, break or segment: each method validates the resulting layout\n * (everything inside its punch, no overlapping breaks, segments or punches,\n * no segment across a break), recomputes `durationSeconds` and\n * `unpaidBreakSeconds`, keeps a still-draft linked service entry in step, and\n * appends an `AttendanceCorrection` row, all in one transaction serialized with\n * the subject's own taps. Open punches are not corrected here: use\n * `AttendanceService`.\n */\nexport class AttendanceCorrectionService {\n private readonly actor: AttendanceActor;\n constructor(\n private readonly db: DatabaseInterface,\n actor: AttendanceActor,\n private readonly options: AttendanceCorrectionOptions = {},\n ) {\n if (!actor?.tenantId || !actor.profileId)\n throw new Error('Corrections require a trusted tenant/profile actor.');\n this.actor = {\n tenantId: attendanceIdentity(db, actor.tenantId),\n profileId: attendanceIdentity(db, actor.profileId),\n };\n }\n\n /** Change a closed punch's start and/or end. A reviewed punch no longer needs review. */\n async correctPunch(input: CorrectPunchInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const before = punchState(punch);\n const startedAt =\n input.startedAt === undefined\n ? punch.startedAt\n : instant(input.startedAt, 'startedAt');\n const endedAt =\n input.endedAt === undefined\n ? punch.endedAt!\n : instant(input.endedAt, 'endedAt');\n if (endedAt <= startedAt)\n throw new AttendanceConflict('A punch must end after it starts.');\n const children = await this.children(tx, punch);\n validateLayout(this.layout(startedAt, endedAt, children));\n for (const other of await tx.punches.list({\n where: { tenantId: subject.tenantId, profileId: subject.profileId },\n }))\n if (\n other.id !== punch.id &&\n overlapping(\n { start: startedAt.getTime(), end: endedAt.getTime() },\n {\n start: other.startedAt.getTime(),\n end: other.endedAt?.getTime() ?? Number.POSITIVE_INFINITY,\n },\n )\n )\n throw new AttendanceConflict('Punches must not overlap.');\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n { startsAt: startedAt, endsAt: endedAt },\n ]);\n const entry = await this.editableEntry(tx, punch);\n punch.startedAt = startedAt;\n punch.endedAt = endedAt;\n punch.reviewRequired = false;\n await this.settle(tx, punch, children.breaks, entry);\n return record(\n 'punch.corrected',\n punch,\n punch.id!,\n before,\n punchState(punch),\n );\n });\n }\n\n /** Record a missed punch retroactively, as an already-closed interval. */\n async addPunch(input: AddPunchInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const startedAt = instant(input.startedAt, 'startedAt');\n const endedAt = instant(input.endedAt, 'endedAt');\n const meta = attendanceProvenance(input);\n if (endedAt <= startedAt)\n throw new AttendanceConflict('A punch must end after it starts.');\n for (const other of await tx.punches.list({\n where: { tenantId: subject.tenantId, profileId: subject.profileId },\n }))\n if (\n overlapping(\n { start: startedAt.getTime(), end: endedAt.getTime() },\n {\n start: other.startedAt.getTime(),\n end: other.endedAt?.getTime() ?? Number.POSITIVE_INFINITY,\n },\n )\n )\n throw new AttendanceConflict('Punches must not overlap.');\n await this.guard(tx, subject, [{ startsAt: startedAt, endsAt: endedAt }]);\n const punch = await tx.punches.create({\n tenantId: subject.tenantId,\n profileId: subject.profileId,\n startedAt,\n endedAt,\n durationSeconds: Math.round(\n (endedAt.getTime() - startedAt.getTime()) / 1000,\n ),\n ...meta,\n _insertOnly: true,\n });\n return record('punch.added', punch, punch.id!, null, punchState(punch));\n });\n }\n\n /** Add a break to a closed punch, or change one (times and/or paid). */\n async saveBreak(input: SaveBreakInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const children = await this.children(tx, punch);\n const existing = input.breakId\n ? children.breaks.find((b) => b.id === input.breakId)\n : undefined;\n if (input.breakId && !existing)\n throw new AttendanceConflict('Break not found in that punch.');\n if (\n !existing &&\n (input.startedAt === undefined || input.endedAt === undefined)\n )\n throw new AttendanceConflict(\n 'A new break needs startedAt and endedAt.',\n );\n if (input.paid !== undefined && typeof input.paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n const next = {\n startedAt: instant(input.startedAt ?? existing?.startedAt, 'startedAt'),\n endedAt: instant(\n input.endedAt ?? existing?.endedAt ?? undefined,\n 'endedAt',\n ),\n paid: input.paid ?? existing?.paid ?? false,\n kind:\n input.kind === undefined\n ? (existing?.kind ?? '')\n : attendanceLabel(input.kind, 'kind'),\n };\n const rest = children.breaks.filter((b) => b !== existing);\n validateLayout(\n this.layout(punch.startedAt, punch.endedAt!, {\n breaks: [...rest, next as unknown as AttendanceBreak],\n segments: children.segments,\n }),\n );\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const entry = await this.editableEntry(tx, punch);\n const before = existing ? breakState(existing) : null;\n let target: AttendanceBreak;\n if (existing) {\n existing.startedAt = next.startedAt;\n existing.endedAt = next.endedAt;\n existing.paid = next.paid;\n existing.kind = next.kind;\n await persistCorrection(existing);\n target = existing;\n } else {\n target = await new AttendanceBreak({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(target, {\n tenantId: subject.tenantId,\n punchId: punch.id ?? undefined,\n ...next,\n });\n await persistCorrection(target);\n }\n await this.settle(tx, punch, [...rest, target], entry);\n return record(\n existing ? 'break.changed' : 'break.added',\n punch,\n target.id!,\n before,\n breakState(target),\n );\n });\n }\n\n /** Remove a break from a closed punch. */\n async removeBreak(input: RemoveBreakInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const [target] = await tx.breaks.list({\n where: { tenantId: subject.tenantId, id: input.breakId },\n limit: 1,\n });\n if (!target) throw new AttendanceConflict('Break not found.');\n const punch = await this.closedPunch(tx, subject, target.punchId);\n const children = await this.children(tx, punch);\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const entry = await this.editableEntry(tx, punch);\n const before = breakState(target);\n await deleteCorrection(target);\n await this.settle(\n tx,\n punch,\n children.breaks.filter((b) => b.id !== target.id),\n entry,\n );\n return record('break.removed', punch, target.id!, before, null);\n });\n }\n\n /** Add a work segment to a closed punch, or change one (times and/or work attribution). */\n async saveSegment(input: SaveSegmentInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const children = await this.children(tx, punch);\n const existing = input.segmentId\n ? children.segments.find((s) => s.id === input.segmentId)\n : undefined;\n if (input.segmentId && !existing)\n throw new AttendanceConflict('Work segment not found in that punch.');\n if (\n !existing &&\n (input.startedAt === undefined ||\n input.endedAt === undefined ||\n !input.work)\n )\n throw new AttendanceConflict(\n 'A new work segment needs startedAt, endedAt and work.',\n );\n if (input.work) validateAttendanceWork(input.work);\n const next = {\n startedAt: instant(input.startedAt ?? existing?.startedAt, 'startedAt'),\n endedAt: instant(\n input.endedAt ?? existing?.endedAt ?? undefined,\n 'endedAt',\n ),\n };\n const rest = children.segments.filter((s) => s !== existing);\n validateLayout(\n this.layout(punch.startedAt, punch.endedAt!, {\n breaks: children.breaks,\n segments: [...rest, next as unknown as AttendanceSegment],\n }),\n );\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const before = existing ? segmentState(existing) : null;\n let target: AttendanceSegment;\n if (existing) {\n existing.startedAt = next.startedAt;\n existing.endedAt = next.endedAt;\n if (input.work) this.attribute(existing, input.work);\n await persistCorrection(existing);\n target = existing;\n } else {\n target = await new AttendanceSegment({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(target, {\n tenantId: subject.tenantId,\n punchId: punch.id ?? undefined,\n workRefType: input.work!.workRefType,\n workRefId: input.work!.workRefId,\n taskRefType: input.work!.taskRefType ?? '',\n taskRefId: input.work!.taskRefId ?? '',\n kind: input.work!.kind ?? '',\n description: input.work!.description,\n ...next,\n closedReason: 'stop',\n });\n await persistCorrection(target);\n }\n return record(\n existing ? 'segment.changed' : 'segment.added',\n punch,\n target.id!,\n before,\n segmentState(target),\n );\n });\n }\n\n /** Remove a work segment from a closed punch. */\n async removeSegment(\n input: RemoveSegmentInput,\n ): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const [target] = await tx.segments.list({\n where: { tenantId: subject.tenantId, id: input.segmentId },\n limit: 1,\n });\n if (!target) throw new AttendanceConflict('Work segment not found.');\n const punch = await this.closedPunch(tx, subject, target.punchId);\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const before = segmentState(target);\n await deleteCorrection(target);\n return record('segment.removed', punch, target.id!, before, null);\n });\n }\n\n /**\n * The subject confirms they have seen a correction that required it. Only\n * the subject (this service's actor) may; it can be done once.\n */\n async acknowledge(\n correctionId: string,\n at: Date = new Date(),\n ): Promise<AttendanceCorrection> {\n instant(at, 'at');\n const id = await this.attendance(this.actor.profileId).withTransaction(\n async (tx) => {\n const ledger = await AttendanceCorrectionCollection.create({\n db: tx.db,\n });\n const [row] = await ledger.list({\n where: { tenantId: this.actor.tenantId, id: correctionId },\n limit: 1,\n });\n if (!row) throw new AttendanceConflict('Correction not found.');\n if (row.subjectProfileId !== this.actor.profileId)\n throw new AttendanceConflict(\n 'Only the subject can acknowledge a correction.',\n );\n if (!row.acknowledgementRequired)\n throw new AttendanceConflict(\n 'This correction does not require acknowledgement.',\n );\n if (row.acknowledgedAt)\n throw new AttendanceConflict('Correction already acknowledged.');\n row.acknowledgedAt = at;\n row.acknowledgedByProfileId = this.actor.profileId;\n await persistCorrection(row);\n return row.id!;\n },\n );\n return this.read(id);\n }\n\n /** Corrections to one punch, oldest first. */\n async listForPunch(punchId: string): Promise<AttendanceCorrection[]> {\n return this.attendance(this.actor.profileId).withTransaction(\n async (tx) =>\n await (await AttendanceCorrectionCollection.create({ db: tx.db })).list(\n {\n where: { tenantId: this.actor.tenantId, punchId },\n orderBy: 'created_at ASC',\n },\n ),\n );\n }\n\n /** Corrections of the actor's own time that still await their acknowledgement. */\n async pendingAcknowledgements(): Promise<AttendanceCorrection[]> {\n return this.attendance(this.actor.profileId).withTransaction(async (tx) =>\n (\n await (\n await AttendanceCorrectionCollection.create({ db: tx.db })\n ).list({\n where: {\n tenantId: this.actor.tenantId,\n subjectProfileId: this.actor.profileId,\n },\n orderBy: 'created_at ASC',\n })\n ).filter((row) => row.acknowledgementRequired && !row.acknowledgedAt),\n );\n }\n\n private attendance(subjectProfileId: string): AttendanceService {\n return new AttendanceService(this.db, {\n tenantId: this.actor.tenantId,\n profileId: subjectProfileId,\n });\n }\n\n private async read(id: string): Promise<AttendanceCorrection> {\n return this.attendance(this.actor.profileId).withTransaction(async (tx) => {\n const [row] = await (\n await AttendanceCorrectionCollection.create({ db: tx.db })\n ).list({ where: { tenantId: this.actor.tenantId, id }, limit: 1 });\n if (!row) throw new Error('Committed attendance correction disappeared.');\n return row;\n });\n }\n\n private async run(\n request: AttendanceCorrectionRequest,\n operation: (\n tx: AttendanceContext,\n subject: AttendanceActor,\n record: (\n kind: AttendanceCorrectionKind,\n punch: AttendancePunch,\n targetId: string,\n before: object | null,\n after: object | null,\n ) => Promise<string>,\n ) => Promise<string>,\n ): Promise<AttendanceCorrection> {\n const reason =\n typeof request.reason === 'string' ? request.reason.trim() : '';\n if (!reason) throw new AttendanceConflict('A correction needs a reason.');\n if (\n request.acknowledgementRequired !== undefined &&\n typeof request.acknowledgementRequired !== 'boolean'\n )\n throw new AttendanceConflict('acknowledgementRequired must be boolean.');\n if (!request.subjectProfileId)\n throw new AttendanceConflict('A correction needs a subject.');\n const subject: AttendanceActor = {\n tenantId: this.actor.tenantId,\n profileId: attendanceIdentity(this.db, request.subjectProfileId),\n };\n const id = await this.attendance(subject.profileId).withTransaction(\n async (tx) => {\n // Initialise the ledger table binding on this executor before writing.\n await AttendanceCorrectionCollection.create({ db: tx.db });\n return operation(\n tx,\n subject,\n async (kind, punch, targetId, before, after) => {\n const row = await new AttendanceCorrection({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(row, {\n tenantId: subject.tenantId,\n subjectProfileId: subject.profileId,\n actorProfileId: this.actor.profileId,\n kind,\n punchId: punch.id ?? undefined,\n targetId,\n beforeState: JSON.stringify(before),\n afterState: JSON.stringify(after),\n reason,\n acknowledgementRequired: request.acknowledgementRequired ?? false,\n });\n await persistCorrection(row);\n return row.id!;\n },\n );\n },\n );\n return this.read(id);\n }\n\n private async closedPunch(\n tx: AttendanceContext,\n subject: AttendanceActor,\n punchId: string,\n ): Promise<AttendancePunch> {\n const [punch] = await tx.punches.list({\n where: {\n tenantId: subject.tenantId,\n profileId: subject.profileId,\n id: punchId,\n },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for subject.');\n if (!punch.endedAt)\n throw new AttendanceConflict(\n 'Only closed punches are corrected; use AttendanceService for an open one.',\n );\n return punch;\n }\n\n private async children(tx: AttendanceContext, punch: AttendancePunch) {\n const where = { tenantId: punch.tenantId, punchId: punch.id };\n const breaks = await tx.breaks.list({ where, orderBy: 'started_at ASC' });\n const segments = await tx.segments.list({\n where,\n orderBy: 'started_at ASC',\n });\n if (breaks.some((b) => !b.endedAt) || segments.some((s) => !s.endedAt))\n throw new AttendanceConflict(\n 'Closed punch has an open break or segment.',\n );\n return { breaks, segments };\n }\n\n private layout(\n startedAt: Date,\n endedAt: Date,\n children: {\n breaks: readonly AttendanceBreak[];\n segments: readonly AttendanceSegment[];\n },\n ): Layout {\n return {\n startsAt: startedAt.getTime(),\n endsAt: endedAt.getTime(),\n breaks: children.breaks.map((b) => ({\n startedAt: b.startedAt,\n endedAt: b.endedAt!,\n paid: b.paid,\n })),\n segments: children.segments.map((s) => ({\n startedAt: s.startedAt,\n endedAt: s.endedAt!,\n })),\n };\n }\n\n private attribute(segment: AttendanceSegment, work: AttendanceWork): void {\n segment.workRefType = work.workRefType;\n segment.workRefId = work.workRefId;\n segment.taskRefType = work.taskRefType ?? '';\n segment.taskRefId = work.taskRefId ?? '';\n segment.kind = work.kind ?? '';\n segment.description = work.description;\n }\n\n private async guard(\n tx: AttendanceContext,\n subject: AttendanceActor,\n intervals: readonly AttendanceCorrectionInterval[],\n ): Promise<void> {\n const refusal = await this.options.guard?.(tx, subject, intervals);\n if (refusal) throw new AttendanceConflict(refusal);\n }\n\n /** The punch's still-draft evidence entry, refusing when it is no longer editable. */\n private async editableEntry(tx: AttendanceContext, punch: AttendancePunch) {\n if (!punch.serviceTimeEntryId) return null;\n const entries = await ServiceTimeEntryCollection.create({ db: tx.db });\n const [entry] = await entries.list({\n where: { id: punch.serviceTimeEntryId, tenantId: punch.tenantId },\n limit: 1,\n });\n if (!entry) return null;\n if (entry.status !== 'draft')\n throw new AttendanceConflict(\n `Linked service evidence is ${entry.status}; correct it with ServiceEvidenceService first.`,\n );\n return entry;\n }\n\n /** Recompute the punch's durations from its breaks and persist it and its draft entry. */\n private async settle(\n tx: AttendanceContext,\n punch: AttendancePunch,\n breaks: readonly AttendanceBreak[],\n entry: Awaited<ReturnType<AttendanceCorrectionService['editableEntry']>>,\n ): Promise<void> {\n const unpaidMs = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + b.endedAt!.getTime() - b.startedAt.getTime(),\n 0,\n );\n punch.unpaidBreakSeconds = Math.round(unpaidMs / 1000);\n punch.durationSeconds = Math.round(\n (punch.endedAt!.getTime() - punch.startedAt.getTime() - unpaidMs) / 1000,\n );\n await persistCorrection(punch);\n if (entry) {\n entry.startedAt = punch.startedAt;\n entry.endedAt = punch.endedAt;\n entry.durationSeconds = punch.durationSeconds;\n await entry.save();\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAcO,IAAM,8BAA8B;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,IAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AA2BO,IAAM,uBAAN,cAAmC,WAAW;CACvC,WAAW;CAGvB,mBAAmB;CAGnB,iBAAiB;CACQ,OAAiC;CAC7B,UAAU;;CAEvC,WAAW;CACc,cAAc;CACd,aAAa;CACb,SAAS;CAClC,0BAA0B;CAC1B,iBAA8B;CAE9B,0BAAyC;;CAGzC,YAAmD;EACjD,OAAO,mBAA6B,KAAK,aAAa,IAAI;CAC5D;;CAEA,WAAkD;EAChD,OAAO,mBAA6B,KAAK,YAAY,IAAI;CAC3D;CAEA,MAAe,OAAsB;EAGnC,IAAI,CAAC,kBAAkB,IAAI,GACzB,MAAM,IAAI,MACR,+EACF;EACF,IACE,CAAC,KAAK,YACN,CAAC,KAAK,oBACN,CAAC,KAAK,kBACN,CAAC,KAAK,WACN,CAAC,KAAK,YACN,CAAC,4BAA4B,SAAS,KAAK,IAAI,KAC/C,OAAO,KAAK,WAAW,YACvB,CAAC,KAAK,OAAO,KAAK,KAClB,OAAO,KAAK,4BAA4B,WAExC,MAAM,IAAI,MAAM,gCAAgC;EAClD,KAAA,MAAW,QAAQ,CAAC,KAAK,aAAa,KAAK,UAAU,GAAG,KAAK,MAAM,IAAI;EACvE,IAAI,CAAC,CAAC,KAAK,mBAAmB,CAAC,CAAC,KAAK,yBACnC,MAAM,IAAI,MAAM,kDAAkD;EACpE,IAAI,KAAK,gBAAgB;GACvB,IAAI,CAAC,KAAK,yBACR,MAAM,IAAI,MAAM,mDAAmD;GACrE,IAAI,KAAK,4BAA4B,KAAK,kBACxC,MAAM,IAAI,MAAM,gDAAgD;EACpE;EACA,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;EACJ,IAAI,OAAO;GACT,MAAM,MAAM;GACZ,MAAM,SAAS,WACb,OAAO,QAAQ,cAAc,GAAG,MAAc,EAAE,YAAY,CAAC;GAC/D,IACE,kBAAkB,MACf,WAAW,MAAM,YAAY,IAAI,MAAM,MAAM,EAChD,KACA,QAAQ,MAAM,wBAAwB,MACpC,KAAK,2BACN,MAAM,oBACJ,IAAI,KAAK,MAAM,eAAyB,CAAA,CAAE,QAAQ,MACjD,KAAK,gBAAgB,QAAQ,KAC7B,MAAM,+BAA+B,KAAK,0BAE9C,MAAM,IAAI,MACR,kFACF;EACJ;EACA,KAAK,OAAO,OAAO,WAAW;EAC9B,OAAO,MAAM,KAAK;CACpB;CAEA,MAAe,SAAwB;EACrC,MAAM,IAAI,MAAM,kDAAkD;CACpE;AACF;AAtFuD,cAA1C,sBAAiD,mBAAkB,gCAAA;AAClE,gBAAA,CAAX,SAAS,CAAA,GADC,qBACC,WAAA,YAAA,CAAA;AAGZ,gBAAA,CADC,gBAAgB,sCAAsC,CAAA,GAH5C,qBAIX,WAAA,oBAAA,CAAA;AAGA,gBAAA,CADC,gBAAgB,sCAAsC,CAAA,GAN5C,qBAOX,WAAA,kBAAA,CAAA;AACyB,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GARZ,qBAQc,WAAA,QAAA,CAAA;AACI,gBAAA,CAA5B,WAAW,eAAe,CAAA,GAThB,qBASkB,WAAA,WAAA,CAAA;AAGJ,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAZZ,qBAYc,WAAA,eAAA,CAAA;AACA,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAbZ,qBAac,WAAA,cAAA,CAAA;AACA,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAdZ,qBAcc,WAAA,UAAA,CAAA;AAIzB,gBAAA,CADC,gBAAgB,wCAAwC,EAAE,UAAU,KAAK,CAAC,CAAA,GAjBhE,qBAkBX,WAAA,2BAAA,CAAA;AAlBW,uBAAN,gBAAA,CAlBN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,WAAW;CACX,SAAS,CACP;EACE,MAAM;EACN,SAAS,CAAC,YAAY,SAAS;CACjC,GACA;EACE,MAAM;EACN,SAAS,CAAC,YAAY,kBAAkB;CAC1C,CACF;CACA,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;AACrB,CAAC,CAAA,GACY,oBAAA;AAyFN,IAAM,iCAAN,cAA6C,eAAqC;CACvF,OAAgB,aAAa;AAC/B;;;ACvGA,IAAM,YAAY;AAGX,SAAS,gBAAgB,OAAgB,MAAsB;CACpE,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IACE,OAAO,UAAU,YACjB,UAAU,MAAM,KAAK,KACrB,MAAM,SAAS,WAEf,MAAM,IAAI,mBACR,GAAG,KAAI,uCAAwC,UAAS,eACxD,SACF;CACF,OAAO;AACT;AAGO,SAAS,qBAAqB,SAInC;CACA,MAAM,SAAS,gBAAgB,SAAS,QAAQ,QAAQ;CACxD,MAAM,SAAS,SAAS;CACxB,IAAI,WAAW,KAAA,MAAc,CAAC,UAAU,OAAO,WAAW,WACxD,MAAM,IAAI,mBAAmB,gCAAgC,SAAS;CACxE,MAAM,gBAAgB,gBAAgB,QAAQ,MAAM,aAAa;CACjE,MAAM,cAAc,gBAAgB,QAAQ,IAAI,WAAW;CAC3D,IAAI,CAAC,CAAC,kBAAkB,CAAC,CAAC,aACxB,MAAM,IAAI,mBACR,wDACA,SACF;CACF,OAAO;EAAE;EAAQ;EAAe;CAAY;AAC9C;AAGO,IAAM,qBAAN,cAAiC,MAAM;;;;;CAKnC;CACT,YAAY,SAAiB,OAA8B,YAAY;EACrE,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAkCO,SAAS,uBAAuB,MAA4B;CACjE,IACE,CAAC,QACD,OAAO,KAAK,gBAAgB,YAC5B,CAAC,KAAK,eACN,OAAO,KAAK,cAAc,YAC1B,CAAC,KAAK,aACN,OAAO,KAAK,gBAAgB,UAE5B,MAAM,IAAI,mBACR,yDACF;CACF,KAAA,MAAW,OAAO;EAAC;EAAe;EAAa;CAAM,GACnD,IAAI,KAAK,SAAS,KAAA,KAAa,OAAO,KAAK,SAAS,UAClD,MAAM,IAAI,mBAAmB,GAAG,IAAG,mBAAoB;CAC3D,IAAI,CAAC,CAAC,KAAK,gBAAgB,CAAC,CAAC,KAAK,WAChC,MAAM,IAAI,mBACR,sDACF;AACJ;AAGO,SAAS,mBACd,IACA,OACQ;CACR,IAAI,CAAC,mBAAmB,EAAE,GAAG,OAAO;CACpC,MAAM,MAAM,MAAM,QAAQ,UAAU,EAAE,CAAA,CAAE,YAAY;CACpD,IAAI,CAAC,iBAAiB,KAAK,GAAG,GAC5B,MAAM,IAAI,MAAM,iDAAiD;CACnE,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAC,GAAI,IAAI,MAAM,GAAG,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,IAAI,EAAE,EAAC,GAAI,IAAI,MAAM,EAAE;AACzG;;;ACjJA,eAAsB,YACpB,IACA,OACmC;CACnC,MAAM,CAAC,WAAW,MAAM,GAAG,SAAS,KAAK;EACvC,OAAO;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;GAAI,SAAS;EAAK;EACpE,OAAO;CACT,CAAC;CACD,OAAO,WAAW;AACpB;AAGA,eAAsB,aACpB,IACA,OAC8B;CAC9B,OAAO,GAAG,SAAS,KAAK;EACtB,OAAO;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;EAAG;EACrD,SAAS;CACX,CAAC;AACH;AAGA,eAAsB,iBACpB,IACA,OACA,IACA,QACmC;CACnC,MAAM,UAAU,MAAM,YAAY,IAAI,KAAK;CAC3C,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,KAAK,QAAQ,WACf,MAAM,IAAI,mBAAmB,sCAAsC;CACrE,QAAQ,UAAU;CAClB,QAAQ,eAAe;CACvB,MAAM,QAAQ,KAAK;CACnB,OAAO;AACT;AAGA,eAAsB,aACpB,IACA,OACA,IACA,MAC4B;CAC5B,uBAAuB,IAAI;CAC3B,IAAI,MAAM,SAAS,MAAM,IAAI,mBAAmB,kBAAkB;CAIlE,KAAI,MAHiB,GAAG,OAAO,KAAK,EAClC,OAAO;EAAE,UAAU,MAAM;EAAU,SAAS,MAAM;CAAG,EACvD,CAAC,EAAA,CACU,MAAM,MAAM,CAAC,EAAE,OAAO,GAC/B,MAAM,IAAI,mBACR,qCACA,cACF;CACF,IAAI,MAAM,YAAY,IAAI,KAAK,GAC7B,MAAM,IAAI,mBACR,mCACA,WACF;CACF,OAAO,GAAG,SAAS,OAAO;EACxB,UAAU,MAAM;EAChB,SAAS,MAAM,MAAM,KAAA;EACrB,aAAa,KAAK;EAClB,WAAW,KAAK;EAChB,aAAa,KAAK,eAAe;EACjC,WAAW,KAAK,aAAa;EAC7B,MAAM,KAAK,QAAQ;EACnB,aAAa,KAAK;EAClB,WAAW;EACX,aAAa;CACf,CAAC;AACH;AAGA,eAAsB,eACpB,IACA,OACA,IACA,MAC4B;CAC5B,uBAAuB,IAAI;CAC3B,IAAI,CAAE,MAAM,iBAAiB,IAAI,OAAO,IAAI,QAAQ,GAClD,MAAM,IAAI,mBACR,mCACA,cACF;CACF,OAAO,aAAa,IAAI,OAAO,IAAI,IAAI;AACzC;AAGA,eAAsB,WACpB,IACA,OACA,IAC4B;CAC5B,MAAM,UAAU,MAAM,iBAAiB,IAAI,OAAO,IAAI,MAAM;CAC5D,IAAI,CAAC,SACH,MAAM,IAAI,mBAAmB,yBAAyB,cAAc;CACtE,OAAO;AACT;AAMA,eAAsB,gBACpB,IACA,OACA,IAC4B;CAC5B,MAAM,MAAM,MAAM,aAAa,IAAI,KAAK;CACxC,MAAM,OAAO,IAAI,IAAI,SAAS;CAG9B,MAAM,SAAS,MAAM,GAAG,OAAO,KAAK;EAClC,OAAO;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;EAAG;EACrD,SAAS;EACT,OAAO;CACT,CAAC;CACD,IACE,MAAM,iBAAiB,WACvB,CAAC,KAAK,WACN,KAAK,QAAQ,QAAQ,MAAM,OAAO,EAAC,EAAG,UAAU,QAAQ,GAExD,MAAM,IAAI,mBACR,6CACA,mBACF;CACF,OAAO,aAAa,IAAI,OAAO,IAAI,IAAI;AACzC;;;AC3GA,SAAS,gBAAgB,UAAkB,MAA+B;CACxE,MAAM,SAAS,mBAA4C,UAAU,CAAC,CAAC;CACvE,QACG,OAAO,eAAe,SAAS,KAAK,eAAe,QACnD,OAAO,aAAa,SAAS,KAAK,aAAa,QAC/C,OAAO,QAAQ,SAAS,KAAK,QAAQ;AAE1C;AA2CA,IAAM,cAAiC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAkFO,IAAM,oBAAN,MAAwB;CAC7B,YACmB,IACA,OACjB;EAFiB,KAAA,KAAA;EACA,KAAA,QAAA;EAEjB,IAAI,CAAC,MAAM,YAAY,CAAC,MAAM,WAC5B,MAAM,IAAI,MAAM,qDAAqD;EACvE,KAAK,QAAQ;GACX,UAAU,KAAK,SAAS,MAAM,QAAQ;GACtC,WAAW,KAAK,SAAS,MAAM,SAAS;EAC1C;CACF;CATmB;CACA;CAUX,SAAS,OAAuB;EACtC,OAAO,mBAAmB,KAAK,IAAI,KAAK;CAC1C;;;;;;;CAQA,MAAM,gBACJ,WACY;EACZ,OAAO,KAAK,SAAS,SAAS;CAChC;CAEA,MAAc,SACZ,WACY;EACZ,MAAM,UAAU,YAAY;EAC5B,IAAI,WAAW,KAAK,SAAS,OAAO,MAAM,KAAK,MAAM,UACnD,MAAM,IAAI,MAAM,8CAA8C;EAChE,OAAO,WAAW,EAAE,UAAU,KAAK,MAAM,SAAS,GAAG,YAAY;GAC/D,MAAM,SAAS,MAAM,6BACnB,KAAK,IACL,mBAAmB,KAAK,EAAE,GAC1B,OAAO,OAAO;IACZ,IAAI,mBAAmB,EAAE,GACvB,MAAM,GAAG,MACP,yDACA,KAAK,UAAU;KACb;KACA,KAAK,MAAM;KACX,KAAK,MAAM;IACb,CAAC,CACH;IACF,OAAO,UAAU;KACf;KACA,SAAS,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;KACtD,QAAQ,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;KACrD,UAAU,MAAM,4BAA4B,OAAO,EAAE,GAAG,CAAC;KACzD,SAAS,MAAM,2BAA2B,OAAO,EAAE,GAAG,CAAC;IACzD,CAAC;GACH,GACA,IACF;GACA,IAAI,kBAAkB,iBAAiB;IAErC,MAAM,CAAC,SAAS,OAAM,MADA,0BAA0B,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,EAAA,CACxC,KAAK;KACjC,OAAO;MAAE,GAAG,KAAK;MAAO,IAAI,OAAO;KAAG;KACtC,OAAO;IACT,CAAC;IACD,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yCAAyC;IACrE,OAAO;GACT;GACA,IAAI,kBAAkB,mBAAmB;IAIvC,MAAM,CAAC,SAAS,OAAM,MAHC,4BAA4B,OAAO,EACxD,IAAI,KAAK,GACX,CAAC,EAAA,CAC8B,KAAK;KAClC,OAAO;MAAE,UAAU,KAAK,MAAM;MAAU,IAAI,OAAO;KAAG;KACtD,OAAO;IACT,CAAC;IACD,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,2CAA2C;IAC7D,OAAO;GACT;GACA,OAAO;EACT,CAAC;CACH;CAEQ,KAAK,IAAgB;EAC3B,IAAI,EAAE,cAAc,SAAS,CAAC,OAAO,SAAS,GAAG,QAAQ,CAAC,GACxD,MAAM,IAAI,mBACR,4CACF;EACF,OAAO;CACT;CAEA,MAAc,KAAK,IAAuC;EACxD,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;GACpC,OAAO;IAAE,GAAG,KAAK;IAAO,SAAS;GAAK;GACtC,OAAO;EACT,CAAC;EACD,IAAI,CAAC,OAAO,MAAM,IAAI,mBAAmB,kBAAkB,eAAe;EAC1E,OAAO;CACT;CAEA,MAAc,cAAc,IAAa,IAAyB;EAChE,MAAM,OAAO,MAAM,GAAG,QAAQ,KAAK;GACjC,OAAO;IAAE,GAAG,KAAK;IAAO,SAAS;GAAK;GACtC,OAAO;EACT,CAAC;EACD,MAAM,OAAO,KAAK,SACd,OACA,MAAM,GAAG,QAAQ,KAAK;GACpB,OAAO,EAAE,GAAG,KAAK,MAAM;GACvB,SAAS;GACT,OAAO;EACT,CAAC;EACL,IAAI,CAAC,KAAK,QAAQ;EAClB,MAAM,QAAQ,KAAK;EACnB,MAAM,SAAS,MAAM,GAAG,OAAO,KAAK,EAClC,OAAO;GAAE,UAAU,KAAK,MAAM;GAAU,SAAS,MAAM;EAAG,EAC5D,CAAC;EACD,MAAM,WAAW,MAAM,GAAG,SAAS,KAAK,EACtC,OAAO;GAAE,UAAU,KAAK,MAAM;GAAU,SAAS,MAAM;EAAG,EAC5D,CAAC;EACD,MAAM,SAAS,KAAK,IAClB,MAAM,UAAU,QAAQ,GACxB,GAAG,SAAS,SAAS,MAAM,CACzB,EAAE,UAAU,QAAQ,GACpB,EAAE,SAAS,QAAQ,KAAK,CAC1B,CAAC,GACD,MAAM,SAAS,QAAQ,KAAK,GAC5B,GAAG,OAAO,SAAS,MAAM,CACvB,EAAE,UAAU,QAAQ,GACpB,EAAE,SAAS,QAAQ,KAAK,CAC1B,CAAC,CACH;EACA,IAAI,GAAG,QAAQ,IAAI,QACjB,MAAM,IAAI,mBACR,8DACA,oBACF;CACJ;;CAGA,MAAM,QACJ,qBAAK,IAAI,KAAK,GACd,SAC0B;EAC1B,MAAM,OAAO,qBAAqB,OAAO;EACzC,OAAO,KAAK,UAAU,OACpB,KAAK,WAAW,IAAI,WAAW,KAAK,KAAK,EAAE,GAAG,OAAO,KAAA,GAAW,OAAO;GACrE,GAAG;GACH,MAAM;EACR,CAAC,CACH;CACF;;CAEA,MAAM,SAAS,IAAU,MAAiD;EACxE,OAAO,KAAK,UAAU,OACpB,KAAK,WAAW,IAAI,YAAY,KAAK,KAAK,EAAE,GAAG,OAAO,IAAI,CAC5D;CACF;;CAEA,MAAM,WACJ,IACA,OAAO,OACP,SAC0B;EAC1B,MAAM,OAAO,gBAAgB,SAAS,MAAM,MAAM;EAClD,OAAO,KAAK,UAAU,OACpB,KAAK,WAAW,IAAI,cAAc,KAAK,KAAK,EAAE,GAAG,MAAM,KAAA,GAAW,OAAO;GACvE,GAAG,qBAAqB;GACxB;EACF,CAAC,CACH;CACF;;;;;CAKA,MAAM,SACJ,IACA,SAC0B;EAC1B,OAAO,KAAK,UAAU,OACpB,KAAK,WACH,IACA,YACA,KAAK,KAAK,EAAE,GACZ,OACA,KAAA,GACA,SAAS,UACX,CACF;CACF;;CAGA,MAAM,UAAU,IAAU,MAAkD;EAC1E,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,uBAAuB,IAAI;GAC3B,MAAM,KAAK,cAAc,IAAI,KAAK,KAAK,EAAE,CAAC;GAC1C,OAAO,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,IAAI,IAAI;EACvD,CAAC;CACH;;CAEA,MAAM,WAAW,IAAU,MAAkD;EAC3E,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,uBAAuB,IAAI;GAC3B,MAAM,KAAK,cAAc,IAAI,KAAK,KAAK,EAAE,CAAC;GAC1C,OAAO,eAAe,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,IAAI,IAAI;EACzD,CAAC;CACH;;CAEA,MAAM,SAAS,IAAsC;EACnD,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,KAAK,cAAc,IAAI,KAAK,KAAK,EAAE,CAAC;GAC1C,OAAO,WAAW,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,EAAE;EAC/C,CAAC;CACH;;CAEA,MAAM,WAAW,IAAsC;EACrD,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,KAAK,cAAc,IAAI,KAAK,KAAK,EAAE,CAAC;GAC1C,OAAO,gBAAgB,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,EAAE;EACpD,CAAC;CACH;;CAEA,MAAM,cAAiD;EACrD,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;IACpC,OAAO;KAAE,GAAG,KAAK;KAAO,SAAS;IAAK;IACtC,OAAO;GACT,CAAC;GACD,OAAO,QAAQ,YAAY,IAAI,KAAK,IAAI;EAC1C,CAAC;CACH;;CAEA,MAAM,SAAS,SAA+C;EAC5D,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;IACpC,OAAO;KAAE,GAAG,KAAK;KAAO,IAAI;IAAQ;IACpC,OAAO;GACT,CAAC;GACD,IAAI,CAAC,OAAO,MAAM,IAAI,mBAAmB,4BAA4B;GACrE,OAAO,aAAa,IAAI,KAAK;EAC/B,CAAC;CACH;CAEA,MAAc,WACZ,IACA,QACA,IACA,OAAO,OACP,MACA,aAAa,OACb,OAAmE;EACjE,GAAG,qBAAqB;EACxB,MAAM;CACR,GAC0B;EAC1B,MAAM,KAAK,cAAc,IAAI,EAAE;EAC/B,IAAI,WAAW,WAAW;GACxB,KAEI,MAAM,GAAG,QAAQ,KAAK;IACpB,OAAO;KAAE,GAAG,KAAK;KAAO,SAAS;IAAK;IACtC,OAAO;GACT,CAAC,EAAA,CACD,QAEF,MAAM,IAAI,mBAAmB,4BAA4B,YAAY;GACvE,OAAO,GAAG,QAAQ,OAAO;IACvB,GAAG,KAAK;IACR,WAAW;IACX,QAAQ,KAAK;IACb,eAAe,KAAK;IACpB,aAAa,KAAK;IAClB,aAAa;GACf,CAAC;EACH;EACA,MAAM,QAAQ,MAAM,KAAK,KAAK,EAAE;EAChC,MAAM,SAAS,MAAM,GAAG,OAAO,KAAK,EAClC,OAAO;GAAE,UAAU,KAAK,MAAM;GAAU,SAAS,MAAM;EAAG,EAC5D,CAAC;EACD,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC,EAAE,OAAO;EAC5C,IAAI,WAAW,cAAc;GAC3B,IAAI,QACF,MAAM,IAAI,mBAAmB,4BAA4B,YAAY;GACvE,IAAI,OAAO,SAAS,WAClB,MAAM,IAAI,mBAAmB,uBAAuB;GACtD,MAAM,iBAAiB,IAAI,OAAO,IAAI,OAAO;GAC7C,MAAM,GAAG,OAAO,OAAO;IACrB,UAAU,KAAK,MAAM;IACrB,SAAS,MAAM,MAAM,KAAA;IACrB,WAAW;IACX;IACA,MAAM,KAAK;IACX,aAAa;GACf,CAAC;GACD,OAAO;EACT;EACA,IAAI,WAAW,cAAc,WAAW,YACtC,MAAM,IAAI,mBAAmB,4BAA4B;EAC3D,IAAI,WAAW,cAAc,CAAC,QAC5B,MAAM,IAAI,mBAAmB,kBAAkB,eAAe;EAChE,IAAI,WAAW,cAAc,MAAM;GACjC,KAAK,aAAa,IAAI;GACtB,MAAMA,sBAAqB,OACxB,QAAQ,MAAM,CAAC,EAAE,IAAI,CAAA,CACrB,QACE,KAAK,MAAM,OAAO,EAAE,WAAW,GAAA,CAAI,QAAQ,IAAI,EAAE,UAAU,QAAQ,GACpE,CACF;GACF,IACE,KAAK,OACF,GAAG,QAAQ,IAAI,MAAM,UAAU,QAAQ,IAAIA,uBAC1C,GACJ,KAAK,GAEL,MAAM,IAAI,mBACR,0EACF;EACJ;EACA,IAAI,QAAQ;GACV,OAAO,UAAU;GACjB,MAAM,OAAO,KAAK;EACpB;EACA,IAAI,WAAW,YAAY;GACzB,IAAI,YAAY,MAAM,gBAAgB,IAAI,OAAO,EAAE;GACnD,OAAO;EACT;EACA,MAAM,iBAAiB,IAAI,OAAO,IAAI,UAAU;EAChD,MAAM,UAAU;EAChB,MAAM,qBAAqB,OACxB,QAAQ,MAAM,CAAC,EAAE,IAAI,CAAA,CACrB,QACE,KAAK,MAAM,MAAM,EAAE,QAAS,QAAQ,IAAI,EAAE,UAAU,QAAQ,GAC7D,CACF;EACF,MAAM,qBAAqB,KAAK,MAAM,qBAAqB,GAAI;EAC/D,MAAM,kBAAkB,KAAK,OAC1B,GAAG,QAAQ,IAAI,MAAM,UAAU,QAAQ,IAAI,sBAAsB,GACpE;EACA,MAAM,MAAM,KAAK;EACjB,IAAI,MAAM,MAAM,KAAK,KAAK,IAAI,OAAO,IAAI;EACzC,OAAO;CACT;;CAGA,MAAc,MACZ,IACA,KACA,IACkE;EAClE,MAAM,aAA6B;GACjC,IAAI,CAAC,IAAI,MACP,MAAM,IAAI,mBAAmB,qBAAqB,SAAS;GAC7D,OAAO,IAAI;EACb;EACA,IAAI;EACJ,QAAQ,IAAI,QAAZ;GACE,KAAK;IACH,MAAM,KAAK,cAAc,IAAI,EAAE;IAC/B,UAAU,MAAM,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,IAAI,KAAK,CAAC;IAChE;GACF,KAAK;IACH,MAAM,KAAK,cAAc,IAAI,EAAE;IAC/B,UAAU,MAAM,eAAe,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,IAAI,KAAK,CAAC;IAClE;GACF,KAAK;IACH,MAAM,KAAK,cAAc,IAAI,EAAE;IAC/B,UAAU,MAAM,WAAW,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,EAAE;IACtD;GACF,KAAK;IACH,MAAM,KAAK,cAAc,IAAI,EAAE;IAC/B,UAAU,MAAM,gBAAgB,IAAI,MAAM,KAAK,KAAK,EAAE,GAAG,EAAE;IAC3D;GACF,SACE,OAAO,EACL,OAAO,MAAM,KAAK,WAChB,IACA,IAAI,QACJ,IACA,IAAI,MACJ,IAAI,MACJ,OACA;IACE,GAAG,qBAAqB;KACtB,QAAQ,IAAI;KACZ,QAAQ,IAAI;IACd,CAAC;IACD,MAAM,gBAAgB,IAAI,MAAM,MAAM;GACxC,CACF,EACF;EACJ;EACA,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;GACpC,OAAO;IAAE,GAAG,KAAK;IAAO,IAAI,QAAQ;GAAQ;GAC5C,OAAO;EACT,CAAC;EACD,OAAO;GAAE;GAAO;EAAQ;CAC1B;CAEQ,aAAa,MAA4B;EAC/C,uBAAuB,IAAI;CAC7B;CAEA,MAAc,KACZ,IACA,OACA,MACe;EACf,KAAK,aAAa,IAAI;EACtB,IAAI,CAAC,MAAM,WAAW,MAAM,mBAAmB,GAC7C,MAAM,IAAI,mBACR,0EACF;EACF,MAAM,UAAU,MAAM,2BAA2B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC;EACrE,IAAI,MAAM,oBAAoB;GAC5B,MAAM,CAAC,YAAY,MAAM,QAAQ,KAAK;IACpC,OAAO;KAAE,IAAI,MAAM;KAAoB,UAAU,KAAK,MAAM;IAAS;IACrE,OAAO;GACT,CAAC;GACD,IACE,CAAC,YACD,SAAS,gBAAgB,KAAK,eAC9B,SAAS,cAAc,KAAK,aAC5B,SAAS,gBAAgB,KAAK,eAC9B,CAAC,gBAAgB,SAAS,UAAU,IAAI,GAExC,MAAM,IAAI,mBAAmB,yCAAyC;GACxE;EACF;EACA,MAAM,QAAQ,MAAM,QAAQ,OAAO;GACjC,UAAU,KAAK,MAAM;GACrB,iBAAiB;GACjB,sBAAsB,KAAK,MAAM;GACjC,aAAa,KAAK;GAClB,WAAW,KAAK;GAChB,aAAa,KAAK;GAClB,QAAQ;GACR,WAAW,MAAM;GACjB,SAAS,MAAM;GACf,iBAAiB,MAAM;GACvB,QAAQ;GACR,UAAU,KAAK,UAAU;IACvB,mBAAmB,MAAM;IACzB,GAAI,KAAK,cACL;KAAE,aAAa,KAAK;KAAa,WAAW,KAAK;IAAU,IAC3D,CAAC;IACL,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;GACzC,CAAC;GACD,aAAa;EACf,CAAC;EACD,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,gCAAgC;EAC/D,MAAM,qBAAqB,MAAM;EACjC,MAAM,MAAM,KAAK;CACnB;;CAGA,MAAM,qBACJ,SACA,MAC0B;EAC1B,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;IACpC,OAAO;KAAE,GAAG,KAAK;KAAO,IAAI;IAAQ;IACpC,OAAO;GACT,CAAC;GACD,IAAI,CAAC,OAAO,MAAM,IAAI,mBAAmB,4BAA4B;GACrE,MAAM,KAAK,KAAK,IAAI,OAAO,IAAI;GAC/B,OAAO;EACT,CAAC;CACH;;;;;;;;;;CAWA,MAAM,UACJ,KACA,UACiC;EACjC,KAAK,KAAK,GAAG;EACb,IACE,OAAO,aAAa,eACnB,CAAC,OAAO,cAAc,QAAQ,KAAK,YAAY,IAEhD,MAAM,IAAI,mBACR,4CACF;EACF,OAAO,KAAK,SAAS,OAAO,OAAO;GACjC,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;IACpC,OAAO;KAAE,GAAG,KAAK;KAAO,SAAS;IAAK;IACtC,OAAO;GACT,CAAC;GACD,IAAI,CAAC,OAAO,OAAO;GACnB,IAAI;GACJ,IAAI,SAAS;GACb,IAAI,OAAO,aAAa,YAAY;IAClC,MAAM,WAAW,MAAM,SAAS,KAAK;IACrC,IAAI,aAAa,QAAQ,aAAa,KAAA,GAAW,OAAO;IACxD,MAAM,WACJ,oBAAoB,OAAO,EAAE,UAAU,SAAS,IAAI;IACtD,KAAK,SAAS;IACd,IAAI,EAAE,cAAc,SAAS,CAAC,OAAO,SAAS,GAAG,QAAQ,CAAC,GACxD,MAAM,IAAI,mBAAmB,iCAAiC;IAChE,IAAI,KAAK,MAAM,WACb,MAAM,IAAI,mBACR,yCACF;IACF,SAAS,SAAS,UAAU;IAC5B,IAAI,OAAO,WAAW,YAAY,CAAC,OAAO,KAAK,KAAK,OAAO,SAAS,KAClE,MAAM,IAAI,mBACR,gDACF;GACJ,OACE,KAAK,IAAI,KAAK,MAAM,UAAU,QAAQ,IAAI,WAAW,GAAI;GAE3D,IAAI,KAAK,KAAK,OAAO;GAErB,MAAM,SAAS,MAAM,KAAK,WAAW,IAAI,YAAY,EAAE;GACvD,OAAO,iBAAiB;GACxB,OAAO,kBAAkB;GACzB,MAAM,iBAAiB,MAAM;GAC7B,OAAO;EACT,CAAC;CACH;;;;;;;CAQA,MAAM,OACJ,MACA,UAAmC,CAAC,GACA;EACpC,KAAA,MAAW,OAAO,MAAM;GACtB,IACE,CAAC,OACD,OAAO,IAAI,OAAO,YAClB,OAAO,IAAI,aAAa,YACxB,CAAC,IAAI,YACL,CAAC,YAAY,SAAS,IAAI,MAAM,GAEhC,MAAM,IAAI,mBAAmB,2BAA2B;GAC1D,KAAK,KAAK,IAAI,KAAK,IAAI,EAAE,CAAC;GAC1B,IAAI,IAAI,SAAS,KAAA,GAAW,KAAK,aAAa,IAAI,IAAI;GACtD,KACG,IAAI,WAAW,eAAe,IAAI,WAAW,iBAC9C,IAAI,SAAS,KAAA,GAEb,MAAM,IAAI,mBAAmB,2BAA2B;GAC1D,IAAI,IAAI,SAAS,KAAA,KAAa,OAAO,IAAI,SAAS,WAChD,MAAM,IAAI,mBAAmB,uBAAuB;GACtD,qBAAqB;IAAE,QAAQ,IAAI;IAAQ,QAAQ,IAAI;GAAO,CAAC;GAC/D,gBAAgB,IAAI,MAAM,MAAM;EAClC;EACA,MAAM,UAAU,CAAC,GAAG,IAAI,CAAA,CAAE,MACvB,GAAG,MACF,KAAK,MAAM,EAAE,EAAE,IAAI,KAAK,MAAM,EAAE,EAAE,MACjC,EAAE,WAAW,EAAE,WAAW,KAAK,EAAE,WAAW,EAAE,WAAW,IAAI,EAClE;EACA,MAAM,WAAsC,CAAC;EAC7C,KAAA,MAAW,OAAO,SAChB,SAAS,KACP,MAAM,KAAK,SAAS,OAAO,OAAO;GAChC,MAAM,YAAY,KAAK,UAAU;IAC/B,KAAK,MAAM;IACX,KAAK,MAAM;IACX,IAAI;GACN,CAAC;GACD,MAAM,UAAU,KAAK,UAAU;IAC7B,IAAI;IACJ,IAAI,KAAK,IAAI,EAAE,CAAA,CAAE,YAAY;IAC7B,IAAI,QAAQ;IACZ,IAAI,MAAM,eAAe;IACzB,IAAI,MAAM,aAAa;IACvB,IAAI,MAAM,eAAe;IACzB,GAAI,IAAI,MAAM,eAAe,IAAI,MAAM,OACnC;KACE,IAAI,KAAK,eAAe;KACxB,IAAI,KAAK,aAAa;KACtB,IAAI,KAAK,QAAQ;IACnB,IACA,CAAC;IAGL,GAAI,IAAI,UAAU,IAAI,UAAU,IAAI,OAChC,CACE;KACE,QAAQ,IAAI,UAAU;KACtB,YAAY,IAAI,QAAQ,QAAQ;KAChC,UAAU,IAAI,QAAQ,MAAM;KAC5B,MAAM,IAAI,QAAQ;IACpB,CACF,IACA,CAAC;GACP,CAAC;GACD,MAAM,CAAC,WAAW,MAAM,GAAG,QAAQ,KAAK;IACtC,OAAO;KAAE,UAAU,KAAK,MAAM;KAAU;IAAU;IAClD,OAAO;GACT,CAAC;GACD,IAAI,SAAS;IACX,IAAI,QAAQ,YAAY,SACtB,MAAM,IAAI,mBACR,0CACF;IACF,MAAMC,WAAU,QAAQ,WAAW;IACnC,IAAIA,SAAQ,aAAa,IAAI,UAC3B,MAAM,IAAI,MACR,yDACF;IACF,OAAOA;GACT;GACA,MAAM,WAAW,IAAI,KAAK,IAAI,EAAE;GAChC,IAAI,KAAK;GACT,IAAI,SAAS;GACb,IAAI,QAAQ,WAAW;IACrB,MAAM,YAAY,KAAK,KAAK,QAAQ,MAAM,qBAAK,IAAI,KAAK,CAAC;IACzD,MAAM,WAAW,MAAM,QAAQ,UAAU;KACvC;KACA;KACA;KACA,UAAU,UAAU,QAAQ,IAAI,SAAS,QAAQ;KACjD,OAAO,EAAE,GAAG,KAAK,MAAM;IACzB,CAAC;IACD,IACE,CAAC,YACD,EAAE,SAAS,cAAc,SACzB,CAAC,OAAO,SAAS,SAAS,GAAG,QAAQ,CAAC,GAEtC,MAAM,IAAI,MAAM,qCAAqC;IACvD,KAAK,SAAS;IACd,SAAS,SAAS,mBAAmB;GACvC;GACA,IAAI;GACJ,IAAI;IAIF,UAAU,MAAM,6BACd,GAAG,IACH,mBAAmB,GAAG,EAAE,GACxB,OAAO,OAAO;KACZ,MAAM,QAAQ;MACZ;MACA,SAAS,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;MACtD,QAAQ,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;MACrD,UAAU,MAAM,4BAA4B,OAAO,EAAE,GAAG,CAAC;MACzD,SAAS,GAAG;KACd;KACA,MAAM,EAAE,OAAO,YAAY,MAAM,KAAK,MAAM,OAAO,KAAK,EAAE;KAC1D,MAAM,kBAAkB;KACxB,MAAM,iBAAiB;KACvB,MAAM,MAAM,KAAK;KACjB,IAAI,WAAW,QAAQ;MACrB,QAAQ,iBAAiB;MACzB,MAAM,QAAQ,KAAK;KACrB;KACA,OAAO;MACL,UAAU,IAAI;MACd,SAAS,MAAM;MACf,GAAI,UAAU,EAAE,WAAW,QAAQ,GAAI,IAAI,CAAC;MAC5C,GAAI,GAAG,QAAQ,MAAM,SAAS,QAAQ,IAClC,EAAE,YAAY,GAAG,YAAY,EAAE,IAC/B,CAAC;KACP;IACF,GACA,IACF;GACF,SAAS,OAAO;IACd,IAAI,EAAE,iBAAiB,qBAAqB,MAAM;IAClD,MAAM,SAAS,QAAQ,UAAU,KAAK,KAAK,KAAK,KAAA;IAChD,UAAU;KACR,UAAU,IAAI;KACd,OAAO,QAAQ,WAAW,MAAM;KAChC,MAAM,QAAQ,QAAQ,MAAM;IAC9B;GACF;GACA,MAAM,GAAG,QAAQ,OAAO;IACtB,UAAU,KAAK,MAAM;IACrB;IACA;IACA,SAAS,KAAK,UAAU,OAAO;IAC/B,aAAa;GACf,CAAC;GACD,OAAO;EACT,CAAC,CACH;EACF,OAAO;CACT;AACF;;;ACzwBA,IAAM,OAAO,MAAoB,IAAI,EAAE,YAAY,IAAI;AACvD,IAAM,cAAc,OAAwB;CAC1C,WAAW,IAAI,EAAE,SAAS;CAC1B,SAAS,IAAI,EAAE,OAAO;CACtB,iBAAiB,EAAE;CACnB,oBAAoB,EAAE;CACtB,gBAAgB,EAAE;CAClB,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;CACvC,GAAI,EAAE,gBACF;EAAE,eAAe,EAAE;EAAe,aAAa,EAAE;CAAY,IAC7D,CAAC;AACP;AACA,IAAM,cAAc,OAAwB;CAC1C,WAAW,IAAI,EAAE,SAAS;CAC1B,SAAS,IAAI,EAAE,OAAO;CACtB,MAAM,EAAE;CACR,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AACnC;AACA,IAAM,gBAAgB,OAA0B;CAC9C,WAAW,IAAI,EAAE,SAAS;CAC1B,SAAS,IAAI,EAAE,OAAO;CACtB,aAAa,EAAE;CACf,WAAW,EAAE;CACb,aAAa,EAAE;CACf,WAAW,EAAE;CACb,MAAM,EAAE;CACR,aAAa,EAAE;CACf,iBAAiB,EAAE;CACnB,cAAc,EAAE;AAClB;AAEA,SAAS,QAAQ,OAAyB,MAAoB;CAC5D,IAAI,EAAE,iBAAiB,SAAS,CAAC,OAAO,SAAS,MAAM,QAAQ,CAAC,GAC9D,MAAM,IAAI,mBAAmB,GAAG,KAAI,uBAAwB;CAC9D,OAAO;AACT;AAEA,SAAS,YACP,GACA,GACS;CACT,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;AACxC;AAEA,SAAS,eAAe,QAAsB;CAC5C,MAAM,UAAU,MACd,EAAE,QAAQ,QAAQ,KAAK,EAAE,UAAU,QAAQ,KAC3C,EAAE,UAAU,QAAQ,KAAK,OAAO,YAChC,EAAE,QAAQ,QAAQ,KAAK,OAAO;CAChC,IAAI,CAAC,OAAO,OAAO,MAAM,MAAM,GAC7B,MAAM,IAAI,mBACR,+CACF;CACF,IAAI,CAAC,OAAO,SAAS,MAAM,MAAM,GAC/B,MAAM,IAAI,mBACR,sDACF;CACF,MAAM,QAAQ,OAA2C;EACvD,OAAO,EAAE,UAAU,QAAQ;EAC3B,KAAK,EAAE,QAAQ,QAAQ;CACzB;CACA,MAAM,OAAO,CAAC,OAAO,QAAQ,OAAO,QAAQ;CAC5C,KAAA,MAAW,OAAO,MAChB,KAAA,IAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAC9B,KAAA,IAAS,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAClC,IAAI,YAAY,KAAK,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,CAAC,GACxC,MAAM,IAAI,mBACR,QAAQ,OAAO,SACX,6BACA,iCACN;CACR,KAAA,MAAW,KAAK,OAAO,QACrB,KAAA,MAAW,KAAK,OAAO,UACrB,IAAI,YAAY,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAC9B,MAAM,IAAI,mBACR,0CACF;AACR;AAiBO,IAAM,8BAAN,MAAkC;CAEvC,YACmB,IACjB,OACiB,UAAuC,CAAC,GACzD;EAHiB,KAAA,KAAA;EAEA,KAAA,UAAA;EAEjB,IAAI,CAAC,OAAO,YAAY,CAAC,MAAM,WAC7B,MAAM,IAAI,MAAM,qDAAqD;EACvE,KAAK,QAAQ;GACX,UAAU,mBAAmB,IAAI,MAAM,QAAQ;GAC/C,WAAW,mBAAmB,IAAI,MAAM,SAAS;EACnD;CACF;CAVmB;CAEA;CAJF;;CAejB,MAAM,aAAa,OAAyD;EAC1E,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,SAAS,MAAM,OAAO;GAC/D,MAAM,SAAS,WAAW,KAAK;GAC/B,MAAM,YACJ,MAAM,cAAc,KAAA,IAChB,MAAM,YACN,QAAQ,MAAM,WAAW,WAAW;GAC1C,MAAM,UACJ,MAAM,YAAY,KAAA,IACd,MAAM,UACN,QAAQ,MAAM,SAAS,SAAS;GACtC,IAAI,WAAW,WACb,MAAM,IAAI,mBAAmB,mCAAmC;GAClE,MAAM,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK;GAC9C,eAAe,KAAK,OAAO,WAAW,SAAS,QAAQ,CAAC;GACxD,KAAA,MAAW,SAAS,MAAM,GAAG,QAAQ,KAAK,EACxC,OAAO;IAAE,UAAU,QAAQ;IAAU,WAAW,QAAQ;GAAU,EACpE,CAAC,GACC,IACE,MAAM,OAAO,MAAM,MACnB,YACE;IAAE,OAAO,UAAU,QAAQ;IAAG,KAAK,QAAQ,QAAQ;GAAE,GACrD;IACE,OAAO,MAAM,UAAU,QAAQ;IAC/B,KAAK,MAAM,SAAS,QAAQ,KAAK,OAAO;GAC1C,CACF,GAEA,MAAM,IAAI,mBAAmB,2BAA2B;GAC5D,MAAM,KAAK,MAAM,IAAI,SAAS,CAC5B;IAAE,UAAU,MAAM;IAAW,QAAQ,MAAM;GAAS,GACpD;IAAE,UAAU;IAAW,QAAQ;GAAQ,CACzC,CAAC;GACD,MAAM,QAAQ,MAAM,KAAK,cAAc,IAAI,KAAK;GAChD,MAAM,YAAY;GAClB,MAAM,UAAU;GAChB,MAAM,iBAAiB;GACvB,MAAM,KAAK,OAAO,IAAI,OAAO,SAAS,QAAQ,KAAK;GACnD,OAAO,OACL,mBACA,OACA,MAAM,IACN,QACA,WAAW,KAAK,CAClB;EACF,CAAC;CACH;;CAGA,MAAM,SAAS,OAAqD;EAClE,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,YAAY,QAAQ,MAAM,WAAW,WAAW;GACtD,MAAM,UAAU,QAAQ,MAAM,SAAS,SAAS;GAChD,MAAM,OAAO,qBAAqB,KAAK;GACvC,IAAI,WAAW,WACb,MAAM,IAAI,mBAAmB,mCAAmC;GAClE,KAAA,MAAW,SAAS,MAAM,GAAG,QAAQ,KAAK,EACxC,OAAO;IAAE,UAAU,QAAQ;IAAU,WAAW,QAAQ;GAAU,EACpE,CAAC,GACC,IACE,YACE;IAAE,OAAO,UAAU,QAAQ;IAAG,KAAK,QAAQ,QAAQ;GAAE,GACrD;IACE,OAAO,MAAM,UAAU,QAAQ;IAC/B,KAAK,MAAM,SAAS,QAAQ,KAAK,OAAO;GAC1C,CACF,GAEA,MAAM,IAAI,mBAAmB,2BAA2B;GAC5D,MAAM,KAAK,MAAM,IAAI,SAAS,CAAC;IAAE,UAAU;IAAW,QAAQ;GAAQ,CAAC,CAAC;GACxE,MAAM,QAAQ,MAAM,GAAG,QAAQ,OAAO;IACpC,UAAU,QAAQ;IAClB,WAAW,QAAQ;IACnB;IACA;IACA,iBAAiB,KAAK,OACnB,QAAQ,QAAQ,IAAI,UAAU,QAAQ,KAAK,GAC9C;IACA,GAAG;IACH,aAAa;GACf,CAAC;GACD,OAAO,OAAO,eAAe,OAAO,MAAM,IAAK,MAAM,WAAW,KAAK,CAAC;EACxE,CAAC;CACH;;CAGA,MAAM,UAAU,OAAsD;EACpE,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,SAAS,MAAM,OAAO;GAC/D,MAAM,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK;GAC9C,MAAM,WAAW,MAAM,UACnB,SAAS,OAAO,MAAM,MAAM,EAAE,OAAO,MAAM,OAAO,IAClD,KAAA;GACJ,IAAI,MAAM,WAAW,CAAC,UACpB,MAAM,IAAI,mBAAmB,gCAAgC;GAC/D,IACE,CAAC,aACA,MAAM,cAAc,KAAA,KAAa,MAAM,YAAY,KAAA,IAEpD,MAAM,IAAI,mBACR,0CACF;GACF,IAAI,MAAM,SAAS,KAAA,KAAa,OAAO,MAAM,SAAS,WACpD,MAAM,IAAI,mBAAmB,uBAAuB;GACtD,MAAM,OAAO;IACX,WAAW,QAAQ,MAAM,aAAa,UAAU,WAAW,WAAW;IACtE,SAAS,QACP,MAAM,WAAW,UAAU,WAAW,KAAA,GACtC,SACF;IACA,MAAM,MAAM,QAAQ,UAAU,QAAQ;IACtC,MACE,MAAM,SAAS,KAAA,IACV,UAAU,QAAQ,KACnB,gBAAgB,MAAM,MAAM,MAAM;GAC1C;GACA,MAAM,OAAO,SAAS,OAAO,QAAQ,MAAM,MAAM,QAAQ;GACzD,eACE,KAAK,OAAO,MAAM,WAAW,MAAM,SAAU;IAC3C,QAAQ,CAAC,GAAG,MAAM,IAAkC;IACpD,UAAU,SAAS;GACrB,CAAC,CACH;GACA,MAAM,KAAK,MAAM,IAAI,SAAS,CAC5B;IAAE,UAAU,MAAM;IAAW,QAAQ,MAAM;GAAS,CACtD,CAAC;GACD,MAAM,QAAQ,MAAM,KAAK,cAAc,IAAI,KAAK;GAChD,MAAM,SAAS,WAAW,WAAW,QAAQ,IAAI;GACjD,IAAI;GACJ,IAAI,UAAU;IACZ,SAAS,YAAY,KAAK;IAC1B,SAAS,UAAU,KAAK;IACxB,SAAS,OAAO,KAAK;IACrB,SAAS,OAAO,KAAK;IACrB,MAAM,kBAAkB,QAAQ;IAChC,SAAS;GACX,OAAO;IACL,SAAS,MAAM,IAAI,gBAAgB;KACjC,IAAI,GAAG;KACP,IAAI,OAAO,WAAW;KACtB,WAAW;IACb,CAAC,CAAA,CAAE,WAAW;IACd,OAAO,OAAO,QAAQ;KACpB,UAAU,QAAQ;KAClB,SAAS,MAAM,MAAM,KAAA;KACrB,GAAG;IACL,CAAC;IACD,MAAM,kBAAkB,MAAM;GAChC;GACA,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG,KAAK;GACrD,OAAO,OACL,WAAW,kBAAkB,eAC7B,OACA,OAAO,IACP,QACA,WAAW,MAAM,CACnB;EACF,CAAC;CACH;;CAGA,MAAM,YAAY,OAAwD;EACxE,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,CAAC,UAAU,MAAM,GAAG,OAAO,KAAK;IACpC,OAAO;KAAE,UAAU,QAAQ;KAAU,IAAI,MAAM;IAAQ;IACvD,OAAO;GACT,CAAC;GACD,IAAI,CAAC,QAAQ,MAAM,IAAI,mBAAmB,kBAAkB;GAC5D,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,SAAS,OAAO,OAAO;GAChE,MAAM,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK;GAC9C,MAAM,KAAK,MAAM,IAAI,SAAS,CAC5B;IAAE,UAAU,MAAM;IAAW,QAAQ,MAAM;GAAS,CACtD,CAAC;GACD,MAAM,QAAQ,MAAM,KAAK,cAAc,IAAI,KAAK;GAChD,MAAM,SAAS,WAAW,MAAM;GAChC,MAAM,iBAAiB,MAAM;GAC7B,MAAM,KAAK,OACT,IACA,OACA,SAAS,OAAO,QAAQ,MAAM,EAAE,OAAO,OAAO,EAAE,GAChD,KACF;GACA,OAAO,OAAO,iBAAiB,OAAO,OAAO,IAAK,QAAQ,IAAI;EAChE,CAAC;CACH;;CAGA,MAAM,YAAY,OAAwD;EACxE,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,SAAS,MAAM,OAAO;GAC/D,MAAM,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK;GAC9C,MAAM,WAAW,MAAM,YACnB,SAAS,SAAS,MAAM,MAAM,EAAE,OAAO,MAAM,SAAS,IACtD,KAAA;GACJ,IAAI,MAAM,aAAa,CAAC,UACtB,MAAM,IAAI,mBAAmB,uCAAuC;GACtE,IACE,CAAC,aACA,MAAM,cAAc,KAAA,KACnB,MAAM,YAAY,KAAA,KAClB,CAAC,MAAM,OAET,MAAM,IAAI,mBACR,uDACF;GACF,IAAI,MAAM,MAAM,uBAAuB,MAAM,IAAI;GACjD,MAAM,OAAO;IACX,WAAW,QAAQ,MAAM,aAAa,UAAU,WAAW,WAAW;IACtE,SAAS,QACP,MAAM,WAAW,UAAU,WAAW,KAAA,GACtC,SACF;GACF;GACA,MAAM,OAAO,SAAS,SAAS,QAAQ,MAAM,MAAM,QAAQ;GAC3D,eACE,KAAK,OAAO,MAAM,WAAW,MAAM,SAAU;IAC3C,QAAQ,SAAS;IACjB,UAAU,CAAC,GAAG,MAAM,IAAoC;GAC1D,CAAC,CACH;GACA,MAAM,KAAK,MAAM,IAAI,SAAS,CAC5B;IAAE,UAAU,MAAM;IAAW,QAAQ,MAAM;GAAS,CACtD,CAAC;GACD,MAAM,SAAS,WAAW,aAAa,QAAQ,IAAI;GACnD,IAAI;GACJ,IAAI,UAAU;IACZ,SAAS,YAAY,KAAK;IAC1B,SAAS,UAAU,KAAK;IACxB,IAAI,MAAM,MAAM,KAAK,UAAU,UAAU,MAAM,IAAI;IACnD,MAAM,kBAAkB,QAAQ;IAChC,SAAS;GACX,OAAO;IACL,SAAS,MAAM,IAAI,kBAAkB;KACnC,IAAI,GAAG;KACP,IAAI,OAAO,WAAW;KACtB,WAAW;IACb,CAAC,CAAA,CAAE,WAAW;IACd,OAAO,OAAO,QAAQ;KACpB,UAAU,QAAQ;KAClB,SAAS,MAAM,MAAM,KAAA;KACrB,aAAa,MAAM,KAAM;KACzB,WAAW,MAAM,KAAM;KACvB,aAAa,MAAM,KAAM,eAAe;KACxC,WAAW,MAAM,KAAM,aAAa;KACpC,MAAM,MAAM,KAAM,QAAQ;KAC1B,aAAa,MAAM,KAAM;KACzB,GAAG;KACH,cAAc;IAChB,CAAC;IACD,MAAM,kBAAkB,MAAM;GAChC;GACA,OAAO,OACL,WAAW,oBAAoB,iBAC/B,OACA,OAAO,IACP,QACA,aAAa,MAAM,CACrB;EACF,CAAC;CACH;;CAGA,MAAM,cACJ,OAC+B;EAC/B,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI,SAAS,WAAW;GACpD,MAAM,CAAC,UAAU,MAAM,GAAG,SAAS,KAAK;IACtC,OAAO;KAAE,UAAU,QAAQ;KAAU,IAAI,MAAM;IAAU;IACzD,OAAO;GACT,CAAC;GACD,IAAI,CAAC,QAAQ,MAAM,IAAI,mBAAmB,yBAAyB;GACnE,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,SAAS,OAAO,OAAO;GAChE,MAAM,KAAK,MAAM,IAAI,SAAS,CAC5B;IAAE,UAAU,MAAM;IAAW,QAAQ,MAAM;GAAS,CACtD,CAAC;GACD,MAAM,SAAS,aAAa,MAAM;GAClC,MAAM,iBAAiB,MAAM;GAC7B,OAAO,OAAO,mBAAmB,OAAO,OAAO,IAAK,QAAQ,IAAI;EAClE,CAAC;CACH;;;;;CAMA,MAAM,YACJ,cACA,qBAAW,IAAI,KAAK,GACW;EAC/B,QAAQ,IAAI,IAAI;EAChB,MAAM,KAAK,MAAM,KAAK,WAAW,KAAK,MAAM,SAAS,CAAA,CAAE,gBACrD,OAAO,OAAO;GAIZ,MAAM,CAAC,OAAO,OAAM,MAHC,+BAA+B,OAAO,EACzD,IAAI,GAAG,GACT,CAAC,EAAA,CAC0B,KAAK;IAC9B,OAAO;KAAE,UAAU,KAAK,MAAM;KAAU,IAAI;IAAa;IACzD,OAAO;GACT,CAAC;GACD,IAAI,CAAC,KAAK,MAAM,IAAI,mBAAmB,uBAAuB;GAC9D,IAAI,IAAI,qBAAqB,KAAK,MAAM,WACtC,MAAM,IAAI,mBACR,gDACF;GACF,IAAI,CAAC,IAAI,yBACP,MAAM,IAAI,mBACR,mDACF;GACF,IAAI,IAAI,gBACN,MAAM,IAAI,mBAAmB,kCAAkC;GACjE,IAAI,iBAAiB;GACrB,IAAI,0BAA0B,KAAK,MAAM;GACzC,MAAM,kBAAkB,GAAG;GAC3B,OAAO,IAAI;EACb,CACF;EACA,OAAO,KAAK,KAAK,EAAE;CACrB;;CAGA,MAAM,aAAa,SAAkD;EACnE,OAAO,KAAK,WAAW,KAAK,MAAM,SAAS,CAAA,CAAE,gBAC3C,OAAO,OACL,OAAO,MAAM,+BAA+B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC,EAAA,CAAG,KACjE;GACE,OAAO;IAAE,UAAU,KAAK,MAAM;IAAU;GAAQ;GAChD,SAAS;EACX,CACF,CACJ;CACF;;CAGA,MAAM,0BAA2D;EAC/D,OAAO,KAAK,WAAW,KAAK,MAAM,SAAS,CAAA,CAAE,gBAAgB,OAAO,QAEhE,OACE,MAAM,+BAA+B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC,EAAA,CACzD,KAAK;GACL,OAAO;IACL,UAAU,KAAK,MAAM;IACrB,kBAAkB,KAAK,MAAM;GAC/B;GACA,SAAS;EACX,CAAC,EAAA,CACD,QAAQ,QAAQ,IAAI,2BAA2B,CAAC,IAAI,cAAc,CACtE;CACF;CAEQ,WAAW,kBAA6C;EAC9D,OAAO,IAAI,kBAAkB,KAAK,IAAI;GACpC,UAAU,KAAK,MAAM;GACrB,WAAW;EACb,CAAC;CACH;CAEA,MAAc,KAAK,IAA2C;EAC5D,OAAO,KAAK,WAAW,KAAK,MAAM,SAAS,CAAA,CAAE,gBAAgB,OAAO,OAAO;GACzE,MAAM,CAAC,OAAO,OACZ,MAAM,+BAA+B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC,EAAA,CACzD,KAAK;IAAE,OAAO;KAAE,UAAU,KAAK,MAAM;KAAU;IAAG;IAAG,OAAO;GAAE,CAAC;GACjE,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,8CAA8C;GACxE,OAAO;EACT,CAAC;CACH;CAEA,MAAc,IACZ,SACA,WAW+B;EAC/B,MAAM,SACJ,OAAO,QAAQ,WAAW,WAAW,QAAQ,OAAO,KAAK,IAAI;EAC/D,IAAI,CAAC,QAAQ,MAAM,IAAI,mBAAmB,8BAA8B;EACxE,IACE,QAAQ,4BAA4B,KAAA,KACpC,OAAO,QAAQ,4BAA4B,WAE3C,MAAM,IAAI,mBAAmB,0CAA0C;EACzE,IAAI,CAAC,QAAQ,kBACX,MAAM,IAAI,mBAAmB,+BAA+B;EAC9D,MAAM,UAA2B;GAC/B,UAAU,KAAK,MAAM;GACrB,WAAW,mBAAmB,KAAK,IAAI,QAAQ,gBAAgB;EACjE;EACA,MAAM,KAAK,MAAM,KAAK,WAAW,QAAQ,SAAS,CAAA,CAAE,gBAClD,OAAO,OAAO;GAEZ,MAAM,+BAA+B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC;GACzD,OAAO,UACL,IACA,SACA,OAAO,MAAM,OAAO,UAAU,QAAQ,UAAU;IAC9C,MAAM,MAAM,MAAM,IAAI,qBAAqB;KACzC,IAAI,GAAG;KACP,IAAI,OAAO,WAAW;KACtB,WAAW;IACb,CAAC,CAAA,CAAE,WAAW;IACd,OAAO,OAAO,KAAK;KACjB,UAAU,QAAQ;KAClB,kBAAkB,QAAQ;KAC1B,gBAAgB,KAAK,MAAM;KAC3B;KACA,SAAS,MAAM,MAAM,KAAA;KACrB;KACA,aAAa,KAAK,UAAU,MAAM;KAClC,YAAY,KAAK,UAAU,KAAK;KAChC;KACA,yBAAyB,QAAQ,2BAA2B;IAC9D,CAAC;IACD,MAAM,kBAAkB,GAAG;IAC3B,OAAO,IAAI;GACb,CACF;EACF,CACF;EACA,OAAO,KAAK,KAAK,EAAE;CACrB;CAEA,MAAc,YACZ,IACA,SACA,SAC0B;EAC1B,MAAM,CAAC,SAAS,MAAM,GAAG,QAAQ,KAAK;GACpC,OAAO;IACL,UAAU,QAAQ;IAClB,WAAW,QAAQ;IACnB,IAAI;GACN;GACA,OAAO;EACT,CAAC;EACD,IAAI,CAAC,OAAO,MAAM,IAAI,mBAAmB,8BAA8B;EACvE,IAAI,CAAC,MAAM,SACT,MAAM,IAAI,mBACR,2EACF;EACF,OAAO;CACT;CAEA,MAAc,SAAS,IAAuB,OAAwB;EACpE,MAAM,QAAQ;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;EAAG;EAC5D,MAAM,SAAS,MAAM,GAAG,OAAO,KAAK;GAAE;GAAO,SAAS;EAAiB,CAAC;EACxE,MAAM,WAAW,MAAM,GAAG,SAAS,KAAK;GACtC;GACA,SAAS;EACX,CAAC;EACD,IAAI,OAAO,MAAM,MAAM,CAAC,EAAE,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC,EAAE,OAAO,GACnE,MAAM,IAAI,mBACR,4CACF;EACF,OAAO;GAAE;GAAQ;EAAS;CAC5B;CAEQ,OACN,WACA,SACA,UAIQ;EACR,OAAO;GACL,UAAU,UAAU,QAAQ;GAC5B,QAAQ,QAAQ,QAAQ;GACxB,QAAQ,SAAS,OAAO,KAAK,OAAO;IAClC,WAAW,EAAE;IACb,SAAS,EAAE;IACX,MAAM,EAAE;GACV,EAAE;GACF,UAAU,SAAS,SAAS,KAAK,OAAO;IACtC,WAAW,EAAE;IACb,SAAS,EAAE;GACb,EAAE;EACJ;CACF;CAEQ,UAAU,SAA4B,MAA4B;EACxE,QAAQ,cAAc,KAAK;EAC3B,QAAQ,YAAY,KAAK;EACzB,QAAQ,cAAc,KAAK,eAAe;EAC1C,QAAQ,YAAY,KAAK,aAAa;EACtC,QAAQ,OAAO,KAAK,QAAQ;EAC5B,QAAQ,cAAc,KAAK;CAC7B;CAEA,MAAc,MACZ,IACA,SACA,WACe;EACf,MAAM,UAAU,MAAM,KAAK,QAAQ,QAAQ,IAAI,SAAS,SAAS;EACjE,IAAI,SAAS,MAAM,IAAI,mBAAmB,OAAO;CACnD;;CAGA,MAAc,cAAc,IAAuB,OAAwB;EACzE,IAAI,CAAC,MAAM,oBAAoB,OAAO;EAEtC,MAAM,CAAC,SAAS,OAAM,MADA,2BAA2B,OAAO,EAAE,IAAI,GAAG,GAAG,CAAC,EAAA,CACvC,KAAK;GACjC,OAAO;IAAE,IAAI,MAAM;IAAoB,UAAU,MAAM;GAAS;GAChE,OAAO;EACT,CAAC;EACD,IAAI,CAAC,OAAO,OAAO;EACnB,IAAI,MAAM,WAAW,SACnB,MAAM,IAAI,mBACR,8BAA8B,MAAM,OAAM,gDAC5C;EACF,OAAO;CACT;;CAGA,MAAc,OACZ,IACA,OACA,QACA,OACe;EACf,MAAM,WAAW,OACd,QAAQ,MAAM,CAAC,EAAE,IAAI,CAAA,CACrB,QACE,KAAK,MAAM,MAAM,EAAE,QAAS,QAAQ,IAAI,EAAE,UAAU,QAAQ,GAC7D,CACF;EACF,MAAM,qBAAqB,KAAK,MAAM,WAAW,GAAI;EACrD,MAAM,kBAAkB,KAAK,OAC1B,MAAM,QAAS,QAAQ,IAAI,MAAM,UAAU,QAAQ,IAAI,YAAY,GACtE;EACA,MAAM,kBAAkB,KAAK;EAC7B,IAAI,OAAO;GACT,MAAM,YAAY,MAAM;GACxB,MAAM,UAAU,MAAM;GACtB,MAAM,kBAAkB,MAAM;GAC9B,MAAM,MAAM,KAAK;EACnB;CACF;AACF"}
@@ -0,0 +1,372 @@
1
+ import { o as parseTimesheetJson } from "./service-time-entry-t7P-bkOQ.js";
2
+ import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isEmbeddedDatabase, isPostgresDatabase, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
3
+ import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
4
+ //#region src/attendance/write.ts
5
+ var permitted = /* @__PURE__ */ new WeakSet();
6
+ function isCorrectionWrite(object) {
7
+ return permitted.has(object);
8
+ }
9
+ async function persistCorrection(object) {
10
+ permitted.add(object);
11
+ try {
12
+ await object.save();
13
+ return object;
14
+ } finally {
15
+ permitted.delete(object);
16
+ }
17
+ }
18
+ async function deleteCorrection(object) {
19
+ permitted.add(object);
20
+ try {
21
+ await object.delete();
22
+ } finally {
23
+ permitted.delete(object);
24
+ }
25
+ }
26
+ var autoClosing = /* @__PURE__ */ new WeakSet();
27
+ function isAutoCloseWrite(object) {
28
+ return autoClosing.has(object);
29
+ }
30
+ async function persistAutoClose(object) {
31
+ autoClosing.add(object);
32
+ try {
33
+ await object.save();
34
+ return object;
35
+ } finally {
36
+ autoClosing.delete(object);
37
+ }
38
+ }
39
+ //#endregion
40
+ //#region src/attendance/models.ts
41
+ var __defProp = Object.defineProperty;
42
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
43
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value
48
+ }) : obj[key] = value;
49
+ var __decorateClass = (decorators, target, key, kind) => {
50
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
51
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
52
+ if (kind && result) __defProp(target, key, result);
53
+ return result;
54
+ };
55
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
56
+ async function saveWithPunchLock(model, punchId, tenant, save) {
57
+ return withEmbeddedWriteTransaction(model.db, isEmbeddedDatabase(model.db), (db) => model.withDatabase(db, async () => {
58
+ if (punchId && isPostgresDatabase(db)) await db.query("SELECT id FROM attendance_punches WHERE id = $1 AND tenant_id = $2 FOR UPDATE", punchId, tenant);
59
+ return save();
60
+ }), true);
61
+ }
62
+ var AttendancePunch = class extends SmrtObject {
63
+ tenantId = "";
64
+ profileId = "";
65
+ startedAt = /* @__PURE__ */ new Date();
66
+ endedAt = null;
67
+ durationSeconds = 0;
68
+ unpaidBreakSeconds = 0;
69
+ reviewRequired = false;
70
+ /** Why the scheduler closed this punch; empty unless `autoClose` did. Write-once. */
71
+ autoCloseReason = "";
72
+ recordedOffline = false;
73
+ /** Optional short consumer-owned capture label (`device`, `web`, `import`, ...); `''` when unknown. */
74
+ source = "";
75
+ /** Qualified type of the device that captured the punch; both device columns or neither. */
76
+ deviceRefType = "";
77
+ deviceRefId = "";
78
+ serviceTimeEntryId = null;
79
+ openSlot = "";
80
+ async save() {
81
+ return saveWithPunchLock(this, this.id, this.tenantId, () => this.saveConsistent());
82
+ }
83
+ async saveConsistent() {
84
+ if (!this.tenantId || !this.profileId) throw new Error("Attendance requires tenantId and profileId.");
85
+ if (!Number.isFinite(this.startedAt?.getTime()) || this.endedAt && (!Number.isFinite(this.endedAt.getTime()) || this.endedAt < this.startedAt)) throw new Error("Invalid attendance interval.");
86
+ if (![this.durationSeconds, this.unpaidBreakSeconds].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new Error("Attendance durations must be nonnegative safe integer seconds.");
87
+ this.source ??= "";
88
+ this.deviceRefType ??= "";
89
+ this.deviceRefId ??= "";
90
+ this.autoCloseReason ??= "";
91
+ if (typeof this.source !== "string" || this.source.length > 64 || this.source !== this.source.trim() || typeof this.deviceRefType !== "string" || this.deviceRefType.length > 64 || this.deviceRefType !== this.deviceRefType.trim() || typeof this.deviceRefId !== "string" || this.deviceRefId.length > 64 || this.deviceRefId !== this.deviceRefId.trim() || !!this.deviceRefType !== !!this.deviceRefId) throw new Error("Invalid attendance punch source or device.");
92
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
93
+ const corrected = isCorrectionWrite(this);
94
+ if (typeof this.autoCloseReason !== "string" || this.autoCloseReason.length > 200) throw new Error("autoCloseReason must be a string of at most 200 characters.");
95
+ if (this.autoCloseReason !== (prior?.auto_close_reason ?? "") && (!isAutoCloseWrite(this) || prior && prior.auto_close_reason)) throw new Error("autoCloseReason is written only by autoClose, once.");
96
+ if (prior && (prior.tenant_id !== this.tenantId || prior.profile_id !== this.profileId || !corrected && (new Date(prior.started_at).getTime() !== this.startedAt.getTime() || (prior.source ?? "") !== this.source || (prior.device_ref_type ?? "") !== this.deviceRefType || (prior.device_ref_id ?? "") !== this.deviceRefId))) throw new Error("Attendance ownership and start are immutable.");
97
+ if (!corrected && prior?.ended_at && (new Date(prior.ended_at).getTime() !== this.endedAt?.getTime() || Number(prior.duration_seconds) !== this.durationSeconds || Number(prior.unpaid_break_seconds) !== this.unpaidBreakSeconds)) throw new Error("Closed attendance evidence is immutable.");
98
+ if (this.id && this.endedAt) {
99
+ if ((await this.db.list("attendance_segments", {
100
+ tenant_id: this.tenantId,
101
+ punch_id: this.id
102
+ })).some((item) => !item.ended_at || new Date(item.started_at).getTime() < this.startedAt.getTime() || new Date(item.ended_at).getTime() > this.endedAt.getTime())) throw new Error("Work segment must be inside its tenant-owned punch.");
103
+ if ((await this.db.list("attendance_breaks", {
104
+ tenant_id: this.tenantId,
105
+ punch_id: this.id
106
+ })).some((item) => !item.ended_at || new Date(item.started_at).getTime() < this.startedAt.getTime() || new Date(item.ended_at).getTime() > this.endedAt.getTime())) throw new Error("Break must be inside its tenant-owned punch.");
107
+ }
108
+ this.id ??= crypto.randomUUID();
109
+ this.openSlot = this.endedAt ? JSON.stringify(["closed", this.id]) : "open";
110
+ return super.save();
111
+ }
112
+ };
113
+ __publicField(AttendancePunch, "__smrtPackage__", "@happyvertical/smrt-timesheets");
114
+ __decorateClass([tenantId()], AttendancePunch.prototype, "tenantId", 2);
115
+ __decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile")], AttendancePunch.prototype, "profileId", 2);
116
+ __decorateClass([foreignKey("ServiceTimeEntry", { nullable: true })], AttendancePunch.prototype, "serviceTimeEntryId", 2);
117
+ __decorateClass([field({ required: true })], AttendancePunch.prototype, "openSlot", 2);
118
+ AttendancePunch = __decorateClass([TenantScoped({ mode: "required" }), smrt({
119
+ tableName: "attendance_punches",
120
+ indexes: [
121
+ {
122
+ name: "attendance_punches_open_owner_key",
123
+ columns: [
124
+ "tenantId",
125
+ "profileId",
126
+ "openSlot"
127
+ ],
128
+ unique: true
129
+ },
130
+ {
131
+ name: "attendance_punches_source_idx",
132
+ columns: ["tenantId", "source"]
133
+ },
134
+ {
135
+ name: "attendance_punches_device_idx",
136
+ columns: [
137
+ "tenantId",
138
+ "deviceRefType",
139
+ "deviceRefId"
140
+ ]
141
+ }
142
+ ],
143
+ api: { include: ["list", "get"] },
144
+ cli: { include: ["list", "get"] },
145
+ mcp: { include: ["list", "get"] }
146
+ })], AttendancePunch);
147
+ var AttendanceBreak = class extends SmrtObject {
148
+ tenantId = "";
149
+ punchId = "";
150
+ startedAt = /* @__PURE__ */ new Date();
151
+ endedAt = null;
152
+ paid = false;
153
+ /** Optional short consumer-owned label (`meal`, `rest`, ...); `''` when unspecified. */
154
+ kind = "";
155
+ openSlot = "";
156
+ async save() {
157
+ return saveWithPunchLock(this, this.punchId, this.tenantId, () => this.saveConsistent());
158
+ }
159
+ async saveConsistent() {
160
+ this.kind ??= "";
161
+ if (!this.tenantId || !this.punchId || typeof this.paid !== "boolean" || typeof this.kind !== "string" || this.kind.length > 64 || this.kind !== this.kind.trim()) throw new Error("Invalid attendance break.");
162
+ if (!Number.isFinite(this.startedAt?.getTime()) || this.endedAt && (!Number.isFinite(this.endedAt.getTime()) || this.endedAt < this.startedAt)) throw new Error("Invalid attendance break interval.");
163
+ const parent = await this.db.get("attendance_punches", {
164
+ id: this.punchId,
165
+ tenant_id: this.tenantId
166
+ });
167
+ if (!parent || this.startedAt.getTime() < new Date(parent.started_at).getTime() || parent.ended_at && (!this.endedAt || this.endedAt.getTime() > new Date(parent.ended_at).getTime())) throw new Error("Break must be inside its tenant-owned punch.");
168
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
169
+ const corrected = isCorrectionWrite(this);
170
+ if (!prior && parent.ended_at && !corrected) throw new Error("A closed punch changes only through AttendanceCorrectionService.");
171
+ if (prior && (prior.tenant_id !== this.tenantId || prior.punch_id !== this.punchId || !corrected && (new Date(prior.started_at).getTime() !== this.startedAt.getTime() || Boolean(prior.paid) !== this.paid || (prior.kind ?? "") !== this.kind))) throw new Error("Break ownership, start and pay policy are immutable.");
172
+ if (!corrected && prior?.ended_at && new Date(prior.ended_at).getTime() !== this.endedAt?.getTime()) throw new Error("Closed breaks are immutable.");
173
+ const brStart = this.startedAt.getTime();
174
+ const brEnd = this.endedAt?.getTime() ?? Number.POSITIVE_INFINITY;
175
+ if ((await this.db.list("attendance_segments", {
176
+ tenant_id: this.tenantId,
177
+ punch_id: this.punchId
178
+ })).some((row) => brStart < (row.ended_at ? new Date(row.ended_at).getTime() : Number.POSITIVE_INFINITY) && brEnd > new Date(row.started_at).getTime())) throw new Error("Break must not overlap a work segment.");
179
+ this.id ??= crypto.randomUUID();
180
+ this.openSlot = this.endedAt ? JSON.stringify(["closed", this.id]) : "open";
181
+ return super.save();
182
+ }
183
+ async delete() {
184
+ await saveWithPunchLock(this, this.punchId, this.tenantId, async () => {
185
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
186
+ if ((this.endedAt || prior?.ended_at) && !isCorrectionWrite(this)) throw new Error("Closed breaks are immutable; remove one through AttendanceCorrectionService.");
187
+ await super.delete();
188
+ return this;
189
+ });
190
+ }
191
+ };
192
+ __publicField(AttendanceBreak, "__smrtPackage__", "@happyvertical/smrt-timesheets");
193
+ __decorateClass([tenantId()], AttendanceBreak.prototype, "tenantId", 2);
194
+ __decorateClass([foreignKey(AttendancePunch)], AttendanceBreak.prototype, "punchId", 2);
195
+ __decorateClass([field({ required: true })], AttendanceBreak.prototype, "openSlot", 2);
196
+ AttendanceBreak = __decorateClass([TenantScoped({ mode: "required" }), smrt({
197
+ tableName: "attendance_breaks",
198
+ indexes: [{
199
+ name: "attendance_breaks_open_punch_key",
200
+ columns: [
201
+ "tenantId",
202
+ "punchId",
203
+ "openSlot"
204
+ ],
205
+ unique: true
206
+ }],
207
+ api: { include: ["list", "get"] },
208
+ cli: { include: ["list", "get"] },
209
+ mcp: { include: ["list", "get"] }
210
+ })], AttendanceBreak);
211
+ var SEGMENT_CLOSE_REASONS = [
212
+ "stop",
213
+ "switch",
214
+ "break",
215
+ "punchOut"
216
+ ];
217
+ var AttendanceSegment = class extends SmrtObject {
218
+ tenantId = "";
219
+ punchId = "";
220
+ /** Qualified class name of the work context (for example a work order). */
221
+ workRefType = "";
222
+ workRefId = "";
223
+ /** Optional second reference (for example an operation); both or neither. */
224
+ taskRefType = "";
225
+ taskRefId = "";
226
+ /** Optional short consumer lane label. */
227
+ kind = "";
228
+ description = "";
229
+ startedAt = /* @__PURE__ */ new Date();
230
+ endedAt = null;
231
+ /** Elapsed whole seconds, set when the segment closes. */
232
+ durationSeconds = 0;
233
+ /** `stop`, `switch`, `break` or `punchOut` once closed; `''` while open. */
234
+ closedReason = "";
235
+ /** Set when a replay time policy asked for review of this segment's boundary. */
236
+ reviewRequired = false;
237
+ openSlot = "";
238
+ async save() {
239
+ return saveWithPunchLock(this, this.punchId, this.tenantId, () => this.saveConsistent());
240
+ }
241
+ async saveConsistent() {
242
+ if (!this.tenantId || !this.punchId || !this.workRefType || !this.workRefId || !!this.taskRefType !== !!this.taskRefId) throw new Error("Invalid attendance work segment.");
243
+ if (!Number.isFinite(this.startedAt?.getTime()) || this.endedAt && (!Number.isFinite(this.endedAt.getTime()) || this.endedAt < this.startedAt)) throw new Error("Invalid attendance segment interval.");
244
+ if (!this.endedAt !== !this.closedReason) throw new Error("A segment has a close reason exactly when it is closed.");
245
+ if (this.closedReason && !SEGMENT_CLOSE_REASONS.includes(this.closedReason)) throw new Error("Invalid segment close reason.");
246
+ const parent = await this.db.get("attendance_punches", {
247
+ id: this.punchId,
248
+ tenant_id: this.tenantId
249
+ });
250
+ if (!parent || this.startedAt.getTime() < new Date(parent.started_at).getTime() || parent.ended_at && (!this.endedAt || this.endedAt.getTime() > new Date(parent.ended_at).getTime())) throw new Error("Segment must be inside its tenant-owned punch.");
251
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
252
+ const corrected = isCorrectionWrite(this);
253
+ if (!prior && parent.ended_at && !corrected) throw new Error("A closed punch changes only through AttendanceCorrectionService.");
254
+ if (prior && (prior.tenant_id !== this.tenantId || prior.punch_id !== this.punchId || !corrected && (prior.work_ref_type !== this.workRefType || prior.work_ref_id !== this.workRefId || prior.task_ref_type !== this.taskRefType || prior.task_ref_id !== this.taskRefId || prior.kind !== this.kind || prior.description !== this.description || new Date(prior.started_at).getTime() !== this.startedAt.getTime()))) throw new Error("Segment ownership, references and start are immutable.");
255
+ if (!corrected && prior?.ended_at && (new Date(prior.ended_at).getTime() !== this.endedAt?.getTime() || prior.closed_reason !== this.closedReason)) throw new Error("Closed segments are immutable.");
256
+ const start = this.startedAt.getTime();
257
+ const end = this.endedAt?.getTime() ?? Number.POSITIVE_INFINITY;
258
+ const overlaps = (row) => start < (row.ended_at ? new Date(row.ended_at).getTime() : Number.POSITIVE_INFINITY) && end > new Date(row.started_at).getTime();
259
+ const where = {
260
+ tenant_id: this.tenantId,
261
+ punch_id: this.punchId
262
+ };
263
+ if ((await this.db.list("attendance_breaks", where)).some(overlaps)) throw new Error("Segment must not overlap a break.");
264
+ if ((await this.db.list(this.tableName, where)).some((row) => row.id !== this.id && overlaps(row))) throw new Error("Segments must not overlap.");
265
+ this.durationSeconds = this.endedAt ? Math.round((end - start) / 1e3) : 0;
266
+ this.id ??= crypto.randomUUID();
267
+ this.openSlot = this.endedAt ? JSON.stringify(["closed", this.id]) : "open";
268
+ return super.save();
269
+ }
270
+ async delete() {
271
+ await saveWithPunchLock(this, this.punchId, this.tenantId, async () => {
272
+ const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
273
+ if ((this.endedAt || prior?.ended_at) && !isCorrectionWrite(this)) throw new Error("Closed segments are immutable; remove one through AttendanceCorrectionService.");
274
+ await super.delete();
275
+ return this;
276
+ });
277
+ }
278
+ };
279
+ __publicField(AttendanceSegment, "__smrtPackage__", "@happyvertical/smrt-timesheets");
280
+ __decorateClass([tenantId()], AttendanceSegment.prototype, "tenantId", 2);
281
+ __decorateClass([foreignKey(AttendancePunch)], AttendanceSegment.prototype, "punchId", 2);
282
+ __decorateClass([field({ required: true })], AttendanceSegment.prototype, "openSlot", 2);
283
+ AttendanceSegment = __decorateClass([TenantScoped({ mode: "required" }), smrt({
284
+ tableName: "attendance_segments",
285
+ indexes: [
286
+ {
287
+ name: "attendance_segments_open_punch_key",
288
+ columns: [
289
+ "tenantId",
290
+ "punchId",
291
+ "openSlot"
292
+ ],
293
+ unique: true
294
+ },
295
+ {
296
+ name: "attendance_segments_work_idx",
297
+ columns: [
298
+ "tenantId",
299
+ "workRefType",
300
+ "workRefId"
301
+ ]
302
+ },
303
+ {
304
+ name: "attendance_segments_task_idx",
305
+ columns: [
306
+ "tenantId",
307
+ "taskRefType",
308
+ "taskRefId"
309
+ ]
310
+ }
311
+ ],
312
+ api: { include: ["list", "get"] },
313
+ cli: { include: ["list", "get"] },
314
+ mcp: { include: ["list", "get"] }
315
+ })], AttendanceSegment);
316
+ function validReplayOutcome(value) {
317
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
318
+ const result = value;
319
+ return typeof result.clientId === "string" && result.clientId.length > 0 && (result.code === void 0 || typeof result.code === "string" && result.code.length > 0 && result.error !== void 0) && (result.segmentId === void 0 || typeof result.segmentId === "string") && (result.resolvedAt === void 0 || typeof result.resolvedAt === "string") && (typeof result.punchId === "string" && result.punchId.length > 0 && result.error === void 0 || typeof result.error === "string" && result.error.length > 0 && result.punchId === void 0);
320
+ }
321
+ var AttendanceReplay = class extends SmrtObject {
322
+ tenantId = "";
323
+ replayKey = "";
324
+ content = "";
325
+ outcome = "{}";
326
+ /** Read a durable receipt, failing closed rather than re-executing a corrupt tap. */
327
+ getOutcome() {
328
+ const value = parseTimesheetJson(this.outcome, null);
329
+ if (!validReplayOutcome(value)) throw new Error("Invalid stored attendance replay outcome.");
330
+ return value;
331
+ }
332
+ /** Store the JSON representation of a validated success or refusal. */
333
+ setOutcome(value) {
334
+ if (!validReplayOutcome(value)) throw new Error("Invalid attendance replay outcome.");
335
+ this.outcome = JSON.stringify(value);
336
+ }
337
+ async save() {
338
+ this.getOutcome();
339
+ return super.save();
340
+ }
341
+ };
342
+ __publicField(AttendanceReplay, "__smrtPackage__", "@happyvertical/smrt-timesheets");
343
+ __decorateClass([tenantId()], AttendanceReplay.prototype, "tenantId", 2);
344
+ __decorateClass([field({
345
+ required: true,
346
+ unique: true
347
+ })], AttendanceReplay.prototype, "replayKey", 2);
348
+ __decorateClass([field({ type: "text" })], AttendanceReplay.prototype, "content", 2);
349
+ __decorateClass([field({ type: "text" })], AttendanceReplay.prototype, "outcome", 2);
350
+ AttendanceReplay = __decorateClass([TenantScoped({ mode: "required" }), smrt({
351
+ tableName: "attendance_replays",
352
+ sensitive: true,
353
+ api: { include: [] },
354
+ cli: { include: [] },
355
+ mcp: { include: [] }
356
+ })], AttendanceReplay);
357
+ var AttendancePunchCollection = class extends SmrtCollection {
358
+ static _itemClass = AttendancePunch;
359
+ };
360
+ var AttendanceBreakCollection = class extends SmrtCollection {
361
+ static _itemClass = AttendanceBreak;
362
+ };
363
+ var AttendanceSegmentCollection = class extends SmrtCollection {
364
+ static _itemClass = AttendanceSegment;
365
+ };
366
+ var AttendanceReplayCollection = class extends SmrtCollection {
367
+ static _itemClass = AttendanceReplay;
368
+ };
369
+ //#endregion
370
+ export { AttendanceReplay as a, AttendanceSegmentCollection as c, persistAutoClose as d, persistCorrection as f, AttendancePunchCollection as i, deleteCorrection as l, AttendanceBreakCollection as n, AttendanceReplayCollection as o, AttendancePunch as r, AttendanceSegment as s, AttendanceBreak as t, isCorrectionWrite as u };
371
+
372
+ //# sourceMappingURL=models-0-YD5F_o.js.map