@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.
- package/AGENTS.md +26 -0
- package/README.md +156 -11
- package/agents/period-rollup.md +20 -0
- package/dist/attendance/context.d.ts +80 -0
- package/dist/attendance/context.d.ts.map +1 -0
- package/dist/attendance/correction-models.d.ts +39 -0
- package/dist/attendance/correction-models.d.ts.map +1 -0
- package/dist/attendance/correction-service.d.ts +118 -0
- package/dist/attendance/correction-service.d.ts.map +1 -0
- package/dist/attendance/models.d.ts +57 -0
- package/dist/attendance/models.d.ts.map +1 -1
- package/dist/attendance/segments.d.ts +20 -0
- package/dist/attendance/segments.d.ts.map +1 -0
- package/dist/attendance/service.d.ts +106 -22
- package/dist/attendance/service.d.ts.map +1 -1
- package/dist/attendance/write.d.ts +12 -0
- package/dist/attendance/write.d.ts.map +1 -0
- package/dist/attendance.d.ts +2 -0
- package/dist/attendance.d.ts.map +1 -1
- package/dist/attendance.js +3 -323
- package/dist/chunks/{attendance-BVeStEBv.js → attendance-B1jCbqSb.js} +3 -3
- package/dist/chunks/{attendance-BVeStEBv.js.map → attendance-B1jCbqSb.js.map} +1 -1
- package/dist/chunks/attendance-DGVy-iHI.js +1246 -0
- package/dist/chunks/attendance-DGVy-iHI.js.map +1 -0
- package/dist/chunks/models-0-YD5F_o.js +372 -0
- package/dist/chunks/models-0-YD5F_o.js.map +1 -0
- package/dist/chunks/{rollup-U1IO_CMA.js → rollup-CDqzCxI2.js} +211 -6
- package/dist/chunks/rollup-CDqzCxI2.js.map +1 -0
- package/dist/chunks/service-time-entry-t7P-bkOQ.js +227 -0
- package/dist/chunks/service-time-entry-t7P-bkOQ.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/manifest.json +2965 -1358
- package/dist/rollup/correction-guard.d.ts +12 -0
- package/dist/rollup/correction-guard.d.ts.map +1 -0
- package/dist/rollup/models.d.ts +31 -1
- package/dist/rollup/models.d.ts.map +1 -1
- package/dist/rollup/service.d.ts +25 -7
- package/dist/rollup/service.d.ts.map +1 -1
- package/dist/rollup/types.d.ts +18 -0
- package/dist/rollup/types.d.ts.map +1 -1
- package/dist/rollup/write.d.ts +2 -0
- package/dist/rollup/write.d.ts.map +1 -1
- package/dist/rollup.d.ts +3 -2
- package/dist/rollup.d.ts.map +1 -1
- package/dist/rollup.js +2 -2
- package/dist/smrt-knowledge.json +700 -34
- package/package.json +7 -7
- package/dist/attendance.js.map +0 -1
- package/dist/chunks/models-zU8Um7c2.js +0 -176
- package/dist/chunks/models-zU8Um7c2.js.map +0 -1
- package/dist/chunks/rollup-U1IO_CMA.js.map +0 -1
- package/dist/chunks/service-time-entry-CBv-FF-W.js +0 -227
- package/dist/chunks/service-time-entry-CBv-FF-W.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-timesheets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Shared time entries for the SMRT framework: who worked how long on what, approval and correction, with immutable charge and compensation snapshots",
|
|
6
6
|
"type": "module",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@happyvertical/smrt-core": "0.
|
|
43
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
44
|
-
"@happyvertical/smrt-types": "0.
|
|
45
|
-
"@happyvertical/smrt-ui": "0.
|
|
46
|
-
"@happyvertical/sql": "^0.103.
|
|
42
|
+
"@happyvertical/smrt-core": "0.56.0",
|
|
43
|
+
"@happyvertical/smrt-tenancy": "0.56.0",
|
|
44
|
+
"@happyvertical/smrt-types": "0.56.0",
|
|
45
|
+
"@happyvertical/smrt-ui": "0.56.0",
|
|
46
|
+
"@happyvertical/sql": "^0.103.6"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"svelte": "^5.57.0"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@happyvertical/smrt-vitest": "0.
|
|
57
|
+
"@happyvertical/smrt-vitest": "0.56.0",
|
|
58
58
|
"@sveltejs/package": "^2.5.8",
|
|
59
59
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
60
60
|
"@types/node": "24.13.6",
|
package/dist/attendance.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attendance.js","names":["unpaidMilliseconds","outcome"],"sources":["../src/attendance/service.ts"],"sourcesContent":["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 { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport {\n AttendanceBreakCollection,\n AttendancePunch,\n AttendancePunchCollection,\n AttendanceReplayCollection,\n type AttendanceReplayOutcome,\n} from './models.js';\n\nexport type { AttendanceReplayOutcome } 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/** Explicit work attribution for a closed punch's draft evidence entry. */\nexport interface AttendanceWork {\n workRefType: string;\n workRefId: string;\n description: string;\n}\n/** Offline command with a durable client-generated identifier and device timestamp. */\nexport interface AttendanceTap {\n clientId: string;\n at: string;\n action: 'punchIn' | 'punchOut' | 'startBreak' | 'endBreak';\n paid?: boolean;\n work?: AttendanceWork;\n}\n\n/** Expected state conflict, distinct from retryable database failures. */\nexport class AttendanceConflict extends Error {}\n\ntype Context = {\n db: DatabaseInterface;\n punches: AttendancePunchCollection;\n breaks: AttendanceBreakCollection;\n replays: AttendanceReplayCollection;\n};\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 if (!isPostgresDatabase(this.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\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 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 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.');\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 latest = Math.max(\n punch.startedAt.getTime(),\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 );\n }\n\n /** Open one punch; the database unique slot also protects direct model writers. */\n async punchIn(at = new Date()): Promise<AttendancePunch> {\n return this.transact((tx) => this.transition(tx, 'punchIn', this.date(at)));\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(at: Date, paid = false): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(tx, 'startBreak', this.date(at), paid),\n );\n }\n /** End the current break. */\n async endBreak(at: Date): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(tx, 'endBreak', this.date(at)),\n );\n }\n\n private async transition(\n tx: Context,\n action: AttendanceTap['action'],\n at: Date,\n paid = false,\n work?: AttendanceWork,\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.');\n return tx.punches.create({\n ...this.actor,\n startedAt: at,\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) throw new AttendanceConflict('A break is already open.');\n if (typeof paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n await tx.breaks.create({\n tenantId: this.actor.tenantId,\n punchId: punch.id ?? undefined,\n startedAt: at,\n paid,\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.');\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') return punch;\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 private validateWork(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 }\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 )\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({ attendancePunchId: punch.id }),\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 /** Close at startedAt + maxOpenSeconds once due, flagging the punch for review. */\n async autoClose(\n now: Date,\n maxOpenSeconds: number,\n ): Promise<AttendancePunch | null> {\n this.date(now);\n if (!Number.isSafeInteger(maxOpenSeconds) || maxOpenSeconds <= 0)\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 const deadline = new Date(\n punch.startedAt.getTime() + maxOpenSeconds * 1000,\n );\n if (deadline > now) return null;\n // Do not truncate a later committed break. The conflict requires manual review.\n const closed = await this.transition(tx, 'punchOut', deadline);\n closed.reviewRequired = true;\n await closed.save();\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 ): 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 !['punchIn', 'punchOut', 'startBreak', 'endBreak'].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 (tap.paid !== undefined && typeof tap.paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\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 ]);\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 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 replays: tx.replays,\n };\n const punch = await this.transition(\n inner,\n tap.action,\n new Date(tap.at),\n tap.paid,\n tap.work,\n );\n punch.recordedOffline = true;\n punch.reviewRequired = true;\n await punch.save();\n return { clientId: tap.clientId, punchId: punch.id! };\n },\n true,\n );\n } catch (error) {\n if (!(error instanceof AttendanceConflict)) throw error;\n outcome = { clientId: tap.clientId, error: error.message };\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"],"mappings":"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"}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { o as parseTimesheetJson } from "./service-time-entry-CBv-FF-W.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/models.ts
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
|
|
8
|
-
enumerable: true,
|
|
9
|
-
configurable: true,
|
|
10
|
-
writable: true,
|
|
11
|
-
value
|
|
12
|
-
}) : obj[key] = value;
|
|
13
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
14
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
15
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
16
|
-
if (kind && result) __defProp(target, key, result);
|
|
17
|
-
return result;
|
|
18
|
-
};
|
|
19
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
20
|
-
async function saveWithPunchLock(model, punchId, tenant, save) {
|
|
21
|
-
return withEmbeddedWriteTransaction(model.db, isEmbeddedDatabase(model.db), (db) => model.withDatabase(db, async () => {
|
|
22
|
-
if (punchId && isPostgresDatabase(db)) await db.query("SELECT id FROM attendance_punches WHERE id = $1 AND tenant_id = $2 FOR UPDATE", punchId, tenant);
|
|
23
|
-
return save();
|
|
24
|
-
}), true);
|
|
25
|
-
}
|
|
26
|
-
var AttendancePunch = class extends SmrtObject {
|
|
27
|
-
tenantId = "";
|
|
28
|
-
profileId = "";
|
|
29
|
-
startedAt = /* @__PURE__ */ new Date();
|
|
30
|
-
endedAt = null;
|
|
31
|
-
durationSeconds = 0;
|
|
32
|
-
unpaidBreakSeconds = 0;
|
|
33
|
-
reviewRequired = false;
|
|
34
|
-
recordedOffline = false;
|
|
35
|
-
serviceTimeEntryId = null;
|
|
36
|
-
openSlot = "";
|
|
37
|
-
async save() {
|
|
38
|
-
return saveWithPunchLock(this, this.id, this.tenantId, () => this.saveConsistent());
|
|
39
|
-
}
|
|
40
|
-
async saveConsistent() {
|
|
41
|
-
if (!this.tenantId || !this.profileId) throw new Error("Attendance requires tenantId and profileId.");
|
|
42
|
-
if (!Number.isFinite(this.startedAt?.getTime()) || this.endedAt && (!Number.isFinite(this.endedAt.getTime()) || this.endedAt < this.startedAt)) throw new Error("Invalid attendance interval.");
|
|
43
|
-
if (![this.durationSeconds, this.unpaidBreakSeconds].every((value) => Number.isSafeInteger(value) && value >= 0)) throw new Error("Attendance durations must be nonnegative safe integer seconds.");
|
|
44
|
-
const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
|
|
45
|
-
if (prior && (prior.tenant_id !== this.tenantId || prior.profile_id !== this.profileId || new Date(prior.started_at).getTime() !== this.startedAt.getTime())) throw new Error("Attendance ownership and start are immutable.");
|
|
46
|
-
if (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.");
|
|
47
|
-
if (this.id && this.endedAt) {
|
|
48
|
-
if ((await this.db.list("attendance_breaks", {
|
|
49
|
-
tenant_id: this.tenantId,
|
|
50
|
-
punch_id: this.id
|
|
51
|
-
})).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.");
|
|
52
|
-
}
|
|
53
|
-
this.id ??= crypto.randomUUID();
|
|
54
|
-
this.openSlot = this.endedAt ? JSON.stringify(["closed", this.id]) : "open";
|
|
55
|
-
return super.save();
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
__publicField(AttendancePunch, "__smrtPackage__", "@happyvertical/smrt-timesheets");
|
|
59
|
-
__decorateClass([tenantId()], AttendancePunch.prototype, "tenantId", 2);
|
|
60
|
-
__decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile")], AttendancePunch.prototype, "profileId", 2);
|
|
61
|
-
__decorateClass([foreignKey("ServiceTimeEntry", { nullable: true })], AttendancePunch.prototype, "serviceTimeEntryId", 2);
|
|
62
|
-
__decorateClass([field({ required: true })], AttendancePunch.prototype, "openSlot", 2);
|
|
63
|
-
AttendancePunch = __decorateClass([TenantScoped({ mode: "required" }), smrt({
|
|
64
|
-
tableName: "attendance_punches",
|
|
65
|
-
indexes: [{
|
|
66
|
-
name: "attendance_punches_open_owner_key",
|
|
67
|
-
columns: [
|
|
68
|
-
"tenantId",
|
|
69
|
-
"profileId",
|
|
70
|
-
"openSlot"
|
|
71
|
-
],
|
|
72
|
-
unique: true
|
|
73
|
-
}],
|
|
74
|
-
api: { include: ["list", "get"] },
|
|
75
|
-
cli: { include: ["list", "get"] },
|
|
76
|
-
mcp: { include: ["list", "get"] }
|
|
77
|
-
})], AttendancePunch);
|
|
78
|
-
var AttendanceBreak = class extends SmrtObject {
|
|
79
|
-
tenantId = "";
|
|
80
|
-
punchId = "";
|
|
81
|
-
startedAt = /* @__PURE__ */ new Date();
|
|
82
|
-
endedAt = null;
|
|
83
|
-
paid = false;
|
|
84
|
-
openSlot = "";
|
|
85
|
-
async save() {
|
|
86
|
-
return saveWithPunchLock(this, this.punchId, this.tenantId, () => this.saveConsistent());
|
|
87
|
-
}
|
|
88
|
-
async saveConsistent() {
|
|
89
|
-
if (!this.tenantId || !this.punchId || typeof this.paid !== "boolean") throw new Error("Invalid attendance break.");
|
|
90
|
-
if (!Number.isFinite(this.startedAt?.getTime()) || this.endedAt && (!Number.isFinite(this.endedAt.getTime()) || this.endedAt < this.startedAt)) throw new Error("Invalid attendance break interval.");
|
|
91
|
-
const parent = await this.db.get("attendance_punches", {
|
|
92
|
-
id: this.punchId,
|
|
93
|
-
tenant_id: this.tenantId
|
|
94
|
-
});
|
|
95
|
-
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.");
|
|
96
|
-
const prior = this.id ? await this.db.get(this.tableName, { id: this.id }) : null;
|
|
97
|
-
if (prior && (prior.tenant_id !== this.tenantId || prior.punch_id !== this.punchId || new Date(prior.started_at).getTime() !== this.startedAt.getTime() || Boolean(prior.paid) !== this.paid)) throw new Error("Break ownership, start and pay policy are immutable.");
|
|
98
|
-
if (prior?.ended_at && new Date(prior.ended_at).getTime() !== this.endedAt?.getTime()) throw new Error("Closed breaks are immutable.");
|
|
99
|
-
this.id ??= crypto.randomUUID();
|
|
100
|
-
this.openSlot = this.endedAt ? JSON.stringify(["closed", this.id]) : "open";
|
|
101
|
-
return super.save();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
__publicField(AttendanceBreak, "__smrtPackage__", "@happyvertical/smrt-timesheets");
|
|
105
|
-
__decorateClass([tenantId()], AttendanceBreak.prototype, "tenantId", 2);
|
|
106
|
-
__decorateClass([foreignKey(AttendancePunch)], AttendanceBreak.prototype, "punchId", 2);
|
|
107
|
-
__decorateClass([field({ required: true })], AttendanceBreak.prototype, "openSlot", 2);
|
|
108
|
-
AttendanceBreak = __decorateClass([TenantScoped({ mode: "required" }), smrt({
|
|
109
|
-
tableName: "attendance_breaks",
|
|
110
|
-
indexes: [{
|
|
111
|
-
name: "attendance_breaks_open_punch_key",
|
|
112
|
-
columns: [
|
|
113
|
-
"tenantId",
|
|
114
|
-
"punchId",
|
|
115
|
-
"openSlot"
|
|
116
|
-
],
|
|
117
|
-
unique: true
|
|
118
|
-
}],
|
|
119
|
-
api: { include: ["list", "get"] },
|
|
120
|
-
cli: { include: ["list", "get"] },
|
|
121
|
-
mcp: { include: ["list", "get"] }
|
|
122
|
-
})], AttendanceBreak);
|
|
123
|
-
function validReplayOutcome(value) {
|
|
124
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
125
|
-
const result = value;
|
|
126
|
-
return typeof result.clientId === "string" && result.clientId.length > 0 && (typeof result.punchId === "string" && result.punchId.length > 0 && result.error === void 0 || typeof result.error === "string" && result.error.length > 0 && result.punchId === void 0);
|
|
127
|
-
}
|
|
128
|
-
var AttendanceReplay = class extends SmrtObject {
|
|
129
|
-
tenantId = "";
|
|
130
|
-
replayKey = "";
|
|
131
|
-
content = "";
|
|
132
|
-
outcome = "{}";
|
|
133
|
-
/** Read a durable receipt, failing closed rather than re-executing a corrupt tap. */
|
|
134
|
-
getOutcome() {
|
|
135
|
-
const value = parseTimesheetJson(this.outcome, null);
|
|
136
|
-
if (!validReplayOutcome(value)) throw new Error("Invalid stored attendance replay outcome.");
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
/** Store the JSON representation of a validated success or refusal. */
|
|
140
|
-
setOutcome(value) {
|
|
141
|
-
if (!validReplayOutcome(value)) throw new Error("Invalid attendance replay outcome.");
|
|
142
|
-
this.outcome = JSON.stringify(value);
|
|
143
|
-
}
|
|
144
|
-
async save() {
|
|
145
|
-
this.getOutcome();
|
|
146
|
-
return super.save();
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
__publicField(AttendanceReplay, "__smrtPackage__", "@happyvertical/smrt-timesheets");
|
|
150
|
-
__decorateClass([tenantId()], AttendanceReplay.prototype, "tenantId", 2);
|
|
151
|
-
__decorateClass([field({
|
|
152
|
-
required: true,
|
|
153
|
-
unique: true
|
|
154
|
-
})], AttendanceReplay.prototype, "replayKey", 2);
|
|
155
|
-
__decorateClass([field({ type: "text" })], AttendanceReplay.prototype, "content", 2);
|
|
156
|
-
__decorateClass([field({ type: "text" })], AttendanceReplay.prototype, "outcome", 2);
|
|
157
|
-
AttendanceReplay = __decorateClass([TenantScoped({ mode: "required" }), smrt({
|
|
158
|
-
tableName: "attendance_replays",
|
|
159
|
-
sensitive: true,
|
|
160
|
-
api: { include: [] },
|
|
161
|
-
cli: { include: [] },
|
|
162
|
-
mcp: { include: [] }
|
|
163
|
-
})], AttendanceReplay);
|
|
164
|
-
var AttendancePunchCollection = class extends SmrtCollection {
|
|
165
|
-
static _itemClass = AttendancePunch;
|
|
166
|
-
};
|
|
167
|
-
var AttendanceBreakCollection = class extends SmrtCollection {
|
|
168
|
-
static _itemClass = AttendanceBreak;
|
|
169
|
-
};
|
|
170
|
-
var AttendanceReplayCollection = class extends SmrtCollection {
|
|
171
|
-
static _itemClass = AttendanceReplay;
|
|
172
|
-
};
|
|
173
|
-
//#endregion
|
|
174
|
-
export { AttendanceReplay as a, AttendancePunchCollection as i, AttendanceBreakCollection as n, AttendanceReplayCollection as o, AttendancePunch as r, AttendanceBreak as t };
|
|
175
|
-
|
|
176
|
-
//# sourceMappingURL=models-zU8Um7c2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"models-zU8Um7c2.js","names":[],"sources":["../../src/attendance/models.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n foreignKey,\n isEmbeddedDatabase,\n isPostgresDatabase,\n SmrtCollection,\n SmrtObject,\n smrt,\n withEmbeddedWriteTransaction,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { parseTimesheetJson } from '../models/json.js';\n\n// Punch closes and break writes share one atomic parent boundary, including direct ORM saves.\nasync function saveWithPunchLock<T extends SmrtObject>(\n model: T,\n punchId: string | null | undefined,\n tenant: string,\n save: () => Promise<T>,\n): Promise<T> {\n return withEmbeddedWriteTransaction(\n model.db,\n isEmbeddedDatabase(model.db),\n (db) =>\n model.withDatabase(db, async () => {\n if (punchId && isPostgresDatabase(db))\n await db.query(\n 'SELECT id FROM attendance_punches WHERE id = $1 AND tenant_id = $2 FOR UPDATE',\n punchId,\n tenant,\n );\n return save();\n }),\n true,\n );\n}\n\n/** A tenant-scoped interval of attendance, independent of billable work. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_punches',\n indexes: [\n {\n name: 'attendance_punches_open_owner_key',\n columns: ['tenantId', 'profileId', 'openSlot'],\n unique: true,\n },\n ],\n api: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n})\nexport class AttendancePunch extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile') profileId = '';\n startedAt: Date = new Date();\n endedAt: Date | null = null;\n durationSeconds = 0;\n unpaidBreakSeconds = 0;\n reviewRequired = false;\n recordedOffline = false;\n // Resolve the active table family, including smrt-support's same-named subtype.\n @foreignKey('ServiceTimeEntry', { nullable: true }) serviceTimeEntryId:\n | string\n | null = null;\n /** Unique while open; closed rows retain independent non-null slots on every dialect. */\n @field({ required: true }) openSlot = '';\n\n override async save(): Promise<this> {\n return saveWithPunchLock(this, this.id, this.tenantId, () =>\n this.saveConsistent(),\n );\n }\n\n private async saveConsistent(): Promise<this> {\n if (!this.tenantId || !this.profileId)\n throw new Error('Attendance requires tenantId and profileId.');\n if (\n !Number.isFinite(this.startedAt?.getTime()) ||\n (this.endedAt &&\n (!Number.isFinite(this.endedAt.getTime()) ||\n this.endedAt < this.startedAt))\n )\n throw new Error('Invalid attendance interval.');\n if (\n ![this.durationSeconds, this.unpaidBreakSeconds].every(\n (value) => Number.isSafeInteger(value) && value >= 0,\n )\n )\n throw new Error(\n 'Attendance durations must be nonnegative safe integer seconds.',\n );\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n new Date(prior.started_at as string).getTime() !==\n this.startedAt.getTime())\n )\n throw new Error('Attendance ownership and start are immutable.');\n if (\n prior?.ended_at &&\n (new Date(prior.ended_at as string).getTime() !==\n this.endedAt?.getTime() ||\n Number(prior.duration_seconds) !== this.durationSeconds ||\n Number(prior.unpaid_break_seconds) !== this.unpaidBreakSeconds)\n )\n throw new Error('Closed attendance evidence is immutable.');\n if (this.id && this.endedAt) {\n const breaks = await this.db.list('attendance_breaks', {\n tenant_id: this.tenantId,\n punch_id: this.id,\n });\n if (\n breaks.some(\n (item) =>\n !item.ended_at ||\n new Date(item.started_at as string).getTime() <\n this.startedAt.getTime() ||\n new Date(item.ended_at as string).getTime() >\n this.endedAt!.getTime(),\n )\n )\n throw new Error('Break must be inside its tenant-owned punch.');\n }\n this.id ??= crypto.randomUUID();\n this.openSlot = this.endedAt ? JSON.stringify(['closed', this.id]) : 'open';\n return super.save();\n }\n}\n\n/** A paid or unpaid break inside one attendance punch. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_breaks',\n indexes: [\n {\n name: 'attendance_breaks_open_punch_key',\n columns: ['tenantId', 'punchId', 'openSlot'],\n unique: true,\n },\n ],\n api: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n})\nexport class AttendanceBreak extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @foreignKey(AttendancePunch) punchId = '';\n startedAt: Date = new Date();\n endedAt: Date | null = null;\n paid = false;\n @field({ required: true }) openSlot = '';\n\n override async save(): Promise<this> {\n return saveWithPunchLock(this, this.punchId, this.tenantId, () =>\n this.saveConsistent(),\n );\n }\n\n private async saveConsistent(): Promise<this> {\n if (!this.tenantId || !this.punchId || typeof this.paid !== 'boolean')\n throw new Error('Invalid attendance break.');\n if (\n !Number.isFinite(this.startedAt?.getTime()) ||\n (this.endedAt &&\n (!Number.isFinite(this.endedAt.getTime()) ||\n this.endedAt < this.startedAt))\n )\n throw new Error('Invalid attendance break interval.');\n const parent = await this.db.get('attendance_punches', {\n id: this.punchId,\n tenant_id: this.tenantId,\n });\n if (\n !parent ||\n this.startedAt.getTime() <\n new Date(parent.started_at as string).getTime() ||\n (parent.ended_at &&\n (!this.endedAt ||\n this.endedAt.getTime() >\n new Date(parent.ended_at as string).getTime()))\n )\n throw new Error('Break must be inside its tenant-owned punch.');\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.punch_id !== this.punchId ||\n new Date(prior.started_at as string).getTime() !==\n this.startedAt.getTime() ||\n Boolean(prior.paid) !== this.paid)\n )\n throw new Error('Break ownership, start and pay policy are immutable.');\n if (\n prior?.ended_at &&\n new Date(prior.ended_at as string).getTime() !== this.endedAt?.getTime()\n )\n throw new Error('Closed breaks are immutable.');\n this.id ??= crypto.randomUUID();\n this.openSlot = this.endedAt ? JSON.stringify(['closed', this.id]) : 'open';\n return super.save();\n }\n}\n\n/** Durable success or conflict; conflicts do not mutate attendance. */\nexport interface AttendanceReplayOutcome {\n clientId: string;\n punchId?: string;\n error?: string;\n}\n\nfunction validReplayOutcome(value: unknown): value is AttendanceReplayOutcome {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const result = value as Record<string, unknown>;\n return (\n typeof result.clientId === 'string' &&\n result.clientId.length > 0 &&\n ((typeof result.punchId === 'string' &&\n result.punchId.length > 0 &&\n result.error === undefined) ||\n (typeof result.error === 'string' &&\n result.error.length > 0 &&\n result.punchId === undefined))\n );\n}\n\n/** Durable offline-tap outcome; never exposed through generated operations. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_replays',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class AttendanceReplay extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @field({ required: true, unique: true }) replayKey = '';\n @field({ type: 'text' }) content = '';\n @field({ type: 'text' }) outcome = '{}';\n\n /** Read a durable receipt, failing closed rather than re-executing a corrupt tap. */\n getOutcome(): AttendanceReplayOutcome {\n const value = parseTimesheetJson<unknown>(this.outcome, null);\n if (!validReplayOutcome(value))\n throw new Error('Invalid stored attendance replay outcome.');\n return value;\n }\n\n /** Store the JSON representation of a validated success or refusal. */\n setOutcome(value: AttendanceReplayOutcome): void {\n if (!validReplayOutcome(value))\n throw new Error('Invalid attendance replay outcome.');\n this.outcome = JSON.stringify(value);\n }\n\n override async save(): Promise<this> {\n this.getOutcome();\n return super.save();\n }\n}\n\n/** Punches for attendance and period rollup. */\nexport class AttendancePunchCollection extends SmrtCollection<AttendancePunch> {\n static readonly _itemClass = AttendancePunch;\n}\n/** Break intervals for net duration and period clipping. */\nexport class AttendanceBreakCollection extends SmrtCollection<AttendanceBreak> {\n static readonly _itemClass = AttendanceBreak;\n}\n/** Internal durable replay ledger. */\nexport class AttendanceReplayCollection extends SmrtCollection<AttendanceReplay> {\n static readonly _itemClass = AttendanceReplay;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rollup-U1IO_CMA.js","names":["id"],"sources":["../../src/rollup/write.ts","../../src/rollup/models.ts","../../src/rollup/calculation.ts","../../src/rollup/service.ts"],"sourcesContent":["import type { SmrtObject } from '@happyvertical/smrt-core';\n\n// Per-instance capability, deliberately absent from the package export surface.\nconst permitted = new WeakSet<SmrtObject>();\nexport function assertRollupWrite(object: SmrtObject): void {\n if (!permitted.has(object))\n throw new Error(\n 'Timecards and adjustments are writable only through PeriodRollupService.',\n );\n}\nexport async function persistRollup<T extends SmrtObject>(\n object: T,\n): Promise<T> {\n permitted.add(object);\n try {\n await object.save();\n return object;\n } finally {\n permitted.delete(object);\n }\n}\n","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 type { TimecardSource } from './types.js';\nimport { assertRollupWrite } from './write.js';\n\n/** One person's immutable-on-confirmation period snapshot, in integer seconds. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'period_timecards',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class PeriodTimecard extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n startsAt: Date = new Date(0);\n endsAt: Date = new Date(0);\n timezone = '';\n rulesVersion = '';\n @field({ type: 'text' }) rulesSnapshot = '{}';\n @field({ type: 'text' }) sourceSnapshot = '[]';\n regularSeconds = 0;\n overtimeSeconds = 0;\n totalSeconds = 0;\n @field({ type: 'text' }) status: 'open' | 'confirmed' = 'open';\n confirmedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n nullable: true,\n onDelete: 'RESTRICT',\n })\n confirmedByProfileId: string | null = null;\n\n /** Return a detached source snapshot; corrupt persisted JSON fails closed. */\n getSources(): TimecardSource[] {\n const result: unknown = JSON.parse(this.sourceSnapshot);\n if (!Array.isArray(result))\n throw new Error('Invalid timecard source snapshot.');\n return result as TimecardSource[];\n }\n /** Return the consumer policy frozen with this rollup. */\n getRules(): Record<string, unknown> {\n const result: unknown = JSON.parse(this.rulesSnapshot);\n if (!result || typeof result !== 'object' || Array.isArray(result))\n throw new Error('Invalid timecard rules snapshot.');\n return result as Record<string, unknown>;\n }\n override async save(): Promise<this> {\n assertRollupWrite(this);\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior?.status === 'confirmed')\n throw new Error(\n 'Confirmed timecards are immutable; append an adjustment.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n new Date(prior.starts_at as string).getTime() !==\n this.startsAt.getTime())\n )\n throw new Error('Timecard identity is immutable.');\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new Error('Timecard history cannot be deleted.');\n }\n}\n\n/** Attributed append-only signed seconds; the confirmed base card never changes. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'timecard_adjustments',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class TimecardAdjustment extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @foreignKey(PeriodTimecard, { onDelete: 'RESTRICT' }) timecardId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n profileId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', {\n onDelete: 'RESTRICT',\n })\n actorProfileId = '';\n operationId = '';\n regularSeconds = 0;\n overtimeSeconds = 0;\n reason = '';\n override async save(): Promise<this> {\n assertRollupWrite(this);\n if (this.id && (await this.db.get(this.tableName, { id: this.id })))\n throw new Error('Timecard adjustments are append-only.');\n this.requireInsertOnSave();\n return (await super.save()) as this;\n }\n override async delete(): Promise<void> {\n throw new Error('Timecard adjustments cannot be deleted.');\n }\n}\n/** Scoped persistence collection; mutations must use PeriodRollupService. */\nexport class PeriodTimecardCollection extends SmrtCollection<PeriodTimecard> {\n static readonly _itemClass = PeriodTimecard;\n}\n/** Scoped append-only adjustment collection. */\nexport class TimecardAdjustmentCollection extends SmrtCollection<TimecardAdjustment> {\n static readonly _itemClass = TimecardAdjustment;\n}\n","import type { TimecardPeriod, TimecardSource } from './types.js';\n\nexport function integer(value: number, signed = false): number {\n if (!Number.isSafeInteger(value) || (!signed && value < 0))\n throw new Error('Timecard seconds must be safe integers and nonnegative.');\n return value;\n}\nexport function instant(value: Date): number {\n if (!(value instanceof Date) || !Number.isFinite(value.getTime()))\n throw new Error('Timecard timestamps must be valid dates.');\n return value.getTime();\n}\nexport function validatePeriod(\n value: TimecardPeriod,\n at: Date,\n): TimecardPeriod {\n if (\n !value ||\n instant(value.startsAt) > instant(at) ||\n instant(value.endsAt) <= instant(at) ||\n instant(value.startsAt) >= instant(value.endsAt)\n )\n throw new Error('Resolver period must contain the requested instant.');\n if (!value.timezone || !value.version?.trim())\n throw new Error('Resolver must supply a timezone and policy version.');\n new Intl.DateTimeFormat('en', { timeZone: value.timezone });\n if (\n !value.rules ||\n typeof value.rules !== 'object' ||\n Array.isArray(value.rules)\n )\n throw new Error('Resolver rules must be a JSON object.');\n // JSON round-trip detaches consumer-owned state; fail rather than silently lose non-JSON values.\n const json = JSON.stringify(value.rules, (_key, item) => {\n if (\n typeof item === 'function' ||\n typeof item === 'undefined' ||\n typeof item === 'symbol' ||\n (typeof item === 'number' && !Number.isFinite(item))\n )\n throw new Error('Resolver rules must be JSON serializable.');\n return item;\n });\n const rules: unknown = JSON.parse(json);\n if (!rules || typeof rules !== 'object' || Array.isArray(rules))\n throw new Error('Resolver rules must serialize to a JSON object.');\n return {\n startsAt: new Date(value.startsAt),\n endsAt: new Date(value.endsAt),\n timezone: value.timezone,\n version: value.version,\n rules: rules as Record<string, unknown>,\n };\n}\n/** Prefix allocation preserves integer seconds across adjacent half-open periods. */\nexport function clipSource(\n source: TimecardSource,\n period: TimecardPeriod,\n): TimecardSource | null {\n const start = instant(new Date(source.startsAt));\n const end = instant(new Date(source.endsAt));\n integer(source.seconds);\n if (end < start) throw new Error('Invalid source interval.');\n const left = Math.max(start, period.startsAt.getTime());\n const right = Math.min(end, period.endsAt.getTime());\n if (start === end)\n return start >= period.startsAt.getTime() && start < period.endsAt.getTime()\n ? { ...source }\n : null;\n if (left >= right) return null;\n const span = integer(end - start);\n const prefix = (elapsed: number) =>\n Number((BigInt(source.seconds) * BigInt(integer(elapsed))) / BigInt(span));\n const seconds = prefix(right - start) - prefix(left - start);\n return {\n ...source,\n startsAt: new Date(left).toISOString(),\n endsAt: new Date(right).toISOString(),\n seconds: integer(seconds),\n };\n}\n","import { createHash } from 'node:crypto';\nimport {\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 { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport { clipSource, instant, integer, validatePeriod } from './calculation.js';\nimport {\n PeriodTimecard,\n PeriodTimecardCollection,\n TimecardAdjustment,\n TimecardAdjustmentCollection,\n} from './models.js';\nimport type {\n PeriodRulesResolver,\n TimecardActor,\n TimecardAdjustmentInput,\n TimecardSource,\n} from './types.js';\nimport { persistRollup } from './write.js';\n\nfunction identity(parts: string[]): string {\n const bytes = createHash('sha256').update(JSON.stringify(parts)).digest();\n bytes[6] = (bytes[6] & 15) | 80;\n bytes[8] = (bytes[8] & 63) | 128;\n const hex = bytes.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nfunction adjustedSeconds(\n base: number,\n rows: TimecardAdjustment[],\n field: 'regularSeconds' | 'overtimeSeconds',\n): number {\n const total = rows.reduce(\n (sum, row) => sum + BigInt(integer(row[field], true)),\n BigInt(integer(base)),\n );\n return integer(Number(total));\n}\n\n/**\n * Server-side, trusted tenant/person capability. Consumers authorize this scope\n * before construction. Reads and writes reject conflicting ambient tenancy.\n * SQLite and PostgreSQL are supported; policy and payroll remain consumer-owned.\n */\nexport class PeriodRollupService {\n constructor(\n private readonly db: DatabaseInterface,\n private readonly actor: TimecardActor,\n private readonly resolver: PeriodRulesResolver,\n private readonly options: {\n attendance?: boolean;\n actorProfileId?: string;\n } = {},\n ) {\n if (!actor.tenantId?.trim() || !actor.profileId?.trim())\n throw new Error('Timecards require a trusted tenant/profile actor.');\n this.actor = Object.freeze({\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n });\n if (options.actorProfileId !== undefined && !options.actorProfileId.trim())\n throw new Error('Actor attribution must be a nonempty profile id.');\n this.options = {\n attendance: options.attendance,\n actorProfileId: this.identity(\n options.actorProfileId ?? this.actor.profileId,\n ),\n };\n }\n\n private identity(value: string): string {\n if (!isPostgresDatabase(this.db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new Error('PostgreSQL timecard 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 private scope<T>(run: () => Promise<T>): Promise<T> {\n const active = getTenantId();\n if (active && this.identity(active) !== this.actor.tenantId)\n throw new Error('Timecard actor differs from active tenant.');\n return withTenant({ tenantId: this.actor.tenantId }, run);\n }\n private transact<T>(run: (db: DatabaseInterface) => Promise<T>): Promise<T> {\n return this.scope(() =>\n 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.rollup',\n this.actor.tenantId,\n this.actor.profileId,\n ]),\n );\n if (this.options.attendance && 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 run(db);\n },\n true,\n ),\n );\n }\n private async card(\n db: DatabaseInterface,\n id: string,\n ): Promise<PeriodTimecard> {\n const collection = await PeriodTimecardCollection.create({ db });\n const [card] = await collection.list({\n where: { ...this.actor, id },\n limit: 1,\n });\n if (!card) throw new Error('Timecard not found in actor scope.');\n return card;\n }\n /** Scoped read on the root executor, including after transaction completion. */\n async get(id: string): Promise<PeriodTimecard> {\n return this.scope(() => this.card(this.db, id));\n }\n /** Rebuild one open card from a consistent transaction-bound source snapshot. */\n async rollup(at: Date): Promise<PeriodTimecard> {\n at = new Date(instant(at));\n const id = await this.transact(async (db) => {\n const period = validatePeriod(\n await this.resolver.periodFor(new Date(at), this.actor),\n at,\n );\n const id = identity([\n 'smrt.period',\n this.actor.tenantId,\n this.actor.profileId,\n period.startsAt.toISOString(),\n ]);\n const collection = await PeriodTimecardCollection.create({ db });\n const [prior] = await collection.list({\n where: { ...this.actor, id },\n limit: 1,\n });\n if (prior?.status === 'confirmed')\n throw new Error(\n 'Confirmed timecards are immutable; append an adjustment.',\n );\n // A policy change must not create overlapping cards for the same person.\n for (const other of await collection.list({ where: { ...this.actor } })) {\n if (\n other.id !== id &&\n other.startsAt < period.endsAt &&\n other.endsAt > period.startsAt\n )\n throw new Error('Resolver period overlaps an existing timecard.');\n }\n let sources: TimecardSource[] = [];\n let linkedEntryIds = new Set<string>();\n if (this.options.attendance) {\n const { attendanceSources } = await import('./attendance.js');\n ({ sources, linkedEntryIds } = await attendanceSources(\n db,\n this.actor,\n period,\n ));\n }\n const entries = await ServiceTimeEntryCollection.create({ db });\n const evidence = await entries.list({\n where: {\n tenantId: this.actor.tenantId,\n participantProfileId: this.actor.profileId,\n participantKind: 'human',\n },\n });\n const byId = new Map(evidence.map((entry) => [String(entry.id), entry]));\n const approvedRoots = new Set<string>();\n for (const entry of evidence) {\n if (entry.status !== 'approved') continue;\n let linked = linkedEntryIds.has(String(entry.id));\n let ancestor = entry;\n const seen = new Set([String(entry.id)]);\n while (ancestor.correctionOfId) {\n const parentId = ancestor.correctionOfId;\n if (seen.has(parentId))\n throw new Error('Cyclic evidence correction chain.');\n seen.add(parentId);\n const parent = byId.get(parentId);\n if (!parent || parent.status !== 'corrected')\n throw new Error(\n 'Correction requires corrected evidence in the same actor scope.',\n );\n linked ||= linkedEntryIds.has(parentId);\n ancestor = parent;\n }\n const rootId = String(ancestor.id);\n if (approvedRoots.has(rootId))\n throw new Error('Ambiguous approved evidence correction chain.');\n approvedRoots.add(rootId);\n if (linked) continue;\n // Duration-only manual evidence belongs to its approval instant. A\n // bounded interval allocates its actual duration by elapsed fraction.\n const start = entry.startedAt ?? entry.approvedAt;\n if (!start)\n throw new Error(\n 'Approved evidence needs a start or approval timestamp.',\n );\n // Hours-only sources are unbounded and attributed at approval. An\n // unsupported quantity in another period must not block this one.\n if (\n entry.durationSeconds === null &&\n (instant(start) < period.startsAt.getTime() ||\n instant(start) >= period.endsAt.getTime())\n )\n continue;\n const end = entry.startedAt && entry.endedAt ? entry.endedAt : start;\n const source = clipSource(\n {\n kind: 'entry',\n id: String(entry.id),\n startsAt: new Date(instant(start)).toISOString(),\n endsAt: new Date(instant(end)).toISOString(),\n seconds: integer(entry.requireDurationSeconds()),\n },\n period,\n );\n if (source?.seconds) sources.push(source);\n }\n sources.sort(\n (a, b) =>\n a.startsAt.localeCompare(b.startsAt) ||\n a.kind.localeCompare(b.kind) ||\n a.id.localeCompare(b.id),\n );\n const totalSeconds = sources.reduce(\n (sum, source) => integer(sum + source.seconds),\n 0,\n );\n // Resolver receives detached snapshots so it cannot alter persisted evidence.\n const classified = await this.resolver.classify(\n structuredClone(period),\n structuredClone(sources),\n this.actor,\n );\n const regularSeconds = integer(classified?.regularSeconds);\n const overtimeSeconds = integer(classified?.overtimeSeconds);\n if (integer(regularSeconds + overtimeSeconds) !== totalSeconds)\n throw new Error('Resolver classification must conserve total seconds.');\n const card =\n prior ??\n (await new PeriodTimecard({ db, id, _skipLoad: true }).initialize());\n if (!prior) card.requireInsertOnSave();\n Object.assign(card, this.actor, {\n startsAt: period.startsAt,\n endsAt: period.endsAt,\n timezone: period.timezone,\n rulesVersion: period.version,\n rulesSnapshot: JSON.stringify(period.rules),\n sourceSnapshot: JSON.stringify(sources),\n regularSeconds,\n overtimeSeconds,\n totalSeconds,\n });\n await persistRollup(card);\n return id;\n });\n return this.get(id);\n }\n /** Confirm the current snapshot; repeated confirmation is idempotent. */\n async confirm(id: string): Promise<PeriodTimecard> {\n await this.transact(async (db) => {\n const card = await this.card(db, id);\n if (card.status === 'confirmed') return;\n card.status = 'confirmed';\n card.confirmedAt = new Date();\n card.confirmedByProfileId = this.options.actorProfileId!;\n await persistRollup(card);\n });\n return this.get(id);\n }\n /** Append a correction. An operation key retry must have exactly the same payload. */\n async adjust(\n id: string,\n input: TimecardAdjustmentInput,\n ): Promise<TimecardAdjustment> {\n id = this.identity(id);\n input = {\n operationId: input.operationId,\n reason: input.reason,\n regularSeconds: input.regularSeconds,\n overtimeSeconds: input.overtimeSeconds,\n };\n integer(input.regularSeconds, true);\n integer(input.overtimeSeconds, true);\n if (!input.operationId?.trim() || !input.reason?.trim())\n throw new Error('Adjustment requires an operationId and reason.');\n const adjustmentId = identity([\n 'smrt.adjustment',\n this.actor.tenantId,\n this.actor.profileId,\n id,\n input.operationId,\n ]);\n await this.transact(async (db) => {\n const card = await this.card(db, id);\n if (card.status !== 'confirmed')\n throw new Error('Adjustments require a confirmed timecard.');\n const collection = await TimecardAdjustmentCollection.create({ db });\n const rows = await collection.list({\n where: { ...this.actor, timecardId: id },\n });\n const prior = rows.find((row) => row.id === adjustmentId);\n if (prior) {\n if (\n prior.regularSeconds !== input.regularSeconds ||\n prior.overtimeSeconds !== input.overtimeSeconds ||\n prior.reason !== input.reason ||\n prior.actorProfileId !== this.options.actorProfileId\n )\n throw new Error(\n 'Adjustment operationId reused with a different payload.',\n );\n return;\n }\n const regular = adjustedSeconds(\n card.regularSeconds,\n rows,\n 'regularSeconds',\n );\n const overtime = adjustedSeconds(\n card.overtimeSeconds,\n rows,\n 'overtimeSeconds',\n );\n integer(\n integer(regular + input.regularSeconds) +\n integer(overtime + input.overtimeSeconds),\n );\n const adjustment = await new TimecardAdjustment({\n db,\n id: adjustmentId,\n _skipLoad: true,\n }).initialize();\n Object.assign(adjustment, this.actor, {\n timecardId: id,\n actorProfileId: this.options.actorProfileId!,\n ...input,\n });\n await persistRollup(adjustment);\n });\n return this.scope(async () => {\n const collection = await TimecardAdjustmentCollection.create({\n db: this.db,\n });\n const [row] = await collection.list({\n where: { ...this.actor, id: adjustmentId },\n limit: 1,\n });\n if (!row) throw new Error('Committed adjustment disappeared.');\n return row;\n });\n }\n /** Effective totals combine the frozen base with append-only corrections. */\n async totals(id: string): Promise<{\n regularSeconds: number;\n overtimeSeconds: number;\n totalSeconds: number;\n }> {\n return this.transact(async (db) => {\n const card = await this.card(db, id);\n const collection = await TimecardAdjustmentCollection.create({ db });\n const rows = await collection.list({\n where: { ...this.actor, timecardId: id },\n });\n const regularSeconds = adjustedSeconds(\n card.regularSeconds,\n rows,\n 'regularSeconds',\n );\n const overtimeSeconds = adjustedSeconds(\n card.overtimeSeconds,\n rows,\n 'overtimeSeconds',\n );\n return {\n regularSeconds,\n overtimeSeconds,\n totalSeconds: integer(regularSeconds + overtimeSeconds),\n };\n });\n }\n}\n"],"mappings":"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"}
|