@happyvertical/smrt-timesheets 0.56.0 → 0.56.2
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 +34 -0
- package/README.md +34 -1
- package/dist/attendance.js +2 -2
- package/dist/chunks/{attendance-DGVy-iHI.js → attendance-CDfhph1R.js} +3 -3
- package/dist/chunks/{attendance-DGVy-iHI.js.map → attendance-CDfhph1R.js.map} +1 -1
- package/dist/chunks/{attendance-B1jCbqSb.js → attendance-mk6nqJhX.js} +3 -3
- package/dist/chunks/{attendance-B1jCbqSb.js.map → attendance-mk6nqJhX.js.map} +1 -1
- package/dist/chunks/{models-0-YD5F_o.js → models-C1krjn40.js} +2 -2
- package/dist/chunks/{models-0-YD5F_o.js.map → models-C1krjn40.js.map} +1 -1
- package/dist/chunks/{rollup-CDqzCxI2.js → rollup-C8p1PAXX.js} +3 -3
- package/dist/chunks/{rollup-CDqzCxI2.js.map → rollup-C8p1PAXX.js.map} +1 -1
- package/dist/chunks/service-time-entry-Cxh0pt0J.js +286 -0
- package/dist/chunks/service-time-entry-Cxh0pt0J.js.map +1 -0
- package/dist/chunks/time-entry-actions-Bp--TxUD.js +576 -0
- package/dist/chunks/time-entry-actions-Bp--TxUD.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -334
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +373 -17
- package/dist/models/service-snapshots.d.ts.map +1 -1
- package/dist/models/service-time-entry.d.ts +36 -1
- package/dist/models/service-time-entry.d.ts.map +1 -1
- package/dist/recipes.d.ts +124 -0
- package/dist/recipes.d.ts.map +1 -0
- package/dist/rollup.js +1 -1
- package/dist/services/action-principal.d.ts +54 -0
- package/dist/services/action-principal.d.ts.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/time-entry-actions.d.ts +83 -0
- package/dist/services/time-entry-actions.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +230 -34
- package/package.json +8 -7
- package/dist/chunks/service-time-entry-t7P-bkOQ.js +0 -227
- package/dist/chunks/service-time-entry-t7P-bkOQ.js.map +0 -1
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
6
|
-
"packageVersion": "0.56.
|
|
6
|
+
"packageVersion": "0.56.2",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "758a908da24db7967e18896d23983514e45a4c5ee3df46e83113176bed939839",
|
|
11
|
+
"packageJson": "c349b624889d708bcfded901c47cb4fa444d32752994417f6809c27f4f4539b2",
|
|
12
|
+
"agents": "67979245cd75faa81e381084a73ab20fd166fbd875345a8c01e3a3da2ec8ffaf",
|
|
13
13
|
"moduleDoc:agents/period-rollup.md": "7e693235a49e8354a8910f480cde8a7aecdc4f28afaa6d778fd4bfaf67119b46"
|
|
14
14
|
},
|
|
15
15
|
"exports": [
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@happyvertical/smrt-tenancy": "workspace:*",
|
|
27
27
|
"@happyvertical/smrt-types": "workspace:*",
|
|
28
28
|
"@happyvertical/smrt-ui": "workspace:*",
|
|
29
|
+
"@happyvertical/smrt-users": "workspace:*",
|
|
29
30
|
"@happyvertical/sql": "catalog:",
|
|
30
31
|
"@happyvertical/smrt-vitest": "workspace:*",
|
|
31
32
|
"@sveltejs/package": "^2.5.8",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"@happyvertical/smrt-tenancy",
|
|
43
44
|
"@happyvertical/smrt-types",
|
|
44
45
|
"@happyvertical/smrt-ui",
|
|
46
|
+
"@happyvertical/smrt-users",
|
|
45
47
|
"@happyvertical/smrt-vitest"
|
|
46
48
|
],
|
|
47
49
|
"sdkDependencies": [
|
|
@@ -1293,7 +1295,6 @@
|
|
|
1293
1295
|
"name": "ServiceChargeSnapshotCollection",
|
|
1294
1296
|
"qualifiedName": "@happyvertical/smrt-timesheets:ServiceChargeSnapshotCollection",
|
|
1295
1297
|
"collection": "servicechargesnapshots",
|
|
1296
|
-
"description": "Collection over service_charge_snapshots.",
|
|
1297
1298
|
"tableName": "service_charge_snapshots",
|
|
1298
1299
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
1299
1300
|
"extends": "SmrtCollection",
|
|
@@ -1311,7 +1312,6 @@
|
|
|
1311
1312
|
"name": "ServiceCompensationSnapshotCollection",
|
|
1312
1313
|
"qualifiedName": "@happyvertical/smrt-timesheets:ServiceCompensationSnapshotCollection",
|
|
1313
1314
|
"collection": "servicecompensationsnapshots",
|
|
1314
|
-
"description": "Collection over service_compensation_snapshots.",
|
|
1315
1315
|
"tableName": "service_compensation_snapshots",
|
|
1316
1316
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
1317
1317
|
"extends": "SmrtCollection",
|
|
@@ -1711,10 +1711,21 @@
|
|
|
1711
1711
|
"fields": [],
|
|
1712
1712
|
"relationships": [],
|
|
1713
1713
|
"methods": [
|
|
1714
|
+
"approve",
|
|
1714
1715
|
"forWork",
|
|
1715
|
-
"pendingApproval"
|
|
1716
|
+
"pendingApproval",
|
|
1717
|
+
"record",
|
|
1718
|
+
"submit"
|
|
1716
1719
|
],
|
|
1717
1720
|
"methodSignatures": [
|
|
1721
|
+
{
|
|
1722
|
+
"name": "approve",
|
|
1723
|
+
"async": true,
|
|
1724
|
+
"params": [
|
|
1725
|
+
"options: ServiceTimeEntryActionTarget"
|
|
1726
|
+
],
|
|
1727
|
+
"returns": "Promise<ServiceTimeEntry>"
|
|
1728
|
+
},
|
|
1718
1729
|
{
|
|
1719
1730
|
"name": "forWork",
|
|
1720
1731
|
"async": true,
|
|
@@ -1728,36 +1739,64 @@
|
|
|
1728
1739
|
"name": "pendingApproval",
|
|
1729
1740
|
"async": true,
|
|
1730
1741
|
"returns": "Promise<ServiceTimeEntry[]>"
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
"name": "record",
|
|
1745
|
+
"async": true,
|
|
1746
|
+
"params": [
|
|
1747
|
+
"options: RecordServiceTimeActionInput"
|
|
1748
|
+
],
|
|
1749
|
+
"returns": "Promise<ServiceTimeEntry>"
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
"name": "submit",
|
|
1753
|
+
"async": true,
|
|
1754
|
+
"params": [
|
|
1755
|
+
"options: ServiceTimeEntryActionTarget"
|
|
1756
|
+
],
|
|
1757
|
+
"returns": "Promise<ServiceTimeEntry>"
|
|
1731
1758
|
}
|
|
1732
1759
|
],
|
|
1733
1760
|
"surfaces": [
|
|
1734
1761
|
{
|
|
1735
1762
|
"kind": "api",
|
|
1736
|
-
"name": "servicetimeentries.
|
|
1737
|
-
"operation": "
|
|
1763
|
+
"name": "servicetimeentries.record",
|
|
1764
|
+
"operation": "record",
|
|
1738
1765
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1739
|
-
"path": "/servicetimeentries/
|
|
1766
|
+
"path": "/servicetimeentries/record",
|
|
1740
1767
|
"method": "POST"
|
|
1741
1768
|
},
|
|
1742
1769
|
{
|
|
1743
1770
|
"kind": "api",
|
|
1744
|
-
"name": "servicetimeentries.
|
|
1745
|
-
"operation": "
|
|
1771
|
+
"name": "servicetimeentries.submit",
|
|
1772
|
+
"operation": "submit",
|
|
1746
1773
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1747
|
-
"path": "/servicetimeentries/
|
|
1774
|
+
"path": "/servicetimeentries/submit",
|
|
1748
1775
|
"method": "POST"
|
|
1749
1776
|
},
|
|
1750
1777
|
{
|
|
1751
|
-
"kind": "
|
|
1752
|
-
"name": "
|
|
1778
|
+
"kind": "api",
|
|
1779
|
+
"name": "servicetimeentries.approve",
|
|
1780
|
+
"operation": "approve",
|
|
1781
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1782
|
+
"path": "/servicetimeentries/approve",
|
|
1783
|
+
"method": "POST"
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
"kind": "api",
|
|
1787
|
+
"name": "servicetimeentries.forWork",
|
|
1753
1788
|
"operation": "forWork",
|
|
1754
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
1789
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1790
|
+
"path": "/servicetimeentries/forWork",
|
|
1791
|
+
"method": "POST"
|
|
1755
1792
|
},
|
|
1756
1793
|
{
|
|
1757
|
-
"kind": "
|
|
1758
|
-
"name": "
|
|
1794
|
+
"kind": "api",
|
|
1795
|
+
"name": "servicetimeentries.pendingApproval",
|
|
1759
1796
|
"operation": "pendingApproval",
|
|
1760
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
1797
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1798
|
+
"path": "/servicetimeentries/pendingApproval",
|
|
1799
|
+
"method": "POST"
|
|
1761
1800
|
},
|
|
1762
1801
|
{
|
|
1763
1802
|
"kind": "mcp",
|
|
@@ -2548,31 +2587,43 @@
|
|
|
2548
2587
|
},
|
|
2549
2588
|
{
|
|
2550
2589
|
"kind": "api",
|
|
2551
|
-
"name": "servicetimeentries.
|
|
2552
|
-
"operation": "
|
|
2590
|
+
"name": "servicetimeentries.record",
|
|
2591
|
+
"operation": "record",
|
|
2553
2592
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2554
|
-
"path": "/servicetimeentries/
|
|
2593
|
+
"path": "/servicetimeentries/record",
|
|
2555
2594
|
"method": "POST"
|
|
2556
2595
|
},
|
|
2557
2596
|
{
|
|
2558
2597
|
"kind": "api",
|
|
2559
|
-
"name": "servicetimeentries.
|
|
2560
|
-
"operation": "
|
|
2598
|
+
"name": "servicetimeentries.submit",
|
|
2599
|
+
"operation": "submit",
|
|
2561
2600
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2562
|
-
"path": "/servicetimeentries/
|
|
2601
|
+
"path": "/servicetimeentries/submit",
|
|
2563
2602
|
"method": "POST"
|
|
2564
2603
|
},
|
|
2565
2604
|
{
|
|
2566
|
-
"kind": "
|
|
2567
|
-
"name": "
|
|
2605
|
+
"kind": "api",
|
|
2606
|
+
"name": "servicetimeentries.approve",
|
|
2607
|
+
"operation": "approve",
|
|
2608
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2609
|
+
"path": "/servicetimeentries/approve",
|
|
2610
|
+
"method": "POST"
|
|
2611
|
+
},
|
|
2612
|
+
{
|
|
2613
|
+
"kind": "api",
|
|
2614
|
+
"name": "servicetimeentries.forWork",
|
|
2568
2615
|
"operation": "forWork",
|
|
2569
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
2616
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2617
|
+
"path": "/servicetimeentries/forWork",
|
|
2618
|
+
"method": "POST"
|
|
2570
2619
|
},
|
|
2571
2620
|
{
|
|
2572
|
-
"kind": "
|
|
2573
|
-
"name": "
|
|
2621
|
+
"kind": "api",
|
|
2622
|
+
"name": "servicetimeentries.pendingApproval",
|
|
2574
2623
|
"operation": "pendingApproval",
|
|
2575
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
2624
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2625
|
+
"path": "/servicetimeentries/pendingApproval",
|
|
2626
|
+
"method": "POST"
|
|
2576
2627
|
},
|
|
2577
2628
|
{
|
|
2578
2629
|
"kind": "mcp",
|
|
@@ -2596,7 +2647,7 @@
|
|
|
2596
2647
|
"polymorphicAssociations": 0,
|
|
2597
2648
|
"uuidColumns": 92
|
|
2598
2649
|
},
|
|
2599
|
-
"agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n`AttendanceSegment` (`attendance_segments`) is a running work interval inside a\npunch: queryable `workRef*`/`taskRef*`/`kind` columns, one open per punch\n(`openSlot`), closed by `startBreak`/`punchOut`/`autoClose`, never overlapping\nbreaks or each other, immutable once closed. It creates no `ServiceTimeEntry`.\nClosed punches, breaks and segments change only through\n`AttendanceCorrectionService` (`attendance/correction-service.ts`), which sets a\nper-instance capability (`attendance/write.ts`, not exported) that lifts the\nmodels' closed-interval checks for one save, validates the whole punch layout,\nrecomputes durations and appends an `AttendanceCorrection` (append-only ledger,\nno generated operations; only a first acknowledgement by the subject may update\na row). Never export `write.ts`. Punch `source`/`deviceRef*` and break `kind` are immutable except through corrections; `autoCloseReason` is write-once via `persistAutoClose`. A confirmed `PeriodTimecard` is honoured by the\nopt-in `confirmedTimecardGuard` (`rollup/correction-guard.ts`), which refuses\nwhile a card is submitted or confirmed. `PeriodRollupService.reopen()` returns\nsuch a card to `open` and appends a `TimecardReopening` (`timecard_reopenings`,\nappend-only, service-only via `rollup/write.ts`); `submit()` is the optional\nopen -> submitted step (`requireSubmission` makes it mandatory) (#3950).\nSegment transitions live in `attendance/segments.ts`; compose them with other\nattendance writes via `AttendanceService.withTransaction`. Any new attendance\ntable must be added to the `includeObjects` lists in the `*.optional.test.ts`\nfiles.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\nDisplay components keep the legacy one-decimal hours format by default. A\nconsumer that retains more precise source evidence supplies `hoursFormatter`;\ncard/list/queue detail snippets carry domain evidence without widening the base\nview. ApprovalQueue's optional action snippet replaces its callback controls so\nthe host can own a native form, request identity, authorization and retry flow.\nDisplay currency codes are open ISO strings and amounts remain integer minor\nunits; never put private rates into a view that should not expose them.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n\nReplay hashes the tap AS SENT (action, device `at`, paid, work, and `source`/`device`/`kind` only when present, so older receipts still match) so retries match;\n`replay(taps, { resolveAt, now, refusal })` only changes the stored time and\nreason. Never hash the resolved time. Work actions (`startWork`, `switchWork`,\n`stopWork`, `resumeWork`) run in the tap's transaction via `apply()`. Refusals store\n`code` from `AttendanceConflict.code`; give new conflicts a code.\n",
|
|
2650
|
+
"agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n`AttendanceSegment` (`attendance_segments`) is a running work interval inside a\npunch: queryable `workRef*`/`taskRef*`/`kind` columns, one open per punch\n(`openSlot`), closed by `startBreak`/`punchOut`/`autoClose`, never overlapping\nbreaks or each other, immutable once closed. It creates no `ServiceTimeEntry`.\nClosed punches, breaks and segments change only through\n`AttendanceCorrectionService` (`attendance/correction-service.ts`), which sets a\nper-instance capability (`attendance/write.ts`, not exported) that lifts the\nmodels' closed-interval checks for one save, validates the whole punch layout,\nrecomputes durations and appends an `AttendanceCorrection` (append-only ledger,\nno generated operations; only a first acknowledgement by the subject may update\na row). Never export `write.ts`. Punch `source`/`deviceRef*` and break `kind` are immutable except through corrections; `autoCloseReason` is write-once via `persistAutoClose`. A confirmed `PeriodTimecard` is honoured by the\nopt-in `confirmedTimecardGuard` (`rollup/correction-guard.ts`), which refuses\nwhile a card is submitted or confirmed. `PeriodRollupService.reopen()` returns\nsuch a card to `open` and appends a `TimecardReopening` (`timecard_reopenings`,\nappend-only, service-only via `rollup/write.ts`); `submit()` is the optional\nopen -> submitted step (`requireSubmission` makes it mandatory) (#3950).\nSegment transitions live in `attendance/segments.ts`; compose them with other\nattendance writes via `AttendanceService.withTransaction`. Any new attendance\ntable must be added to the `includeObjects` lists in the `*.optional.test.ts`\nfiles.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Generated write actions (#4001)\n\nGenerated CRUD writes stay closed. `ServiceTimeEntryCollection` hosts the only\ngenerated writes, `record` / `submit` / `approve` (`POST\n/servicetimeentries/<action>`, body `{ entryId }` for submit/approve), on REST\nonly; MCP and CLI stay read-only until per-operation MCP authorization (#4004).\n`services/time-entry-actions.ts` delegates each to `ServiceEvidenceService`, so\nthe trail and snapshots are set in one place. `services/action-principal.ts`\ntakes identity from the tenancy ALS, then the smrt-users session ALS, never the\nbody; authorizes `servicetimeentries.<action>` with `assertOperationPermission`\n(system context never bypasses; `approve` refuses super-admin bypass); and\nstamps the user's linked profile. Only the participant records/submits their\nown time unless the caller holds `approve`. `approve` prices with the host's\n`setServiceTimeEntryCommercialResolver()` — a factory called per request with\nthe collection's options, so a writing resolver shares the request executor;\nwithout one it is a 409 and writes nothing. The runtime REST transport gates\na collection-hosted action on the ITEM's `api.include`, SvelteKit on the\ncollection's, so both list the actions;\na consumer closing them overrides both registrations. The collection and both\nsnapshot collections carry their own `@smrt()` surface: undecorated, they\npublished generated create/update/delete MCP tools. `reject` waits for #3994.\n\n## Recipe\n\n`src/recipes.ts` declares one `SmrtRecipe`, `timesheets.time-entries`\n(label \"Time entries\", section `work`), emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`. It shows\n`ServiceTimeEntry` alone, with the generic work reference, evidence, agent\nand approval-trail fields hidden; attendance and rollup models stay out of\nit. `status` and its trail are hidden and locked, and the help never says to\nset them: `save()` accepts a bare transition, so submit and approve must go\nthrough `ServiceEvidenceService` (the recipe declares no surface for that). Help lives in `src/time-entries.recipe.md` (#3591); keep it and the recipe\nin plain language. See [core recipes](../core/agents/recipes.md).\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\nDisplay components keep the legacy one-decimal hours format by default. A\nconsumer that retains more precise source evidence supplies `hoursFormatter`;\ncard/list/queue detail snippets carry domain evidence without widening the base\nview. ApprovalQueue's optional action snippet replaces its callback controls so\nthe host can own a native form, request identity, authorization and retry flow.\nDisplay currency codes are open ISO strings and amounts remain integer minor\nunits; never put private rates into a view that should not expose them.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n\nReplay hashes the tap AS SENT (action, device `at`, paid, work, and `source`/`device`/`kind` only when present, so older receipts still match) so retries match;\n`replay(taps, { resolveAt, now, refusal })` only changes the stored time and\nreason. Never hash the resolved time. Work actions (`startWork`, `switchWork`,\n`stopWork`, `resumeWork`) run in the tap's transaction via `apply()`. Refusals store\n`code` from `AttendanceConflict.code`; give new conflicts a code.\n",
|
|
2600
2651
|
"moduleDocs": [
|
|
2601
2652
|
{
|
|
2602
2653
|
"path": "agents/period-rollup.md",
|
|
@@ -2604,14 +2655,159 @@
|
|
|
2604
2655
|
"content": "# Period timecards\n\n`@happyvertical/smrt-timesheets/rollup` exports `PeriodRollupService`,\n`PeriodTimecard`, `TimecardAdjustment`, their collections, and resolver types.\nThe package root also exports them. SQLite and PostgreSQL are supported.\nCreate application tables through normal migrations; neither service provisions\nschema. The attendance option additionally requires the attendance tables.\n\nConstruct a service only after authenticating and authorizing its trusted\n`{ tenantId, profileId }` scope. A conflicting ambient tenant is rejected.\n`options.actorProfileId` records a separately authorized supervisor; it defaults\nto the subject profile for self confirmation/correction. Never accept this\ntrusted scope or attribution unchecked from request input. Profile authorization\nis consumer-owned; the service filters every source/card/adjustment read by its\ntrusted subject and tenant. Generic API, CLI and MCP surfaces are closed by\ndefault; consumer registry overrides may further restrict them (#3318).\n\n## Resolver contract\n\n`periodFor(at, actor)` supplies half-open UTC `startsAt`/`endsAt`, an IANA\n`timezone`, a nonempty policy `version`, and a JSON `rules` snapshot.\nThe consumer owns period length, calendar start day, timezone and DST, overtime\nthresholds, holidays and classification. The requested instant must be inside\nthe window. Overlapping windows for existing cards are rejected: introduce a new\ncalendar at a boundary after existing windows, and use adjustments for already\nconfirmed periods.\n\n`classify(period, sources, actor)` returns nonnegative safe integer\n`regularSeconds` and `overtimeSeconds`, whose sum must equal source seconds.\nInputs are detached so resolver mutation cannot rewrite evidence. Resolver\nexceptions, malformed windows, invalid timezone, fractional/unsafe totals and\nnonconserving classification abort the transaction. No Alberta or other\njurisdiction rule is assumed, and no pay rate or money is stored.\n\nA configurable UTC calendar example (illustrative policy, not payroll advice):\n\n```ts\nimport { PeriodRollupService, type PeriodRulesResolver } from\n '@happyvertical/smrt-timesheets/rollup';\n\nconst settings = {\n lengthDays: 14, startDay: 1, timezone: 'UTC', // 0 = Sunday\n anchor: '2026-01-05T00:00:00Z', // aligned Monday; anchors fortnight phase\n thresholdSeconds: 80 * 3600,\n holidays: ['2026-12-25'],\n};\nconst resolver: PeriodRulesResolver = {\n async periodFor(at) {\n const anchor = new Date(settings.anchor);\n if (anchor.getUTCDay() !== settings.startDay || settings.timezone !== 'UTC')\n throw new Error('Use a timezone-aware calendar resolver for local periods');\n const length = settings.lengthDays * 86400000;\n const start = anchor.getTime() +\n Math.floor((at.getTime() - anchor.getTime()) / length) * length;\n return {\n startsAt: new Date(start), endsAt: new Date(start + length),\n timezone: settings.timezone, version: 'shop-policy-v1', rules: settings,\n };\n },\n async classify(period, sources) {\n const rules = period.rules as typeof settings;\n // This example assigns a contribution to its clipped start's UTC date.\n // Local daily rules should split contributions at their own local midnight.\n let regular = 0;\n let holiday = 0;\n for (const source of sources) {\n if (rules.holidays.includes(source.startsAt.slice(0, 10))) holiday += source.seconds;\n else regular += source.seconds;\n }\n return {\n regularSeconds: Math.min(regular, rules.thresholdSeconds),\n overtimeSeconds: holiday + Math.max(0, regular - rules.thresholdSeconds),\n };\n },\n};\nconst rollups = new PeriodRollupService(db, authorizedSubject, resolver,\n { attendance: true, actorProfileId: authenticatedSupervisorProfileId });\nconst card = await rollups.rollup(new Date());\nawait rollups.confirm(card.id!);\nawait rollups.adjust(card.id!, {\n operationId: requestId, regularSeconds: 900, overtimeSeconds: 0,\n reason: 'Approved correction after confirmation',\n});\nconst effectiveSeconds = await rollups.totals(card.id!);\n```\n\nA timezone-aware resolver must compute each local boundary separately: a local\nday crossing DST is not necessarily 86400 seconds. Resolvers can use an app's\nexisting calendar library; no new timezone dependency or implicit default is\nintroduced here.\n\n## Evidence and immutability\n\nDecimal-hours-only entries have no measured integer seconds. The seconds-only\nrollup rejects these sources explicitly when their approval instant falls in the\nrequested period; it never rounds them or counts null as\nzero. Use an hours-aware consumer policy for that source representation.\n\nOnly approved human `ServiceTimeEntry` rows for the subject count; corrected\nancestors do not. Correction chains must remain within that subject/tenant and\npoint to corrected parents. Multiple approved descendants of one root are\nrejected as ambiguous rather than double-counted. Entries with both `startedAt`\nand `endedAt` allocate their declared seconds\nproportionally across the period using exact integer prefix division, preserving\nseconds over adjacent periods. Unbounded entries count at `startedAt`, or, when\nabsent, `approvedAt`; an end-only timestamp does not create an interval. Missing\nboth attribution timestamps is an error. This is time attribution, not a\nclaim that a manual entry has a continuous wall-clock duration.\n\nWith `attendance: true`, closed punches without `reviewRequired` count, excluding\nunpaid breaks and retaining paid breaks. Open/review-required punches do not\ncount. Linked attendance is authoritative: its evidence entry and any approved\ncorrection descendants are excluded, including when the punch is still under\nreview or outside the requested period. Unlinked approved work entries and\nattendance both count; consumers must link overlapping evidence explicitly.\nSource snapshots preserve each paid attendance segment and entry identity.\n\nRepeated rollup updates one open card per tenant/person/start; a confirmed card\ncannot be recomputed. Confirmation is attributed and idempotent. Model save,\ncollection create/update/upsert and object/collection delete cannot bypass the\nservice. Model mutation capabilities are per-instance and not exported.\nDirect SQL is a trusted administrative boundary, not an exposed application API.\nProfile/card foreign keys restrict deletion to preserve history.\n\nAdjustments contain signed integer regular/overtime deltas, reason, operation\nkey and actor attribution. A retry reuses its row only for the exact same\npayload and actor. Effective totals may never become negative or unsafe.\nAdjustments never alter the confirmed row or source snapshot. Undo an adjustment\nwith a separately attributed compensating adjustment, never deletion.\n\nAll service mutations use a root transaction, transaction-bound collections,\nembedded writer serialization or a PostgreSQL tenant/person advisory lock.\nOutputs are reloaded on the root executor after commit. Rollup reads are an\napplication snapshot: subsequent source approvals/corrections appear on the\nnext open rollup, and confirmed cards require explicit adjustments.\n\n## Lifecycle: submit, confirm, reopen (#3950)\n\n`open` (draft) -> `submitted` (optional) -> `confirmed`. `submit(id)` freezes an\nopen card for review; `confirm(id)` accepts an open card too unless the service\nis built with `{ requireSubmission: true }`. Submitted and confirmed cards are\nnot re-rolled.\n\n`reopen(id, { operationId, reason })` returns a submitted or confirmed card to\n`open` (clearing its submission/confirmation attribution) and appends a\n`TimecardReopening`: actor, reason, prior status and a JSON snapshot of the\nprevious base, adjustment and effective seconds (`getPrevious()`). It is\nidempotent per `operationId` and refuses a blank reason. `reopenings(id)` lists\nthe history. The consumer authorizes the actor; give reopening its own\npermission. Existing adjustments stay and keep counting in `totals()`: after a\nre-roll, offset ones the correction made redundant with a new adjustment.\n`confirmedTimecardGuard` refuses corrections while a card is submitted or\nconfirmed and allows them once it is reopened. The new `timecard_reopenings`\ntable, and nullable `submitted_at`/`submitted_by_profile_id` columns on\n`period_timecards`, need a migration.\n"
|
|
2605
2656
|
}
|
|
2606
2657
|
],
|
|
2658
|
+
"recipes": [
|
|
2659
|
+
{
|
|
2660
|
+
"id": "timesheets.time-entries",
|
|
2661
|
+
"className": "TimeEntriesRecipe",
|
|
2662
|
+
"label": "Time entries",
|
|
2663
|
+
"summary": "Record who worked how long on what, for approval.",
|
|
2664
|
+
"synonyms": [
|
|
2665
|
+
"timesheets",
|
|
2666
|
+
"time tracking",
|
|
2667
|
+
"hours",
|
|
2668
|
+
"billable hours",
|
|
2669
|
+
"timecards"
|
|
2670
|
+
],
|
|
2671
|
+
"models": [
|
|
2672
|
+
"@happyvertical/smrt-timesheets:ServiceTimeEntry"
|
|
2673
|
+
],
|
|
2674
|
+
"nav": [
|
|
2675
|
+
{
|
|
2676
|
+
"label": "Time entries",
|
|
2677
|
+
"model": "@happyvertical/smrt-timesheets:ServiceTimeEntry",
|
|
2678
|
+
"icon": "fileText",
|
|
2679
|
+
"description": "Hours worked, by person and by job, from draft to approved."
|
|
2680
|
+
}
|
|
2681
|
+
],
|
|
2682
|
+
"requires": [],
|
|
2683
|
+
"section": {
|
|
2684
|
+
"id": "work",
|
|
2685
|
+
"label": "Work",
|
|
2686
|
+
"icon": "wrench",
|
|
2687
|
+
"description": "Time and costs: the work behind each job."
|
|
2688
|
+
},
|
|
2689
|
+
"options": {
|
|
2690
|
+
"@happyvertical/smrt-timesheets:ServiceTimeEntry": {
|
|
2691
|
+
"fields": {
|
|
2692
|
+
"participantProfileId": {
|
|
2693
|
+
"label": "Person",
|
|
2694
|
+
"order": 1
|
|
2695
|
+
},
|
|
2696
|
+
"description": {
|
|
2697
|
+
"order": 2,
|
|
2698
|
+
"help": "What the time was spent on, in a few words."
|
|
2699
|
+
},
|
|
2700
|
+
"startedAt": {
|
|
2701
|
+
"label": "Started",
|
|
2702
|
+
"order": 3
|
|
2703
|
+
},
|
|
2704
|
+
"endedAt": {
|
|
2705
|
+
"label": "Ended",
|
|
2706
|
+
"order": 4
|
|
2707
|
+
},
|
|
2708
|
+
"durationSeconds": {
|
|
2709
|
+
"label": "Duration (seconds)",
|
|
2710
|
+
"order": 5,
|
|
2711
|
+
"help": "Whole seconds: 3600 is one hour."
|
|
2712
|
+
},
|
|
2713
|
+
"workRefId": {
|
|
2714
|
+
"label": "Work reference",
|
|
2715
|
+
"order": 6,
|
|
2716
|
+
"help": "The job, project or case this time belongs to."
|
|
2717
|
+
},
|
|
2718
|
+
"workRefType": {
|
|
2719
|
+
"visibility": "hidden"
|
|
2720
|
+
},
|
|
2721
|
+
"participantKind": {
|
|
2722
|
+
"default": "human",
|
|
2723
|
+
"visibility": "hidden"
|
|
2724
|
+
},
|
|
2725
|
+
"agentRef": {
|
|
2726
|
+
"visibility": "hidden"
|
|
2727
|
+
},
|
|
2728
|
+
"source": {
|
|
2729
|
+
"default": "manual",
|
|
2730
|
+
"visibility": "hidden"
|
|
2731
|
+
},
|
|
2732
|
+
"evidence": {
|
|
2733
|
+
"visibility": "hidden"
|
|
2734
|
+
},
|
|
2735
|
+
"status": {
|
|
2736
|
+
"visibility": "hidden",
|
|
2737
|
+
"locked": true
|
|
2738
|
+
},
|
|
2739
|
+
"submittedAt": {
|
|
2740
|
+
"visibility": "hidden",
|
|
2741
|
+
"locked": true
|
|
2742
|
+
},
|
|
2743
|
+
"submittedByProfileId": {
|
|
2744
|
+
"visibility": "hidden",
|
|
2745
|
+
"locked": true
|
|
2746
|
+
},
|
|
2747
|
+
"approvedAt": {
|
|
2748
|
+
"visibility": "hidden",
|
|
2749
|
+
"locked": true
|
|
2750
|
+
},
|
|
2751
|
+
"approvedByProfileId": {
|
|
2752
|
+
"visibility": "hidden",
|
|
2753
|
+
"locked": true
|
|
2754
|
+
},
|
|
2755
|
+
"approvalPath": {
|
|
2756
|
+
"visibility": "hidden",
|
|
2757
|
+
"locked": true
|
|
2758
|
+
},
|
|
2759
|
+
"rejectedAt": {
|
|
2760
|
+
"visibility": "hidden",
|
|
2761
|
+
"locked": true
|
|
2762
|
+
},
|
|
2763
|
+
"rejectedByProfileId": {
|
|
2764
|
+
"visibility": "hidden",
|
|
2765
|
+
"locked": true
|
|
2766
|
+
},
|
|
2767
|
+
"rejectionReason": {
|
|
2768
|
+
"visibility": "hidden",
|
|
2769
|
+
"locked": true
|
|
2770
|
+
},
|
|
2771
|
+
"correctionOfId": {
|
|
2772
|
+
"visibility": "hidden",
|
|
2773
|
+
"locked": true
|
|
2774
|
+
},
|
|
2775
|
+
"metadata": {
|
|
2776
|
+
"visibility": "hidden"
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
"help": {
|
|
2782
|
+
"markdown": "## Overview\n\nTime entries record who worked how long on what. Each entry names the person,\nwhat they did and when, and moves from draft to submitted to approved.\n\n## Tasks\n\n### Record time\n\n1. Create a new time entry.\n2. Pick the **{field:participantProfileId}** who did the work.\n3. Write a short **{field:description}** of what was done.\n4. Enter when it **{field:startedAt}** and **{field:endedAt}**, or type the\n **{field:durationSeconds}** directly (3600 is one hour).\n\n### Send it for approval\n\nApproval is not something you change on this form. It runs through your\napp's approval screen, which records who did each step and when.\n\n1. When an entry is ready to check, the person who did the work submits it.\n2. An approver, someone your app allows to approve time, approves it or sends\n it back with a reason. Approving fixes what the hours are charged and paid\n at, so an approved entry never changes; correct it with a new entry.\n3. If your app has no approval screen yet, entries stay as drafts until one is\n added.\n",
|
|
2783
|
+
"fieldRefs": [
|
|
2784
|
+
"description",
|
|
2785
|
+
"durationSeconds",
|
|
2786
|
+
"endedAt",
|
|
2787
|
+
"participantProfileId",
|
|
2788
|
+
"startedAt"
|
|
2789
|
+
]
|
|
2790
|
+
},
|
|
2791
|
+
"demo": {
|
|
2792
|
+
"mode": "server",
|
|
2793
|
+
"reasons": [
|
|
2794
|
+
"Needs a server because its package does not build for a browser: node:crypto; and its package depends on @happyvertical/smrt-profiles, @happyvertical/smrt-tenancy, @happyvertical/smrt-users, which do not build for a browser (#3617, #3624, #3626)."
|
|
2795
|
+
]
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
],
|
|
2607
2799
|
"browser": {
|
|
2608
2800
|
"status": "server-only",
|
|
2609
2801
|
"issues": [
|
|
2610
|
-
"#
|
|
2802
|
+
"#3617",
|
|
2803
|
+
"#3624",
|
|
2804
|
+
"#3626"
|
|
2611
2805
|
],
|
|
2612
2806
|
"reason": "node:crypto",
|
|
2613
2807
|
"via": [
|
|
2614
|
-
"@happyvertical/smrt-
|
|
2808
|
+
"@happyvertical/smrt-profiles",
|
|
2809
|
+
"@happyvertical/smrt-tenancy",
|
|
2810
|
+
"@happyvertical/smrt-users"
|
|
2615
2811
|
]
|
|
2616
2812
|
}
|
|
2617
2813
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-timesheets",
|
|
3
|
-
"version": "0.56.
|
|
3
|
+
"version": "0.56.2",
|
|
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,12 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@happyvertical/smrt-core": "0.56.
|
|
43
|
-
"@happyvertical/smrt-tenancy": "0.56.
|
|
44
|
-
"@happyvertical/smrt-types": "0.56.
|
|
45
|
-
"@happyvertical/smrt-ui": "0.56.
|
|
46
|
-
"@happyvertical/
|
|
42
|
+
"@happyvertical/smrt-core": "0.56.2",
|
|
43
|
+
"@happyvertical/smrt-tenancy": "0.56.2",
|
|
44
|
+
"@happyvertical/smrt-types": "0.56.2",
|
|
45
|
+
"@happyvertical/smrt-ui": "0.56.2",
|
|
46
|
+
"@happyvertical/smrt-users": "0.56.2",
|
|
47
|
+
"@happyvertical/sql": "^0.103.7"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"svelte": "^5.57.0"
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
}
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
|
-
"@happyvertical/smrt-vitest": "0.56.
|
|
58
|
+
"@happyvertical/smrt-vitest": "0.56.2",
|
|
58
59
|
"@sveltejs/package": "^2.5.8",
|
|
59
60
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
60
61
|
"@types/node": "24.13.6",
|